diff --git a/.all-contributorsrc b/.all-contributorsrc index e6854a477467..32f5734184c4 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3258,6 +3258,278 @@ "contributions": [ "code" ] + }, + { + "login": "apophizzz", + "name": "Patrick Kleindienst", + "avatar_url": "https://avatars.githubusercontent.com/u/12052783?v=4", + "profile": "https://github.com/apophizzz", + "contributions": [ + "code" + ] + }, + { + "login": "proceane", + "name": "Juyeon", + "avatar_url": "https://avatars.githubusercontent.com/u/62143949?v=4", + "profile": "https://github.com/proceane", + "contributions": [ + "translation" + ] + }, + { + "login": "mammadyahyayev", + "name": "Mammad Yahyayev", + "avatar_url": "https://avatars.githubusercontent.com/u/66476643?v=4", + "profile": "https://mammadyahya.vercel.app/", + "contributions": [ + "doc" + ] + }, + { + "login": "SalmaAzeem", + "name": "Salma", + "avatar_url": "https://avatars.githubusercontent.com/u/121863224?v=4", + "profile": "https://github.com/SalmaAzeem", + "contributions": [ + "code" + ] + }, + { + "login": "CodeMaverick-143", + "name": "Arpit Sarang", + "avatar_url": "https://avatars.githubusercontent.com/u/182847716?v=4", + "profile": "https://codemaverick-143.github.io/My-Portfolio/", + "contributions": [ + "code" + ] + }, + { + "login": "mayatarek", + "name": "Maya", + "avatar_url": "https://avatars.githubusercontent.com/u/111644421?v=4", + "profile": "https://github.com/mayatarek", + "contributions": [ + "translation" + ] + }, + { + "login": "HabibaMekay", + "name": "HabibaMekay", + "avatar_url": "https://avatars.githubusercontent.com/u/133516736?v=4", + "profile": "https://github.com/HabibaMekay", + "contributions": [ + "code" + ] + }, + { + "login": "Ahmed-Taha-981", + "name": "Ahmed-Taha-981", + "avatar_url": "https://avatars.githubusercontent.com/u/122402269?v=4", + "profile": "https://github.com/Ahmed-Taha-981", + "contributions": [ + "code" + ] + }, + { + "login": "malak-elbanna", + "name": "Malak Elbanna", + "avatar_url": "https://avatars.githubusercontent.com/u/67643605?v=4", + "profile": "https://malakelbanna.netlify.app/", + "contributions": [ + "code" + ] + }, + { + "login": "depthlending", + "name": "BiKangNing", + "avatar_url": "https://avatars.githubusercontent.com/u/164312726?v=4", + "profile": "https://github.com/depthlending", + "contributions": [ + "doc" + ] + }, + { + "login": "TarunVishwakarma1", + "name": "Tarun Vishwakarma", + "avatar_url": "https://avatars.githubusercontent.com/u/138651451?v=4", + "profile": "https://github.com/TarunVishwakarma1", + "contributions": [ + "code" + ] + }, + { + "login": "shahdhoss", + "name": "Shahd Hossam", + "avatar_url": "https://avatars.githubusercontent.com/u/132148556?v=4", + "profile": "https://github.com/shahdhoss", + "contributions": [ + "code" + ] + }, + { + "login": "mehdirahimi", + "name": "Mehdi Rahimi", + "avatar_url": "https://avatars.githubusercontent.com/u/24210842?v=4", + "profile": "https://mehdirahimi.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "clintaire", + "name": "Clint Airé", + "avatar_url": "https://avatars.githubusercontent.com/u/111376518?v=4", + "profile": "https://github.com/clintaire", + "contributions": [ + "code" + ] + }, + { + "login": "darkhyper24", + "name": "darkhyper24", + "avatar_url": "https://avatars.githubusercontent.com/u/132711528?v=4", + "profile": "https://github.com/darkhyper24", + "contributions": [ + "code" + ] + }, + { + "login": "MohanedAtef238", + "name": "Mohaned Atef", + "avatar_url": "https://avatars.githubusercontent.com/u/105852138?v=4", + "profile": "https://github.com/MohanedAtef238", + "contributions": [ + "code" + ] + }, + { + "login": "maximevtush", + "name": "Maxim Evtush", + "avatar_url": "https://avatars.githubusercontent.com/u/154841002?v=4", + "profile": "https://github.com/maximevtush", + "contributions": [ + "code" + ] + }, + { + "login": "hvgh88", + "name": "Harshita Vidapanakal", + "avatar_url": "https://avatars.githubusercontent.com/u/65297242?v=4", + "profile": "https://github.com/hvgh88", + "contributions": [ + "code" + ] + }, + { + "login": "smile-ab", + "name": "smile-ab", + "avatar_url": "https://avatars.githubusercontent.com/u/202159894?v=4", + "profile": "https://github.com/smile-ab", + "contributions": [ + "translation", + "code" + ] + }, + { + "login": "Francisco-G-P", + "name": "Francisco-G-P", + "avatar_url": "https://avatars.githubusercontent.com/u/186766789?v=4", + "profile": "https://github.com/Francisco-G-P", + "contributions": [ + "translation" + ] + }, + { + "login": "Duartegdm", + "name": "Gabriel Duarte", + "avatar_url": "https://avatars.githubusercontent.com/u/137895372?v=4", + "profile": "https://github.com/Duartegdm", + "contributions": [ + "doc" + ] + }, + { + "login": "DenizAltunkapan", + "name": "Deniz Altunkapan", + "avatar_url": "https://avatars.githubusercontent.com/u/93663085?v=4", + "profile": "https://github.com/DenizAltunkapan", + "contributions": [ + "translation" + ] + }, + { + "login": "johnklint81", + "name": "John Klint", + "avatar_url": "https://avatars.githubusercontent.com/u/70539458?v=4", + "profile": "https://github.com/johnklint81", + "contributions": [ + "code" + ] + }, + { + "login": "sanurah", + "name": "Sanura Hettiarachchi", + "avatar_url": "https://avatars.githubusercontent.com/u/16178588?v=4", + "profile": "https://github.com/sanurah", + "contributions": [ + "code" + ] + }, + { + "login": "2897robo", + "name": "Kim Gi Uk", + "avatar_url": "https://avatars.githubusercontent.com/u/31699375?v=4", + "profile": "https://github.com/2897robo", + "contributions": [ + "code" + ] + }, + { + "login": "Suchismita-Deb", + "name": "Suchismita Deb", + "avatar_url": "https://avatars.githubusercontent.com/u/68535074?v=4", + "profile": "https://github.com/Suchismita-Deb", + "contributions": [ + "code" + ] + }, + { + "login": "ssrijan-007-sys", + "name": "ssrijan-007-sys", + "avatar_url": "https://avatars.githubusercontent.com/u/137605821?v=4", + "profile": "https://github.com/ssrijan-007-sys", + "contributions": [ + "code" + ] + }, + { + "login": "e5LA", + "name": "e5LA", + "avatar_url": "https://avatars.githubusercontent.com/u/208197507?v=4", + "profile": "https://github.com/e5LA", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "maziyar-gerami", + "name": "Maziyar Gerami", + "avatar_url": "https://avatars.githubusercontent.com/u/122622721?v=4", + "profile": "http://maziyar-gerami.github.io/portfolio/", + "contributions": [ + "translation" + ] + }, + { + "login": "yybmion", + "name": "yoobin_mion", + "avatar_url": "https://avatars.githubusercontent.com/u/113106136?v=4", + "profile": "https://github.com/yybmion", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 6, diff --git a/.github/stale.yml b/.github/stale.yml deleted file mode 100644 index 992d8cb25e94..000000000000 --- a/.github/stale.yml +++ /dev/null @@ -1,61 +0,0 @@ -# Configuration for probot-stale - https://github.com/probot/stale - -# Number of days of inactivity before an Issue or Pull Request becomes stale -daysUntilStale: 60 - -# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. -# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: false - -# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) -onlyLabels: [] - -# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable -exemptLabels: - - "info: help wanted" - -# Set to true to ignore issues in a project (defaults to false) -exemptProjects: false - -# Set to true to ignore issues in a milestone (defaults to false) -exemptMilestones: false - -# Set to true to ignore issues with an assignee (defaults to false) -exemptAssignees: false - -# Label to use when marking as stale -staleLabel: "status: stale" - -# Comment to post when marking as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. The issue will be unassigned if no further activity occurs. Thank you - for your contributions. - -# Comment to post when removing the stale label. -# unmarkComment: > -# Your comment here. - -# Comment to post when closing a stale Issue or Pull Request. -# closeComment: > -# Your comment here. - -# Limit the number of actions per hour, from 1-30. Default is 30 -limitPerRun: 30 - -# Limit to only `issues` or `pulls` -# only: issues - -# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': -pulls: - daysUntilStale: 30 - daysUntilClose: 45 - markComment: > - This pull request has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. - closeComment: > - Closed due to inactivity. Thank you for your contributions. - -# issues: -# exemptLabels: -# - confirmed diff --git a/.github/workflows/maven-ci.yml b/.github/workflows/maven-ci.yml index 1b650f213f73..5d2812e72fb7 100644 --- a/.github/workflows/maven-ci.yml +++ b/.github/workflows/maven-ci.yml @@ -1,32 +1,3 @@ -# -# The MIT License -# Copyright © 2014-2021 Ilkka Seppälä -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# - -# This workflow will build a Java project with Maven -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -# We are using two jobs here for testing our code on the latest JDK 11 build as well as a more satble build version of 11.0.3 -# You can see the full discussion here https://github.com/iluwatar/java-design-patterns/pull/1868#issue-1029459688 - name: Java CI on: @@ -37,46 +8,46 @@ jobs: build-and-analyze: - name: Build and Run Sonar analysis on JDK 17 - runs-on: ubuntu-20.04 + name: Build and Run Sonar analysis on JDK 21 + runs-on: ubuntu-22.04 steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - # Disabling shallow clone for improving relevancy of SonarQube reporting - fetch-depth: 0 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - - - name: Cache local Maven repository - uses: actions/cache@v4 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - # Cache Sonar packages which as used to run analysis and collect metrics - - name: Cache SonarCloud packages - uses: actions/cache@v4 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - # Some tests need screen access - - name: Install xvfb - run: sudo apt-get install -y xvfb - - - name: Build with Maven and run SonarQube analysis - run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar - env: - # These two env variables are needed for sonar analysis - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + - name: Checkout Code + uses: actions/checkout@v4 + with: + # Disabling shallow clone for improving relevancy of SonarQube reporting + fetch-depth: 0 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Cache local Maven repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Cache Sonar packages which are used to run analysis and collect metrics + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + # Some tests need screen access + - name: Install xvfb + run: sudo apt-get install -y xvfb + + - name: Build with Maven and run SonarQube analysis + run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + env: + # These two env variables are needed for sonar analysis + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/maven-pr-builder.yml b/.github/workflows/maven-pr-builder.yml index bd9a848410dd..99ee85152962 100644 --- a/.github/workflows/maven-pr-builder.yml +++ b/.github/workflows/maven-pr-builder.yml @@ -1,29 +1,3 @@ -# -# The MIT License -# Copyright © 2014-2021 Ilkka Seppälä -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# - -# This workflow will build a Java project with Maven -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - name: Java PR Builder on: @@ -37,47 +11,47 @@ permissions: jobs: build-and-analyze: - name: Build on JDK 17 - runs-on: ubuntu-20.04 + name: Build on JDK 21 + runs-on: ubuntu-22.04 steps: - - - name: Checkout Code - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: 'maven' - - - name: Cache local Maven repository - uses: actions/cache@v4 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - # Cache Sonar packages which as used to run analysis and collect metrics - - name: Cache SonarCloud packages - uses: actions/cache@v4 - with: - path: ~/.sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - # Some tests need screen access - - name: Install xvfb - run: sudo apt-get install -y xvfb - - name: Build with Maven and run SonarQube analysis - env: - # Intermediate variable - HEAD_REF: ${{ github.head_ref }} - # These two env variables are needed for sonar analysis - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=iluwatar -Dsonar.projectKey=iluwatar_java-design-patterns -Dsonar.pullrequest.branch=$HEAD_REF -Dsonar.pullrequest.base=${{ github.base_ref }} -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} + - name: Checkout Code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Cache local Maven repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + + # Cache Sonar packages which are used to run analysis and collect metrics + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + # Some tests need screen access + - name: Install xvfb + run: sudo apt-get install -y xvfb + + - name: Build with Maven and run SonarQube analysis + env: + # Intermediate variable + HEAD_REF: ${{ github.head_ref }} + # These two env variables are needed for sonar analysis + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: xvfb-run ./mvnw clean verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=iluwatar -Dsonar.projectKey=iluwatar_java-design-patterns -Dsonar.pullrequest.branch=$HEAD_REF -Dsonar.pullrequest.base=${{ github.base_ref }} -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} \ No newline at end of file diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml new file mode 100644 index 000000000000..cac1250b70e8 --- /dev/null +++ b/.github/workflows/presubmit.yml @@ -0,0 +1,35 @@ +name: Presubmit.ai + +permissions: + contents: read + pull-requests: write + issues: write + +on: + pull_request_target: # Handle forked repository PRs in the base repository context + types: [opened, synchronize] + pull_request_review_comment: # Handle review comments + types: [created] + +jobs: + review: + runs-on: ubuntu-latest + steps: + - name: Check required secrets + run: | + if [ -z "${{ secrets.LLM_API_KEY }}" ]; then + echo "Error: LLM_API_KEY secret is not configured" + exit 1 + fi + + - name: Check out PR code + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Run AI Reviewer + uses: presubmit/ai-reviewer@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LLM_API_KEY: ${{ secrets.LLM_API_KEY }} + LLM_MODEL: "gemini-1.5-flash" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..48a4271e470b --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,20 @@ +name: 'Comment on stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: 'This issue is stale because it has been open 60 days with no activity.' + stale-pr-message: 'This PR is stale because it has been open 60 days with no activity.' + close-issue-message: 'This issue was closed because it has been stalled for too long with no activity.' + close-pr-message: 'This PR was closed because it has been stalled for too long with no activity.' + days-before-issue-stale: 60 + days-before-pr-stale: 60 + days-before-issue-close: -1 + days-before-pr-close: -1 + exempt-issue-labels: 'info: help wanted' diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 5ce9356e40e3..bd5b40615e7f 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -1,14 +1,7 @@ - -If you still have questions, please let us know via issues or [gitter](https://matrix.to/#/#iluwatar_java-design-patterns:gitter.im). ---> - -## What problem does this PR solve? - - - + diff --git a/README.md b/README.md index fa2d2ccd2a3c..2c2e375bf4f9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) [![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![All Contributors](https://img.shields.io/badge/all_contributors-357-orange.svg?style=flat-square)](#contributors-) +[![All Contributors](https://img.shields.io/badge/all_contributors-387-orange.svg?style=flat-square)](#contributors-)
@@ -45,7 +45,7 @@ If you are willing to contribute to the project you will find the relevant infor # The Book -The design patterns are now available as an e-book. Find out more about "Open Source Java Design Patterns" here: https://payhip.com/b/kcaF9 +The design patterns are now available as an e-book. Find out more about "Open Source Java Design Patterns" here: https://payhip.com/b/bNQFX The project contributors can get the book for free. Contact the maintainer via [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns) or email (iluwatar (at) gmail (dot) com ). Send a message that contains your email address, Github username, and a link to an accepted pull request. @@ -536,6 +536,46 @@ This project is licensed under the terms of the MIT license. jasonjyu
jasonjyu

💻 jeffmorrison
jeffmorrison

💻 David M.
David M.

💻 + Patrick Kleindienst
Patrick Kleindienst

💻 + Juyeon
Juyeon

🌍 + Mammad Yahyayev
Mammad Yahyayev

📖 + + + Salma
Salma

💻 + Arpit Sarang
Arpit Sarang

💻 + Maya
Maya

🌍 + HabibaMekay
HabibaMekay

💻 + Ahmed-Taha-981
Ahmed-Taha-981

💻 + Malak Elbanna
Malak Elbanna

💻 + + + BiKangNing
BiKangNing

📖 + Tarun Vishwakarma
Tarun Vishwakarma

💻 + Shahd Hossam
Shahd Hossam

💻 + Mehdi Rahimi
Mehdi Rahimi

💻 + Clint Airé
Clint Airé

💻 + darkhyper24
darkhyper24

💻 + + + Mohaned Atef
Mohaned Atef

💻 + Maxim Evtush
Maxim Evtush

💻 + Harshita Vidapanakal
Harshita Vidapanakal

💻 + smile-ab
smile-ab

🌍 💻 + Francisco-G-P
Francisco-G-P

🌍 + Gabriel Duarte
Gabriel Duarte

📖 + + + Deniz Altunkapan
Deniz Altunkapan

🌍 + John Klint
John Klint

💻 + Sanura Hettiarachchi
Sanura Hettiarachchi

💻 + Kim Gi Uk
Kim Gi Uk

💻 + Suchismita Deb
Suchismita Deb

💻 + ssrijan-007-sys
ssrijan-007-sys

💻 + + + e5LA
e5LA

💻 📖 + Maziyar Gerami
Maziyar Gerami

🌍 + yoobin_mion
yoobin_mion

💻 diff --git a/abstract-document/README.md b/abstract-document/README.md index 1dd521c736e8..56f8e775c676 100644 --- a/abstract-document/README.md +++ b/abstract-document/README.md @@ -33,6 +33,11 @@ Wikipedia says > An object-oriented structural design pattern for organizing objects in loosely typed key-value stores and exposing the data using typed views. The purpose of the pattern is to achieve a high degree of flexibility between components in a strongly typed language where new properties can be added to the object-tree on the fly, without losing the support of type-safety. The pattern makes use of traits to separate different properties of a class into different interfaces. +Class diagram + +![Abstract Document class diagram](./etc/abstract-document.png "Abstract Document class diagram") + + ## Programmatic Example of Abstract Document Pattern in Java Consider a car that consists of multiple parts. However, we don't know if the specific car really has all the parts, or just some of them. Our cars are dynamic and extremely flexible. @@ -119,6 +124,13 @@ public interface HasParts extends Document { return children(Property.PARTS.toString(), Part::new); } } + +public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { + + public Part(Map properties) { + super(properties); + } +} ``` Now we are ready to introduce the `Car`. @@ -179,10 +191,6 @@ The program output: 07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- door/Lambo/300 ``` -## Abstract Document Pattern Class Diagram - -![Abstract Document](./etc/abstract-document.png "Abstract Document Traits and Domain") - ## When to Use the Abstract Document Pattern in Java The Abstract Document design pattern is especially beneficial in scenarios requiring management of different document types in Java that share some common attributes or behaviors, but also have unique attributes or behaviors specific to their individual types. Here are some scenarios where the Abstract Document design pattern can be applicable: diff --git a/abstract-document/pom.xml b/abstract-document/pom.xml index 2b7d58519edd..ef190e088d5e 100644 --- a/abstract-document/pom.xml +++ b/abstract-document/pom.xml @@ -34,6 +34,14 @@ abstract-document + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java index 70190604150b..ab5ccada1414 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/AbstractDocument.java @@ -31,9 +31,7 @@ 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 documentProperties; @@ -57,12 +55,12 @@ public Object get(String key) { @Override public Stream children(String key, Function, T> childConstructor) { return Stream.ofNullable(get(key)) - .filter(Objects::nonNull) - .map(el -> (List>) el) - .findAny() - .stream() - .flatMap(Collection::stream) - .map(childConstructor); + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(childConstructor); } @Override @@ -100,5 +98,4 @@ private String buildStringRepresentation() { builder.append("]"); return builder.toString(); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java index 6775523efff7..607b4a7f7913 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/App.java @@ -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 static void main(String[] args) { 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))); } } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java index 198a543b5c04..79a51b610337 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/Document.java @@ -28,15 +28,13 @@ 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 */ diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java index 6b7bbcf5246e..93fbbb9c1eae 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Car.java @@ -27,13 +27,10 @@ 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 properties) { super(properties); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java index 008f5a257bb6..6f517588e5a0 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasModel.java @@ -28,13 +28,10 @@ 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 getModel() { return Optional.ofNullable((String) get(Property.MODEL.toString())); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java index 5dee245d1560..8bffa753e6ef 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasParts.java @@ -28,13 +28,10 @@ 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 getParts() { return children(Property.PARTS.toString(), Part::new); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java index db985dfba333..ce876e5faf54 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasPrice.java @@ -28,13 +28,10 @@ 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 getPrice() { return Optional.ofNullable((Number) get(Property.PRICE.toString())); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java index bd83adecb058..5e0f49df7b7b 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/HasType.java @@ -28,13 +28,10 @@ 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 getType() { return Optional.ofNullable((String) get(Property.TYPE.toString())); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java index 9aa46be15e41..6eec08b0d2a4 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/Part.java @@ -27,13 +27,10 @@ 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 properties) { super(properties); } - } diff --git a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java index 3aa97ba84f65..3e0d6d10ab1f 100644 --- a/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java +++ b/abstract-document/src/main/java/com/iluwatar/abstractdocument/domain/enums/Property.java @@ -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 } diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java index 61d1f1128784..a098517c3a69 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AbstractDocumentTest.java @@ -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 @@ void shouldIncludePropsInToString() { @Test void shouldHandleExceptionDuringConstruction() { - Map invalidProperties = null; // Invalid properties, causing NullPointerException + Map 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 @@ void shouldPutAndGetNestedDocument() { 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")); diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java index 09af6d7b5260..16dcba0db37f 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/AppTest.java @@ -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. + *

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)); } - } diff --git a/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java index 4b52fa7a6ac4..fc29dea45c43 100644 --- a/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java +++ b/abstract-document/src/test/java/com/iluwatar/abstractdocument/DomainTest.java @@ -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 @@ void shouldConstructPart() { @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()); } - } diff --git a/abstract-factory/README.md b/abstract-factory/README.md index 7ec0d60bc00b..ee9823a39a5f 100644 --- a/abstract-factory/README.md +++ b/abstract-factory/README.md @@ -36,6 +36,10 @@ Wikipedia says > The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes +Class diagram + +![Abstract Factory class diagram](./etc/abstract-factory.urm.png "Abstract Factory class diagram") + ## Programmatic Example of Abstract Factory in Java To create a kingdom using the Abstract Factory pattern in Java, we need objects with a common theme. The elven kingdom needs an elven king, elven castle, and elven army whereas the orcish kingdom needs an orcish king, orcish castle, and orcish army. There is a dependency between the objects in the kingdom. @@ -165,10 +169,6 @@ The program output: 07:35:46.343 [main] INFO com.iluwatar.abstractfactory.App -- This is the orc king! ``` -## Abstract Factory Pattern Class Diagram - -![Abstract Factory](./etc/abstract-factory.urm.png "Abstract Factory class diagram") - ## When to Use the Abstract Factory Pattern in Java Use the Abstract Factory pattern in Java when: diff --git a/abstract-factory/pom.xml b/abstract-factory/pom.xml index 99a92423beae..60fbf72eff54 100644 --- a/abstract-factory/pom.xml +++ b/abstract-factory/pom.xml @@ -34,6 +34,14 @@ abstract-factory + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java index e360822ca147..798cbe4fd118 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/App.java @@ -74,6 +74,7 @@ public void run() { /** * Creates kingdom. + * * @param kingdomType type of Kingdom */ public void createKingdom(final Kingdom.FactoryMaker.KingdomType kingdomType) { @@ -82,4 +83,4 @@ public void createKingdom(final Kingdom.FactoryMaker.KingdomType kingdomType) { kingdom.setCastle(kingdomFactory.createCastle()); kingdom.setArmy(kingdomFactory.createArmy()); } -} \ No newline at end of file +} diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java index 3efec4c87eb1..78c75323f1c0 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Army.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * Army interface. - */ +/** Army interface. */ public interface Army { String getDescription(); diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java index 8fca068199cb..ee1e16f3cd3f 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Castle.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * Castle interface. - */ +/** Castle interface. */ public interface Castle { String getDescription(); diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java index 055d2cc75b0c..d7e46c1456f0 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfArmy.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * ElfArmy. - */ +/** ElfArmy. */ public class ElfArmy implements Army { static final String DESCRIPTION = "This is the elven army!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java index 5b0c26c4290d..136afb11fd23 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfCastle.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * ElfCastle. - */ +/** ElfCastle. */ public class ElfCastle implements Castle { static final String DESCRIPTION = "This is the elven castle!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java index 0696e1d096f9..9b0d3a6f1a77 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKing.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * ElfKing. - */ +/** ElfKing. */ public class ElfKing implements King { static final String DESCRIPTION = "This is the elven king!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java index f45d2ee036c1..b09a2f47c252 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/ElfKingdomFactory.java @@ -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 King createKing() { public Army createArmy() { return new ElfArmy(); } - } diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java index 01af71a6a293..9f65ed434577 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/King.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * King interface. - */ +/** King interface. */ public interface King { String getDescription(); diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java index db1c65ca46de..d1f85a6a4812 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/Kingdom.java @@ -27,9 +27,7 @@ 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(); diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java index fdfe19dc08d2..199c6697dcaa 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/KingdomFactory.java @@ -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(); - } diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java index d687c32e8c27..31ed6896d921 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcArmy.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * OrcArmy. - */ +/** OrcArmy. */ public class OrcArmy implements Army { static final String DESCRIPTION = "This is the orc army!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java index f842bb3c62d0..bdae5709a97c 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcCastle.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * OrcCastle. - */ +/** OrcCastle. */ public class OrcCastle implements Castle { static final String DESCRIPTION = "This is the orc castle!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java index e25d93bfba40..7f106d45a01d 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKing.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * OrcKing. - */ +/** OrcKing. */ public class OrcKing implements King { static final String DESCRIPTION = "This is the orc king!"; diff --git a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java index c80728a87fdc..82d258570460 100644 --- a/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java +++ b/abstract-factory/src/main/java/com/iluwatar/abstractfactory/OrcKingdomFactory.java @@ -24,9 +24,7 @@ */ package com.iluwatar.abstractfactory; -/** - * OrcKingdomFactory concrete factory. - */ +/** OrcKingdomFactory concrete factory. */ public class OrcKingdomFactory implements KingdomFactory { @Override diff --git a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java index 0f7708e07a12..b5dde940c464 100644 --- a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java +++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AbstractFactoryTest.java @@ -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(); diff --git a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java index 736a7f8b740f..9f53691a594d 100644 --- a/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java +++ b/abstract-factory/src/test/java/com/iluwatar/abstractfactory/AppTest.java @@ -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[] {})); } } diff --git a/active-object/README.md b/active-object/README.md index c17cbebd79ae..a804ce9e632b 100644 --- a/active-object/README.md +++ b/active-object/README.md @@ -41,6 +41,11 @@ Wikipedia says > * The implementation of the active object method. > * A callback or variable for the client to receive the result. +Sequence diagram + +![Active Object sequence diagram](./etc/active-object-sequence-diagram.png) + + ## Programmatic Example of Active Object in Java This section explains how the Active Object design pattern works in Java, highlighting its use in asynchronous task management and concurrency control. diff --git a/active-object/etc/active-object-sequence-diagram.png b/active-object/etc/active-object-sequence-diagram.png new file mode 100644 index 000000000000..b725d9b07b6d Binary files /dev/null and b/active-object/etc/active-object-sequence-diagram.png differ diff --git a/active-object/pom.xml b/active-object/pom.xml index 08a09e6642e8..aa26dbbe2e48 100644 --- a/active-object/pom.xml +++ b/active-object/pom.xml @@ -34,6 +34,14 @@ active-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java b/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java index c7b661845a00..5a440020c0ac 100644 --- a/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java +++ b/active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java @@ -29,86 +29,87 @@ 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 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() { diff --git a/active-object/src/main/java/com/iluwatar/activeobject/App.java b/active-object/src/main/java/com/iluwatar/activeobject/App.java index b88b4c5590a0..ca3a5526ebb8 100644 --- a/active-object/src/main/java/com/iluwatar/activeobject/App.java +++ b/active-object/src/main/java/com/iluwatar/activeobject/App.java @@ -30,17 +30,17 @@ 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. + * *

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 creatures = new ArrayList<>(); diff --git a/active-object/src/main/java/com/iluwatar/activeobject/Orc.java b/active-object/src/main/java/com/iluwatar/activeobject/Orc.java index 8f5570a8663b..30adde034de5 100644 --- a/active-object/src/main/java/com/iluwatar/activeobject/Orc.java +++ b/active-object/src/main/java/com/iluwatar/activeobject/Orc.java @@ -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); } - } diff --git a/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java b/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java index 5441ed6b0d9b..be79e2fb5527 100644 --- a/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java +++ b/active-object/src/test/java/com/iluwatar/activeobject/ActiveCreatureTest.java @@ -27,17 +27,16 @@ 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); - } - + @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); + } } diff --git a/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java b/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java index 8ef2f142cf46..559e2a1f58f1 100644 --- a/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java +++ b/active-object/src/test/java/com/iluwatar/activeobject/AppTest.java @@ -28,11 +28,10 @@ import org.junit.jupiter.api.Test; - class AppTest { - @Test - void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/actor-model/README.md b/actor-model/README.md new file mode 100644 index 000000000000..be8065ffefef --- /dev/null +++ b/actor-model/README.md @@ -0,0 +1,201 @@ +--- +title: "Actor Model Pattern in Java: Building Concurrent Systems with Elegance" +shortTitle: Actor Model +description: "Explore the Actor Model pattern in Java with real-world examples and practical implementation. Learn how to build scalable, message-driven systems using actors, messages, and asynchronous communication." +category: Concurrency +language: en +tag: + - Concurrency + - Messaging + - Isolation + - Asynchronous + - Distributed Systems + - Actor Model +--- + +## Also Known As + +- Message-passing concurrency +- Actor-based concurrency + +--- + +## Intent of Actor Model Pattern + +The Actor Model pattern enables the construction of highly concurrent, distributed, and fault-tolerant systems by using isolated components (actors) that interact exclusively through asynchronous message passing. + +--- + +## Detailed Explanation of Actor Model Pattern with Real-World Examples + +### 📦 Real-world Example + +Imagine a customer service system: +- Each **customer support agent** is an **actor**. +- Customers **send questions (messages)** to agents. +- Each agent handles one request at a time and can **respond asynchronously** without interfering with other agents. + +--- + +### 🧠 In Plain Words + +> "Actors are like independent workers that never share memory and only communicate through messages." + +--- + +### 📖 Wikipedia Says + +> [Actor model](https://en.wikipedia.org/wiki/Actor_model) is a mathematical model of concurrent computation that treats "actors" as the universal primitives of concurrent computation. + +--- + +### 🧹 Architecture Diagram + +![UML Class Diagram](./etc/Actor_Model_UML_Class_Diagram.png) + +--- + +## Programmatic Example of Actor Model Pattern in Java + +### Actor.java + +```java +public abstract class Actor implements Runnable { + + @Setter @Getter private String actorId; + private final BlockingQueue mailbox = new LinkedBlockingQueue<>(); + private volatile boolean active = true; + + + public void send(Message message) { + mailbox.add(message); + } + + public void stop() { + active = false; + } + + @Override + public void run() { + + } + + protected abstract void onReceive(Message message); +} + +``` + +### Message.java + +```java + +@AllArgsConstructor +@Getter +@Setter +public class Message { + private final String content; + private final String senderId; +} +``` + +### ActorSystem.java + +```java +public class ActorSystem { + public void startActor(Actor actor) { + String actorId = "actor-" + idCounter.incrementAndGet(); // Generate a new and unique ID + actor.setActorId(actorId); // assign the actor it's ID + actorRegister.put(actorId, actor); // Register and save the actor with it's ID + executor.submit(actor); // Run the actor in a thread + } + public Actor getActorById(String actorId) { + return actorRegister.get(actorId); // Find by Id + } + + public void shutdown() { + executor.shutdownNow(); // Stop all threads + } +} +``` + +### App.java + +```java +public class App { + public static void main(String[] args) { + ActorSystem system = new ActorSystem(); + Actor srijan = new ExampleActor(system); + Actor ansh = new ExampleActor2(system); + + system.startActor(srijan); + system.startActor(ansh); + ansh.send(new Message("Hello ansh", srijan.getActorId())); + srijan.send(new Message("Hello srijan!", ansh.getActorId())); + + Thread.sleep(1000); // Give time for messages to process + + srijan.stop(); // Stop the actor gracefully + ansh.stop(); + system.shutdown(); // Stop the actor system + } +} +``` + +--- + +## When to Use the Actor Model Pattern in Java + +- When building **concurrent or distributed systems** +- When you want **no shared mutable state** +- When you need **asynchronous, message-driven communication** +- When components should be **isolated and loosely coupled** + +--- + +## Actor Model Pattern Java Tutorials + +- [Baeldung – Akka with Java](https://www.baeldung.com/java-akka) +- [Vaughn Vernon – Reactive Messaging Patterns](https://vaughnvernon.co/?p=1143) + +--- + +## Real-World Applications of Actor Model Pattern in Java + +- [Akka Framework](https://akka.io/) +- [Erlang and Elixir concurrency](https://www.erlang.org/) +- [Microsoft Orleans](https://learn.microsoft.com/en-us/dotnet/orleans/) +- JVM-based game engines and simulators + +--- + +## Benefits and Trade-offs of Actor Model Pattern + +### ✅ Benefits +- High concurrency support +- Easy scaling across threads or machines +- Fault isolation and recovery +- Message ordering within actors + +### ⚠️ Trade-offs +- Harder to debug due to asynchronous behavior +- Slight performance overhead due to message queues +- More complex to design than simple method calls + +--- + +## Related Java Design Patterns + +- [Command Pattern](../command) +- [Mediator Pattern](../mediator) +- [Event-Driven Architecture](../event-driven-architecture) +- [Observer Pattern](../observer) + +--- + +## References and Credits + +- *Programming Erlang*, Joe Armstrong +- *Reactive Design Patterns*, Roland Kuhn +- *The Actor Model in 10 Minutes*, [InfoQ Article](https://www.infoq.com/articles/actor-model/) +- [Akka Documentation](https://doc.akka.io/docs/akka/current/index.html) + diff --git a/actor-model/etc/Actor_Model_UML_Class_Diagram.png b/actor-model/etc/Actor_Model_UML_Class_Diagram.png new file mode 100644 index 000000000000..a4c34d7a75fd Binary files /dev/null and b/actor-model/etc/Actor_Model_UML_Class_Diagram.png differ diff --git a/actor-model/etc/actor-model.urm.puml b/actor-model/etc/actor-model.urm.puml new file mode 100644 index 000000000000..020c1fc735a4 --- /dev/null +++ b/actor-model/etc/actor-model.urm.puml @@ -0,0 +1,35 @@ +@startuml actor-model + +title Actor Model - UML Class Diagram + +class ActorSystem { + +actorOf(actor: Actor): Actor + +shutdown(): void +} + +class Actor { + -mailbox: BlockingQueue + -active: boolean + +send(message: Message): void + +stop(): void + +run(): void + #onReceive(message: Message): void +} + +class ExampleActor { + +onReceive(message: Message): void +} + +class Message { + -content: String + -sender: Actor + +getContent(): String + +getSender(): Actor +} + +ActorSystem --> Actor : creates +Actor <|-- ExampleActor : extends +Actor --> Message : processes +ExampleActor --> Message : uses + +@enduml diff --git a/actor-model/pom.xml b/actor-model/pom.xml new file mode 100644 index 000000000000..76c288829b8d --- /dev/null +++ b/actor-model/pom.xml @@ -0,0 +1,114 @@ + + + + + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + actor-model + Actor Model + + + + + + org.junit + junit-bom + 5.11.0 + pom + import + + + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.platform + junit-platform-launcher + test + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.3.0 + + + jar-with-dependencies + + + + com.iluwatar.actormodel.App + + + + + + make-assembly + package + + single + + + + + + + + + diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java new file mode 100644 index 000000000000..6e2aaccd1937 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/Actor.java @@ -0,0 +1,63 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import lombok.Getter; +import lombok.Setter; + +public abstract class Actor implements Runnable { + + @Setter @Getter private String actorId; + private final BlockingQueue mailbox = new LinkedBlockingQueue<>(); + private volatile boolean active = + true; // always read from main memory and written back to main memory, + + // rather than being cached in a thread's local memory. To make it consistent to all Actors + + public void send(Message message) { + mailbox.add(message); // Add message to queue + } + + public void stop() { + active = false; // Stop the actor loop + } + + @Override + public void run() { + while (active) { + try { + Message message = mailbox.take(); // Wait for a message + onReceive(message); // Process it + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + // Child classes must define what to do with a message + protected abstract void onReceive(Message message); +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java new file mode 100644 index 000000000000..db7c21cb6088 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ActorSystem.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +public class ActorSystem { + private final ExecutorService executor = Executors.newCachedThreadPool(); + private final ConcurrentHashMap actorRegister = new ConcurrentHashMap<>(); + private final AtomicInteger idCounter = new AtomicInteger(0); + + public void startActor(Actor actor) { + String actorId = "actor-" + idCounter.incrementAndGet(); // Generate a new and unique ID + actor.setActorId(actorId); // assign the actor it's ID + actorRegister.put(actorId, actor); // Register and save the actor with it's ID + executor.submit(actor); // Run the actor in a thread + } + + public Actor getActorById(String actorId) { + return actorRegister.get(actorId); // Find by Id + } + + public void shutdown() { + executor.shutdownNow(); // Stop all threads + } +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/App.java b/actor-model/src/main/java/com/iluwatar/actormodel/App.java new file mode 100644 index 000000000000..79fe79e48a6f --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/App.java @@ -0,0 +1,64 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/** + * The Actor Model is a design pattern used to handle concurrency in a safe, scalable, and + * message-driven way. + * + *

In the Actor Model: - An **Actor** is an independent unit that has its own state and behavior. + * - Actors **communicate only through messages** — they do not share memory. - An **ActorSystem** + * is responsible for creating, starting, and managing the lifecycle of actors. - Messages are + * delivered asynchronously, and each actor processes them one at a time. + * + *

💡 Key benefits: - No shared memory = no need for complex thread-safety - Easy to scale with + * many actors - Suitable for highly concurrent or distributed systems + * + *

🔍 This example demonstrates the Actor Model: - `ActorSystem` starts two actors: `srijan` and + * `ansh`. - `ExampleActor` and `ExampleActor2` extend the `Actor` class and override the + * `onReceive()` method to handle messages. - Actors communicate using `send()` to pass `Message` + * objects that include the message content and sender's ID. - The actors process messages + * **asynchronously in separate threads**, and we allow a short delay (`Thread.sleep`) to let them + * run. - The system is shut down gracefully at the end. + */ +package com.iluwatar.actormodel; + +public class App { + public static void main(String[] args) throws InterruptedException { + ActorSystem system = new ActorSystem(); + Actor srijan = new ExampleActor(system); + Actor ansh = new ExampleActor2(system); + + system.startActor(srijan); + system.startActor(ansh); + ansh.send(new Message("Hello ansh", srijan.getActorId())); + srijan.send(new Message("Hello srijan!", ansh.getActorId())); + + Thread.sleep(1000); // Give time for messages to process + + srijan.stop(); // Stop the actor gracefully + ansh.stop(); + system.shutdown(); // Stop the actor system + } +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java new file mode 100644 index 000000000000..fd49325f44bd --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor.java @@ -0,0 +1,53 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ExampleActor extends Actor { + private final ActorSystem actorSystem; + @Getter private final List receivedMessages = new ArrayList<>(); + + public ExampleActor(ActorSystem actorSystem) { + this.actorSystem = actorSystem; + } + + // Logger log = Logger.getLogger(getClass().getName()); + + @Override + protected void onReceive(Message message) { + LOGGER.info( + "[{}]Received : {} from : [{}]", getActorId(), message.getContent(), message.getSenderId()); + Actor sender = actorSystem.getActorById(message.getSenderId()); // sender actor id + // Reply of the message + if (sender != null && !message.getSenderId().equals(getActorId())) { + sender.send(new Message("I got your message ", getActorId())); + } + } +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java new file mode 100644 index 000000000000..037f96716558 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/ExampleActor2.java @@ -0,0 +1,46 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +import java.util.ArrayList; +import java.util.List; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ExampleActor2 extends Actor { + private final ActorSystem actorSystem; + @Getter private final List receivedMessages = new ArrayList<>(); + + public ExampleActor2(ActorSystem actorSystem) { + this.actorSystem = actorSystem; + } + + @Override + protected void onReceive(Message message) { + receivedMessages.add(message.getContent()); + LOGGER.info("[{}]Received : {}", getActorId(), message.getContent()); + } +} diff --git a/actor-model/src/main/java/com/iluwatar/actormodel/Message.java b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java new file mode 100644 index 000000000000..03ca6e02cac0 --- /dev/null +++ b/actor-model/src/main/java/com/iluwatar/actormodel/Message.java @@ -0,0 +1,35 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actormodel; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class Message { + private final String content; + private final String senderId; +} diff --git a/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java b/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java new file mode 100644 index 000000000000..a4a0dee569ab --- /dev/null +++ b/actor-model/src/test/java/com/iluwatar/actor/ActorModelTest.java @@ -0,0 +1,63 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.actor; + +import static org.junit.jupiter.api.Assertions.*; + +import com.iluwatar.actormodel.ActorSystem; +import com.iluwatar.actormodel.App; +import com.iluwatar.actormodel.ExampleActor; +import com.iluwatar.actormodel.ExampleActor2; +import com.iluwatar.actormodel.Message; +import org.junit.jupiter.api.Test; + +public class ActorModelTest { + @Test + void testMainMethod() throws InterruptedException { + App.main(new String[] {}); + } + + @Test + public void testMessagePassing() throws InterruptedException { + ActorSystem system = new ActorSystem(); + + ExampleActor srijan = new ExampleActor(system); + ExampleActor2 ansh = new ExampleActor2(system); + + system.startActor(srijan); + system.startActor(ansh); + + // Ansh recieves a message from Srijan + ansh.send(new Message("Hello ansh", srijan.getActorId())); + + // Wait briefly to allow async processing + Thread.sleep(200); + + // Check that Srijan received the message + assertTrue( + ansh.getReceivedMessages().contains("Hello ansh"), + "ansh should receive the message from Srijan"); + } +} diff --git a/acyclic-visitor/README.md b/acyclic-visitor/README.md index eef18bbed344..fb57d5681fd4 100644 --- a/acyclic-visitor/README.md +++ b/acyclic-visitor/README.md @@ -29,6 +29,11 @@ In plain words > The Acyclic Visitor pattern allows new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating the dependency cycles that are inherent to the GangOfFour VisitorPattern. +Sequence diagram + +![Acyclic Visitor sequence diagram](./etc/acyclic-visitor-sequence-diagram.png "Acyclic Visitor sequence diagram") + + ## Programmatic Example of Acyclic Visitor in Java In this Java example, we have a hierarchy of modem classes illustrating the Acyclic Visitor pattern. The modems in this hierarchy need to be visited by an external algorithm based on filtering criteria (is it Unix or DOS compatible modem). @@ -138,10 +143,6 @@ Program output: 09:15:11.127 [main] INFO com.iluwatar.acyclicvisitor.ConfigureForUnixVisitor -- Zoom modem used with Unix configurator. ``` -## Acyclic Visitor Pattern Class Diagram - -![Acyclic Visitor](./etc/acyclic-visitor.png "Acyclic Visitor") - ## When to Use the Acyclic Visitor Pattern in Java This pattern can be used: diff --git a/acyclic-visitor/etc/acyclic-visitor-sequence-diagram.png b/acyclic-visitor/etc/acyclic-visitor-sequence-diagram.png new file mode 100644 index 000000000000..a3c2ba56b89f Binary files /dev/null and b/acyclic-visitor/etc/acyclic-visitor-sequence-diagram.png differ diff --git a/acyclic-visitor/pom.xml b/acyclic-visitor/pom.xml index 52604048e127..b4f5646b71c0 100644 --- a/acyclic-visitor/pom.xml +++ b/acyclic-visitor/pom.xml @@ -34,6 +34,14 @@ acyclic-visitor + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java index 38da4923a467..a3b1679a2d9f 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/AllModemVisitor.java @@ -28,6 +28,4 @@ * 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 {} diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java index 64d4d039aba3..3b7c6cd61e4b 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/App.java @@ -37,9 +37,7 @@ */ 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 static void main(String[] args) { 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 } } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java index 9f9f29187839..267a8d66ac45 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForDosVisitor.java @@ -27,8 +27,7 @@ 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 { diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java index 097f19c0dbbd..d9fd14f69435 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ConfigureForUnixVisitor.java @@ -37,4 +37,4 @@ public class ConfigureForUnixVisitor implements ZoomVisitor { public void visit(Zoom zoom) { LOGGER.info(zoom + " used with Unix configurator."); } -} \ No newline at end of file +} diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java index 384df8a4d9c9..e0b2fcc2b530 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Hayes.java @@ -26,15 +26,11 @@ 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 void accept(ModemVisitor modemVisitor) { } 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"; diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java index a33c87cfa645..aad9b970994f 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/HayesVisitor.java @@ -24,9 +24,7 @@ */ package com.iluwatar.acyclicvisitor; -/** - * HayesVisitor interface. - */ +/** HayesVisitor interface. */ public interface HayesVisitor extends ModemVisitor { void visit(Hayes hayes); } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java index fd15ee422468..8552574453e5 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Modem.java @@ -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); } diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java index e9f02e1ad6fd..59b50a54a12f 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/Zoom.java @@ -26,15 +26,11 @@ 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 void accept(ModemVisitor modemVisitor) { } } - /** - * Zoom modem's toString method. - */ + /** Zoom modem's toString method. */ @Override public String toString() { return "Zoom modem"; diff --git a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java index 639af1c65777..5388ded6f735 100644 --- a/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java +++ b/acyclic-visitor/src/main/java/com/iluwatar/acyclicvisitor/ZoomVisitor.java @@ -24,9 +24,7 @@ */ package com.iluwatar.acyclicvisitor; -/** - * ZoomVisitor interface. - */ +/** ZoomVisitor interface. */ public interface ZoomVisitor extends ModemVisitor { void visit(Zoom zoom); } diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java index 9cc242d8f7fc..7a21498a63ea 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/AppTest.java @@ -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. + *

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[] {})); } -} \ No newline at end of file +} diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java index 66640e3ca1ac..a989d9287921 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/HayesTest.java @@ -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 diff --git a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java index df7b7e8408a5..d5fe79965d47 100644 --- a/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java +++ b/acyclic-visitor/src/test/java/com/iluwatar/acyclicvisitor/ZoomTest.java @@ -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 diff --git a/adapter/README.md b/adapter/README.md index 945de2db00e8..489742494709 100644 --- a/adapter/README.md +++ b/adapter/README.md @@ -35,6 +35,10 @@ Wikipedia says > In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code. +Sequence diagram + +![Adapter sequence diagram](./etc/adapter-sequence-diagram.png "Adapter sequence diagram") + ## Programmatic Example of Adapter Pattern in Java The Adapter Pattern example in Java shows how a class with an incompatible interface can be adapted to work with another class. diff --git a/adapter/etc/adapter-sequence-diagram.png b/adapter/etc/adapter-sequence-diagram.png new file mode 100644 index 000000000000..a9bb557ea61e Binary files /dev/null and b/adapter/etc/adapter-sequence-diagram.png differ diff --git a/adapter/pom.xml b/adapter/pom.xml index d54cbd048708..6e7f45a515b5 100644 --- a/adapter/pom.xml +++ b/adapter/pom.xml @@ -34,6 +34,14 @@ adapter + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/adapter/src/main/java/com/iluwatar/adapter/App.java b/adapter/src/main/java/com/iluwatar/adapter/App.java index 1f572813cef1..a4fa74274f74 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/App.java +++ b/adapter/src/main/java/com/iluwatar/adapter/App.java @@ -37,16 +37,15 @@ *

The Adapter ({@link FishingBoatAdapter}) converts the interface of the adaptee class ({@link * FishingBoat}) into a suitable one expected by the client ({@link RowingBoat}). * - *

The story of this implementation is this.
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}. + *

The story of this implementation is this.
+ * 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. diff --git a/adapter/src/main/java/com/iluwatar/adapter/Captain.java b/adapter/src/main/java/com/iluwatar/adapter/Captain.java index 3d6d7746d00a..3b771e9d833e 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/Captain.java +++ b/adapter/src/main/java/com/iluwatar/adapter/Captain.java @@ -29,7 +29,8 @@ import lombok.Setter; /** - * The Captain uses {@link RowingBoat} to sail.
This is the client in the pattern. + * The Captain uses {@link RowingBoat} to sail.
+ * This is the client in the pattern. */ @Setter @NoArgsConstructor @@ -41,5 +42,4 @@ public final class Captain { void row() { rowingBoat.row(); } - } diff --git a/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java index e692d859873c..dd39f88f1ce0 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java +++ b/adapter/src/main/java/com/iluwatar/adapter/FishingBoat.java @@ -36,5 +36,4 @@ final class FishingBoat { void sail() { LOGGER.info("The fishing boat is sailing"); } - } diff --git a/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java b/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java index c8714ef91040..55eeeaf4bd42 100644 --- a/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java +++ b/adapter/src/main/java/com/iluwatar/adapter/RowingBoat.java @@ -25,10 +25,10 @@ package com.iluwatar.adapter; /** - * The interface expected by the client.
A rowing boat is rowed to move. + * The interface expected by the client.
+ * A rowing boat is rowed to move. */ public interface RowingBoat { void row(); - } diff --git a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java index 10024ff0dc46..bc4984da3d6f 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AdapterPatternTest.java @@ -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 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<>(); diff --git a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java index be51d2687548..a2cc4c22bcaa 100644 --- a/adapter/src/test/java/com/iluwatar/adapter/AppTest.java +++ b/adapter/src/test/java/com/iluwatar/adapter/AppTest.java @@ -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. - */ -class AppTest { +import org.junit.jupiter.api.Test; - /** - * Check whether the execution of the main method in {@link App} - * throws an exception. - */ +/** Tests that Adapter example runs without errors. */ +class AppTest { + /** 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[] {})); } } diff --git a/ambassador/README.md b/ambassador/README.md index 6f87afae1bf1..fa08223feb75 100644 --- a/ambassador/README.md +++ b/ambassador/README.md @@ -33,6 +33,10 @@ Microsoft documentation states > An ambassador service can be thought of as an out-of-process proxy which is co-located with the client. This pattern can be useful for offloading common client connectivity tasks such as monitoring, logging, routing, security (such as TLS), and resiliency patterns in a language agnostic way. It is often used with legacy applications, or other applications that are difficult to modify, in order to extend their networking capabilities. It can also enable a specialized team to implement those features. +Sequence diagram + +![Ambassador sequence diagram](./etc/ambassador-sequence-diagram.png "Ambassador sequence diagram") + ## Programmatic Example of Ambassador Pattern in Java In this example of the Ambassador Pattern in Java, we demonstrate how to implement latency checks, logging, and retry mechanisms to improve system reliability. diff --git a/ambassador/etc/ambassador-sequence-diagram.png b/ambassador/etc/ambassador-sequence-diagram.png new file mode 100644 index 000000000000..71e6a947c5dd Binary files /dev/null and b/ambassador/etc/ambassador-sequence-diagram.png differ diff --git a/ambassador/pom.xml b/ambassador/pom.xml index a6d702426080..15e4a07f0dc3 100644 --- a/ambassador/pom.xml +++ b/ambassador/pom.xml @@ -34,6 +34,14 @@ 4.0.0 ambassador + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/App.java b/ambassador/src/main/java/com/iluwatar/ambassador/App.java index ff025d9b2bf6..8de149fe0813 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/App.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/App.java @@ -28,8 +28,8 @@ * 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. * - *

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 + *

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 @@ * capabilities. * *

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(); diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/Client.java b/ambassador/src/main/java/com/iluwatar/ambassador/Client.java index d0f81c1dd121..0baabf4ffc06 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/Client.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/Client.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * A simple Client. - */ +/** A simple Client. */ @Slf4j public class Client { diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java index eba634494fc7..d99348040cfe 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteService.java @@ -29,9 +29,7 @@ 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 @@ private RemoteService() { 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 long doRemoteFunction(int value) { LOGGER.error("Thread sleep state interrupted", e); Thread.currentThread().interrupt(); } - return waitTime <= THRESHOLD ? value * 10 + return waitTime <= THRESHOLD + ? value * 10 : RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue(); } } diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java index 104d81ec2eff..aa6012bae33f 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceInterface.java @@ -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); diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java index 8f1a0a1a4907..8549ed7247f3 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/RemoteServiceStatus.java @@ -29,17 +29,14 @@ /** * Holds information regarding the status of the Remote Service. * - *

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.

+ *

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; diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java index f3f30a09dc9b..4d310169770b 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/ServiceAmbassador.java @@ -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) { diff --git a/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java b/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java index e8243cdcc7cf..4eba2fada7fa 100644 --- a/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java +++ b/ambassador/src/main/java/com/iluwatar/ambassador/util/RandomProvider.java @@ -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(); } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java index cea0eeac7bb9..ddb2d6eff411 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/AppTest.java @@ -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. + *

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[] {})); } } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java index ff7f027f64f1..24603efff9d4 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/ClientTest.java @@ -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 @@ void test() { Client client = new Client(); var result = client.useService(10); - assertTrue(result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); + assertTrue( + result == 100 || result == RemoteServiceStatus.FAILURE.getRemoteServiceStatusValue()); } } diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java index 5fed19a169b1..81e4f744128f 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/RemoteServiceTest.java @@ -29,9 +29,7 @@ import com.iluwatar.ambassador.util.RandomProvider; import org.junit.jupiter.api.Test; -/** - * Test for {@link RemoteService} - */ +/** Test for {@link RemoteService} */ class RemoteServiceTest { @Test diff --git a/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java index 50c354c1485a..0543b2e7e370 100644 --- a/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java +++ b/ambassador/src/test/java/com/iluwatar/ambassador/ServiceAmbassadorTest.java @@ -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()); } } diff --git a/anti-corruption-layer/README.md b/anti-corruption-layer/README.md index fdc3edd7a337..9b6042850eba 100644 --- a/anti-corruption-layer/README.md +++ b/anti-corruption-layer/README.md @@ -44,6 +44,10 @@ In plain words > Implement a façade or adapter layer between different subsystems that don't share the same semantics. This layer translates requests that one subsystem makes to the other subsystem. Use this pattern to ensure that an application's design is not limited by dependencies on outside subsystems. This pattern was first described by Eric Evans in Domain-Driven Design. +Sequence diagram + +![Anti-Corruption Layer sequence diagram](./etc/anti-corruption-layer-sequence-diagram.png "Anti-Corruption Layer sequence diagram") + ## Programmatic Example of Anti-Corruption Layer Pattern in Java The ACL design pattern in Java provides an intermediary layer that translates data formats, ensuring that integration between different systems does not lead to data corruption. @@ -122,7 +126,7 @@ public class LegacyShop { String id = legacyOrder.getId(); - Optional orderInModernSystem = acl.findOrderInModernSystem(id); + Optional orderInModernSystem = acl.findOrderInModernSystem(id); if (orderInModernSystem.isPresent()) { // order is already in the modern system diff --git a/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png b/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png new file mode 100644 index 000000000000..835ff4e84a0f Binary files /dev/null and b/anti-corruption-layer/etc/anti-corruption-layer-sequence-diagram.png differ diff --git a/anti-corruption-layer/pom.xml b/anti-corruption-layer/pom.xml index 711e006b7076..2fddfd3ecc46 100644 --- a/anti-corruption-layer/pom.xml +++ b/anti-corruption-layer/pom.xml @@ -45,8 +45,8 @@ test - junit - junit + org.junit.jupiter + junit-jupiter-engine test diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java index ce2dbffd6f9a..f7cf8f075f83 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/App.java @@ -28,9 +28,8 @@ 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 { diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java index c8f72fca49fd..880d98c7d5b0 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/package-info.java @@ -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. * - *

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. + *

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. * - *

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. + *

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. * *

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; diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java index fae658ee5bac..4e8a17fa5d2a 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/AntiCorruptionLayer.java @@ -33,36 +33,34 @@ 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 * - *

It allows using one system a domain model of the other system - * without changing the domain model of the system. + *

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 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()), + "")); } - } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java index e9fdaa14ec1f..e84578528be7 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/DataStore.java @@ -29,6 +29,7 @@ /** * The class represents a data store for the modern system. + * * @param the type of the value stored in the data store */ public abstract class DataStore { @@ -44,6 +45,5 @@ public Optional get(String key) { public Optional put(String key, V value) { return Optional.ofNullable(inner.put(key, value)); - } } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java index 103fcfccd9e7..c0acd288ed0c 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/ShopException.java @@ -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 ShopException(String message) { * @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); } - } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java index 2481d19c8212..45faa06cb26c 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyOrder.java @@ -28,8 +28,8 @@ 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 diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java index b29b71d87e5c..ec1d613a7235 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/legacy/LegacyStore.java @@ -28,10 +28,8 @@ 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 { -} - +public class LegacyStore extends DataStore {} diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java index dd335141579d..130f36d39674 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Customer.java @@ -27,9 +27,7 @@ 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 { diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java index 94c4f57b9a9c..7b62985015d6 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernOrder.java @@ -27,9 +27,7 @@ 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; - - } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java index 45cd355bea87..24080abe1533 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernShop.java @@ -31,20 +31,18 @@ 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 void placeOrder(ModernOrder order) throws ShopException { } } - /** - * Finds the order in the modern system. - */ + /** Finds the order in the modern system. */ public Optional findOrder(String orderId) { return store.get(orderId); } diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java index 4ec4d7dbcfc0..4fb3952fae5e 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/ModernStore.java @@ -27,10 +27,6 @@ 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 { -} - +public class ModernStore extends DataStore {} diff --git a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java index 292c0d8e3771..085a3921ceeb 100644 --- a/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java +++ b/anti-corruption-layer/src/main/java/com/iluwatar/corruption/system/modern/Shipment.java @@ -28,8 +28,8 @@ 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 diff --git a/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java b/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java index ba24c89811db..ee46d124eee6 100644 --- a/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java +++ b/anti-corruption-layer/src/test/java/com/iluwatar/corruption/system/AntiCorruptionLayerTest.java @@ -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 java.util.Optional; +import org.springframework.test.context.junit.jupiter.SpringExtension; -import static org.junit.jupiter.api.Assertions.*; - -@RunWith(SpringRunner.class) +@ExtendWith(SpringExtension.class) @SpringBootTest public class AntiCorruptionLayerTest { - @Autowired - private LegacyShop legacyShop; - - @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. - * To avoid corrupting the domain models of the 2 shops, we use an anti-corruption layer - * that transforms one model to another under the hood. - * - */ - @Test - public void antiCorruptionLayerTest() throws ShopException { - - // a new order comes to the legacy shop. - 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 legacyOrderWithIdOne = legacyShop.findOrder("1"); - assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne); - - // a new order (or maybe just the same order) appears in the modern shop. - ModernOrder modernOrder = new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 1, 1), ""); - - // the system places it, but it checks if there is an order with the same id in the legacy shop. - modernShop.placeOrder(modernOrder); - - Optional 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. - *

- * 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 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); - - - } -} \ No newline at end of file + @Autowired private LegacyShop legacyShop; + + @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 legacyOrderWithIdOne = legacyShop.findOrder("1"); + assertEquals(Optional.of(legacyOrder), legacyOrderWithIdOne); + + // a new order (or maybe just the same order) appears in the modern shop + ModernOrder modernOrder = + new ModernOrder("1", new Customer("addr1"), new Shipment("item1", 1, 1), ""); + // the system places it, but it checks if there is an order with the same id in the legacy shop. + modernShop.placeOrder(modernOrder); + + Optional 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 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 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)); + } +} diff --git a/arrange-act-assert/README.md b/arrange-act-assert/README.md index a4b7ec8deb92..584d8601a23e 100644 --- a/arrange-act-assert/README.md +++ b/arrange-act-assert/README.md @@ -38,6 +38,10 @@ WikiWikiWeb says > Arrange/Act/Assert is a pattern for arranging and formatting code in UnitTest methods. +Flowchart + +![Arrange/Act/Assert flowchart](./etc/arrange-act-assert-flowchart.png "Arrange/Act/Assert flowchart") + ## Programmatic Example of Arrange/Act/Assert Pattern in Java We need to write comprehensive and clear unit test suite for a class. Using the Arrange/Act/Assert pattern in Java testing ensures clarity. diff --git a/arrange-act-assert/etc/arrange-act-assert-flowchart.png b/arrange-act-assert/etc/arrange-act-assert-flowchart.png new file mode 100644 index 000000000000..8b7615352523 Binary files /dev/null and b/arrange-act-assert/etc/arrange-act-assert-flowchart.png differ diff --git a/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java b/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java index c3f5e6fe43e1..0c31b1f89f44 100644 --- a/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java +++ b/arrange-act-assert/src/main/java/com/iluwatar/arrangeactassert/Cash.java @@ -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 @@ boolean minus(int subtrahend) { } } - //count + // count int count() { return amount; } diff --git a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java index b771cb6e7dee..ebb261277080 100644 --- a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java +++ b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAAATest.java @@ -35,8 +35,11 @@ * tests, so they're easier to read, maintain and enhance. * *

It breaks tests down into three clear and distinct steps: + * *

1. Arrange: Perform the setup and initialization required for the test. + * *

2. Act: Take action(s) required for the test. + * *

3. Assert: Verify the outcome(s) of the test. * *

This pattern has several significant benefits. It creates a clear separation between a test's @@ -48,53 +51,52 @@ * 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. * - *

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 + *

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()); } diff --git a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java index 142fd623abb8..5756822516b8 100644 --- a/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java +++ b/arrange-act-assert/src/test/java/com/iluwatar/arrangeactassert/CashAntiAAATest.java @@ -37,23 +37,22 @@ * 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()); diff --git a/async-method-invocation/README.md b/async-method-invocation/README.md index 9b0e4e43e13c..519152e1268c 100644 --- a/async-method-invocation/README.md +++ b/async-method-invocation/README.md @@ -36,6 +36,10 @@ Wikipedia says > In multithreaded computer programming, asynchronous method invocation (AMI), also known as asynchronous method calls or the asynchronous pattern is a design pattern in which the call site is not blocked while waiting for the called code to finish. Instead, the calling thread is notified when the reply arrives. Polling for a reply is an undesired option. +Sequence diagram + +![Async Method Invocation sequence diagram](./etc/async-method-invocation-sequence-diagram.png "Async Method Invocation sequence diagram") + ## Programmatic Example of Async Method Invocation Pattern in Java Consider a scenario where multiple tasks need to be executed simultaneously. Using the Async Method Invocation pattern, you can initiate these tasks without waiting for each to complete, thus optimizing resource usage and reducing latency. diff --git a/async-method-invocation/etc/async-method-invocation-sequence-diagram.png b/async-method-invocation/etc/async-method-invocation-sequence-diagram.png new file mode 100644 index 000000000000..28420d783379 Binary files /dev/null and b/async-method-invocation/etc/async-method-invocation-sequence-diagram.png differ diff --git a/async-method-invocation/pom.xml b/async-method-invocation/pom.xml index 52a03369f47d..d9ddd918c8e9 100644 --- a/async-method-invocation/pom.xml +++ b/async-method-invocation/pom.xml @@ -34,6 +34,14 @@ async-method-invocation + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java index 01fd8f1c681a..ec3beed3be4d 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/App.java @@ -30,15 +30,15 @@ /** * In this example, we are launching space rockets and deploying lunar rovers. * - *

The application demonstrates the async method invocation pattern. The key parts of the - * pattern are AsyncResult which is an intermediate container for an asynchronously - * evaluated value, AsyncCallback which can be provided to be executed on task - * completion and AsyncExecutor that manages the execution of the async tasks. + *

The application demonstrates the async method invocation pattern. The key parts of the pattern + * are AsyncResult which is an intermediate container for an asynchronously evaluated + * value, AsyncCallback which can be provided to be executed on task completion and + * AsyncExecutor that manages the execution of the async tasks. * - *

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. + *

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. * *

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 static void main(String[] args) throws Exception { 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 static void main(String[] args) throws Exception { /** * 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 */ diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java index fcea6d07190d..3bae90830098 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncExecutor.java @@ -27,9 +27,7 @@ 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 endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java index d71cf0defcd1..3eebdc4e773d 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/AsyncResult.java @@ -44,7 +44,7 @@ public interface AsyncResult { * 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; diff --git a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java index f4a50e0d6c30..a1261f34184c 100644 --- a/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java +++ b/async-method-invocation/src/main/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutor.java @@ -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 AsyncResult startProcess(Callable task) { @Override public AsyncResult startProcess(Callable task, AsyncCallback 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 endProcess(AsyncResult asyncResult) throws ExecutionException, - InterruptedException { + public T endProcess(AsyncResult asyncResult) + throws ExecutionException, InterruptedException { if (!asyncResult.isCompleted()) { asyncResult.await(); } diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java index f31c5549bdd1..d58a3f6b5c30 100644 --- a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/AppTest.java @@ -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. + *

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[] {})); } } diff --git a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java index c9f85f48d9c9..d6540ce77309 100644 --- a/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java +++ b/async-method-invocation/src/test/java/com/iluwatar/async/method/invocation/ThreadAsyncExecutorTest.java @@ -33,7 +33,6 @@ 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.Mock; import org.mockito.MockitoAnnotations; -/** - * ThreadAsyncExecutorTest - * - */ +/** ThreadAsyncExecutorTest */ class ThreadAsyncExecutorTest { - @Captor - private ArgumentCaptor exceptionCaptor; + @Captor private ArgumentCaptor exceptionCaptor; - @Mock - private Callable task; + @Mock private Callable task; - @Mock - private AsyncCallback callback; + @Mock private AsyncCallback 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(); - - 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()); - - // Our task should only execute once ... - verify(task, times(1)).call(); - - // ... and the result should be exactly the same object - assertSame(result, asyncResult.getValue()); - }); + 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 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(); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + }); } /** @@ -94,28 +87,30 @@ void testSuccessfulTaskWithoutCallback() { */ @Test void testSuccessfulTaskWithCallback() { - 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 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(); - - // ... 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()); - }); + 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 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(); + + // ... 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()); + }); } /** @@ -124,38 +119,43 @@ void testSuccessfulTaskWithCallback() { */ @Test void testLongRunningTaskWithoutCallback() { - 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 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()); - } - - // 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); - - // ... and the result should be exactly the same object - assertSame(result, asyncResult.getValue()); - }); + 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 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()); + } + + // 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); + + // ... and the result should be exactly the same object + assertSame(result, asyncResult.getValue()); + }); } /** @@ -164,42 +164,47 @@ void testLongRunningTaskWithoutCallback() { */ @Test void testLongRunningTaskWithCallback() { - 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 asyncResult = executor.startProcess(task, callback); - assertNotNull(asyncResult); - assertFalse(asyncResult.isCompleted()); - - verifyNoMoreInteractions(callback); - - 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)); - - // 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()); - }); + 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 asyncResult = executor.startProcess(task, callback); + assertNotNull(asyncResult); + assertFalse(asyncResult.isCompleted()); + + verifyNoMoreInteractions(callback); + + 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)); + + // 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()); + }); } /** @@ -209,35 +214,40 @@ void testLongRunningTaskWithCallback() { */ @Test void testEndProcess() { - 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 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()); - } - - 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); - }); + 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 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()); + } + + 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); + }); } /** @@ -246,25 +256,28 @@ void testEndProcess() { */ @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); - - 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()); - } - }); - + 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()); + } + }); } /** @@ -273,32 +286,35 @@ void testNullTask() { */ @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); - - 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); - - 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()); - } - }); - + 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()); + + 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()); + } + }); } /** @@ -307,28 +323,27 @@ void testNullTaskWithCallback() { */ @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); - - 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()); - } - }); - + 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()); + } + }); } - } diff --git a/backpressure/README.md b/backpressure/README.md new file mode 100644 index 000000000000..d0734ad1f934 --- /dev/null +++ b/backpressure/README.md @@ -0,0 +1,156 @@ +--- +title: "Backpressure Pattern in Java: Gracefully regulate producer-to-consumer data flow to prevent overload." +shortTitle: Backpressure +description: "Dive into the Backpressure design pattern in Java through practical examples, discovering how it prevents overload while ensuring stability and peak performance by aligning data flow with consumer capacity." +category: Concurrency +language: en +tag: + - Asynchronous + - Event-driven + - Reactive + - Resilience +--- + +## Also known as + +* Flow Control +* Rate Limiting Mechanism + +## Intent of the Backpressure Design Pattern + +Control the rate of data production so downstream consumers are not overwhelmed by excessive load. + +## Detailed Explanation of Backpressure Pattern with Real-World Examples + +Real-world example + +> Imagine a busy coffee shop where multiple baristas brew drinks (producers), and a single barista is responsible for carefully crafting latte art (consumer). If drinks are brewed faster than the latte-art barista can decorate them, they pile up, risking quality issues or discarded drinks. By introducing a pacing system—only sending new cups once the latte-art barista is ready—everyone stays synchronized, ensuring minimal waste and a consistently enjoyable customer experience. + +In plain words + +> The Backpressure design pattern is a flow control mechanism that prevents overwhelming a system by regulating data production based on the consumer’s processing capacity. + +Wikipedia says + +> Back pressure (or backpressure) is the term for a resistance to the desired flow of fluid through pipes. Obstructions or tight bends create backpressure via friction loss and pressure drop. In distributed systems in particular event-driven architecture, back pressure is a technique to regulate flow of data, ensuring that components do not become overwhelmed. + +Sequence diagram + +![Backpressure sequence diagram](./etc/backpressure-sequence-diagram.png) + +## Programmatic Example of Backpressure Pattern in Java + +This example demonstrates how backpressure can be implemented using Project Reactor. We begin by creating a simple publisher that emits a stream of integers, introducing a small delay to mimic a slower production rate: + +```java +public class Publisher { + public static Flux publish(int start, int count, int delay) { + return Flux.range(start, count).delayElements(Duration.ofMillis(delay)).log(); + } +} +``` + +Next, we define a custom subscriber by extending Reactor’s BaseSubscriber. It simulates slow processing by sleeping for 500ms per item. Initially, the subscriber requests ten items; for every five items processed, it requests five more: + +```java +public class Subscriber extends BaseSubscriber { + + private static final Logger logger = LoggerFactory.getLogger(Subscriber.class); + + @Override + protected void hookOnSubscribe(@NonNull Subscription subscription) { + logger.info("subscribe()"); + request(10); //request 10 items initially + } + + @Override + protected void hookOnNext(@NonNull Integer value) { + processItem(); + logger.info("process({})", value); + if (value % 5 == 0) { + // request for the next 5 items after processing first 5 + request(5); + } + } + + @Override + protected void hookOnComplete() { + //completed processing. + } + + private void processItem() { + try { + Thread.sleep(500); // simulate slow processing + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + } +} +``` + +Finally, in the `main` method, we publish a range of integers and subscribe using the custom subscriber. A short sleep in the main thread allows the emission, backpressure requests, and processing to be fully observed: + +```java +public static void main(String[] args) throws InterruptedException { + Subscriber sub = new Subscriber(); + Publisher.publish(1, 8, 200).subscribe(sub); + Thread.sleep(5000); //wait for execution + +} +``` + +Below is an example of the program’s output. It shows the subscriber’s log entries, including when it requests more data and when each integer is processed: + +``` +23:09:55.746 [main] DEBUG reactor.util.Loggers -- Using Slf4j logging framework +23:09:55.762 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onSubscribe(FluxConcatMapNoPrefetch.FluxConcatMapNoPrefetchSubscriber) +23:09:55.762 [main] INFO com.iluwatar.backpressure.Subscriber -- subscribe() +23:09:55.763 [main] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(10) +23:09:55.969 [parallel-1] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(1) +23:09:56.475 [parallel-1] INFO com.iluwatar.backpressure.Subscriber -- process(1) +23:09:56.680 [parallel-2] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(2) +23:09:57.185 [parallel-2] INFO com.iluwatar.backpressure.Subscriber -- process(2) +23:09:57.389 [parallel-3] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(3) +23:09:57.894 [parallel-3] INFO com.iluwatar.backpressure.Subscriber -- process(3) +23:09:58.099 [parallel-4] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(4) +23:09:58.599 [parallel-4] INFO com.iluwatar.backpressure.Subscriber -- process(4) +23:09:58.805 [parallel-5] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(5) +23:09:59.311 [parallel-5] INFO com.iluwatar.backpressure.Subscriber -- process(5) +23:09:59.311 [parallel-5] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- request(5) +23:09:59.516 [parallel-6] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(6) +23:10:00.018 [parallel-6] INFO com.iluwatar.backpressure.Subscriber -- process(6) +23:10:00.223 [parallel-7] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(7) +23:10:00.729 [parallel-7] INFO com.iluwatar.backpressure.Subscriber -- process(7) +23:10:00.930 [parallel-8] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onNext(8) +23:10:01.436 [parallel-8] INFO com.iluwatar.backpressure.Subscriber -- process(8) +23:10:01.437 [parallel-8] INFO reactor.Flux.ConcatMapNoPrefetch.1 -- onComplete() +``` + +## When to Use the Backpressure Pattern In Java + +* Use in Java systems where data is produced at high velocity and consumers risk overload. +* Applicable in reactive or event-driven architectures to maintain stability under varying load conditions. + +## Benefits and Trade-offs of Backpressure Pattern + +Benefits: + +* Protects consumers from saturation and resource exhaustion. + +Trade-offs: + +* Introduces possible delays if production must slow down to match consumer capacity. +* Requires careful orchestration in complex systems with multiple concurrent data sources. + +## Related Java Design Patterns + +* [Observer Pattern](https://java-design-patterns.com/patterns/observer/): Both patterns involve a producer notifying consumers. Observer is synchronous and tightly coupled (observers know the subject). +* [Publish-Subscribe Pattern](https://java-design-patterns.com/patterns/publish-subscribe/): Both patterns deal with asynchronous data flow and can work together to manage message distribution and consumption effectively. + +## References and Credits + +* [Backpressure Explained (RedHat Developers Blog)](https://developers.redhat.com/articles/backpressure-explained) +* [Hands-On Reactive Programming in Spring 5](https://amzn.to/3YuYfyO) +* [Reactive Programming with RxJava: Creating Asynchronous, Event-Based Applications](https://amzn.to/42negbf) +* [Reactive Streams in Java](https://amzn.to/3RJjUzA) +* [Reactive Streams Specification](https://www.reactive-streams.org/) diff --git a/backpressure/etc/backpressure-sequence-diagram.png b/backpressure/etc/backpressure-sequence-diagram.png new file mode 100644 index 000000000000..b74a5d2a5362 Binary files /dev/null and b/backpressure/etc/backpressure-sequence-diagram.png differ diff --git a/backpressure/etc/backpressure.png b/backpressure/etc/backpressure.png new file mode 100644 index 000000000000..0de9ff54b673 Binary files /dev/null and b/backpressure/etc/backpressure.png differ diff --git a/backpressure/pom.xml b/backpressure/pom.xml new file mode 100644 index 000000000000..fcc15892fb8a --- /dev/null +++ b/backpressure/pom.xml @@ -0,0 +1,81 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + backpressure + + + + io.projectreactor + reactor-core + 3.8.0-M1 + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + io.projectreactor + reactor-test + 3.8.0-M1 + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.backpressure.App + + + + + + + + + \ No newline at end of file diff --git a/backpressure/src/main/java/com/iluwatar/backpressure/App.java b/backpressure/src/main/java/com/iluwatar/backpressure/App.java new file mode 100644 index 000000000000..4632c42a467f --- /dev/null +++ b/backpressure/src/main/java/com/iluwatar/backpressure/App.java @@ -0,0 +1,76 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.backpressure; + +import java.util.concurrent.CountDownLatch; + +/** + * The Backpressure pattern is a flow control mechanism. It allows a consumer to signal to a + * producer to slow down or stop sending data when it's overwhelmed. + *
  • Prevents memory overflow, CPU thrashing, and resource exhaustion. + *
  • Ensures fair usage of resources in distributed systems. + *
  • Avoids buffer bloat and latency spikes. Key concepts of this design paradigm involves + *
  • Publisher/Producer: Generates data. + *
  • Subscriber/Consumer: Receives and processes data. + * + *

    In this example we will create a {@link Publisher} and a {@link Subscriber}. Publisher + * will emit a stream of integer values with a predefined delay. Subscriber takes 500 ms to + * process one integer. Since the subscriber can't process the items fast enough we apply + * backpressure to the publisher so that it will request 10 items first, process 5 items and + * request for the next 5 again. After processing 5 items subscriber will keep requesting for + * another 5 until the stream ends. + */ +public class App { + + protected static CountDownLatch latch; + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) throws InterruptedException { + + /* + * This custom subscriber applies backpressure: + * - Has a processing delay of 0.5 milliseconds + * - Requests 10 items initially + * - Process 5 items and request for the next 5 items + */ + Subscriber sub = new Subscriber(); + // slow publisher emit 15 numbers with a delay of 200 milliseconds + Publisher.publish(1, 17, 200).subscribe(sub); + + latch = new CountDownLatch(1); + latch.await(); + + sub = new Subscriber(); + // fast publisher emit 15 numbers with a delay of 1 millisecond + Publisher.publish(1, 17, 1).subscribe(sub); + + latch = new CountDownLatch(1); + latch.await(); + } +} diff --git a/backpressure/src/main/java/com/iluwatar/backpressure/Publisher.java b/backpressure/src/main/java/com/iluwatar/backpressure/Publisher.java new file mode 100644 index 000000000000..1d39a070ae57 --- /dev/null +++ b/backpressure/src/main/java/com/iluwatar/backpressure/Publisher.java @@ -0,0 +1,46 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.backpressure; + +import java.time.Duration; +import reactor.core.publisher.Flux; + +/** This class is the publisher that generates the data stream. */ +public class Publisher { + + private Publisher() {} + + /** + * On message method will trigger when the subscribed event is published. + * + * @param start starting integer + * @param count how many integers to emit + * @param delay delay between each item in milliseconds + * @return a flux stream of integers + */ + public static Flux publish(int start, int count, int delay) { + return Flux.range(start, count).delayElements(Duration.ofMillis(delay)).log(); + } +} diff --git a/backpressure/src/main/java/com/iluwatar/backpressure/Subscriber.java b/backpressure/src/main/java/com/iluwatar/backpressure/Subscriber.java new file mode 100644 index 000000000000..40ff5aebc814 --- /dev/null +++ b/backpressure/src/main/java/com/iluwatar/backpressure/Subscriber.java @@ -0,0 +1,64 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.backpressure; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.reactivestreams.Subscription; +import reactor.core.publisher.BaseSubscriber; + +/** This class is the custom subscriber that subscribes to the data stream. */ +@Slf4j +public class Subscriber extends BaseSubscriber { + + @Override + protected void hookOnSubscribe(@NonNull Subscription subscription) { + request(10); // request 10 items initially + } + + @Override + protected void hookOnNext(@NonNull Integer value) { + processItem(); + LOGGER.info("process({})", value); + if (value % 5 == 0) { + // request for the next 5 items after processing first 5 + request(5); + } + } + + @Override + protected void hookOnComplete() { + App.latch.countDown(); + } + + private void processItem() { + try { + Thread.sleep(500); // simulate slow processing + } catch (InterruptedException e) { + LOGGER.error(e.getMessage(), e); + Thread.currentThread().interrupt(); + } + } +} diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/AppTest.java b/backpressure/src/test/java/com/iluwatar/backpressure/AppTest.java new file mode 100644 index 000000000000..8fe2245a97b7 --- /dev/null +++ b/backpressure/src/test/java/com/iluwatar/backpressure/AppTest.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.backpressure; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +public class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/LoggerExtension.java b/backpressure/src/test/java/com/iluwatar/backpressure/LoggerExtension.java new file mode 100644 index 000000000000..e99926e00a1a --- /dev/null +++ b/backpressure/src/test/java/com/iluwatar/backpressure/LoggerExtension.java @@ -0,0 +1,60 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.backpressure; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.LoggerFactory; + +public class LoggerExtension implements BeforeEachCallback, AfterEachCallback { + + private final ListAppender listAppender = new ListAppender<>(); + private final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + @Override + public void afterEach(ExtensionContext extensionContext) { + listAppender.stop(); + listAppender.list.clear(); + logger.detachAppender(listAppender); + } + + @Override + public void beforeEach(ExtensionContext extensionContext) { + logger.addAppender(listAppender); + listAppender.start(); + } + + public List getFormattedMessages() { + return listAppender.list.stream() + .map(ILoggingEvent::getFormattedMessage) + .collect(Collectors.toList()); + } +} diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/PublisherTest.java b/backpressure/src/test/java/com/iluwatar/backpressure/PublisherTest.java new file mode 100644 index 000000000000..010a80e83959 --- /dev/null +++ b/backpressure/src/test/java/com/iluwatar/backpressure/PublisherTest.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.backpressure; + +import static com.iluwatar.backpressure.Publisher.publish; + +import java.time.Duration; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +public class PublisherTest { + + @Test + public void testPublish() { + + Flux flux = publish(1, 3, 200); + + StepVerifier.withVirtualTime(() -> flux) + .expectSubscription() + .expectNoEvent(Duration.ofMillis(200)) + .expectNext(1) + .expectNoEvent(Duration.ofSeconds(200)) + .expectNext(2) + .expectNoEvent(Duration.ofSeconds(200)) + .expectNext(3) + .verifyComplete(); + } +} diff --git a/backpressure/src/test/java/com/iluwatar/backpressure/SubscriberTest.java b/backpressure/src/test/java/com/iluwatar/backpressure/SubscriberTest.java new file mode 100644 index 000000000000..7f7676612d02 --- /dev/null +++ b/backpressure/src/test/java/com/iluwatar/backpressure/SubscriberTest.java @@ -0,0 +1,54 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.backpressure; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.CountDownLatch; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class SubscriberTest { + + @RegisterExtension public LoggerExtension loggerExtension = new LoggerExtension(); + + @Test + public void testSubscribe() throws InterruptedException { + + Subscriber sub = new Subscriber(); + Publisher.publish(1, 8, 100).subscribe(sub); + + App.latch = new CountDownLatch(1); + App.latch.await(); + + String result = String.join(",", loggerExtension.getFormattedMessages()); + assertTrue( + result.endsWith( + "onSubscribe(FluxConcatMapNoPrefetch." + + "FluxConcatMapNoPrefetchSubscriber),request(10),onNext(1),process(1),onNext(2)," + + "process(2),onNext(3),process(3),onNext(4),process(4),onNext(5),process(5),request(5)," + + "onNext(6),process(6),onNext(7),process(7),onNext(8),process(8),onComplete()")); + } +} diff --git a/balking/README.md b/balking/README.md index f659d278b349..cdd09cf5b7d1 100644 --- a/balking/README.md +++ b/balking/README.md @@ -29,6 +29,10 @@ Wikipedia says > The balking pattern is a software design pattern that only executes an action on an object when the object is in a particular state. For example, if an object reads ZIP files and a calling method invokes a get method on the object when the ZIP file is not open, the object would "balk" at the request. +Flowchart + +![Balking flowchart](./etc/balking-flowchart.png) + ## Programmatic Example of Balking Pattern in Java This example demonstrates the Balking Pattern in a multithreaded Java application, highlighting state management and concurrency control. The Balking Pattern is exemplified by a washing machine's start button that initiates washing only if the machine is idle. This ensures state management and prevents concurrent issues. diff --git a/balking/etc/balking-flowchart.png b/balking/etc/balking-flowchart.png new file mode 100644 index 000000000000..400a1ce8d3e3 Binary files /dev/null and b/balking/etc/balking-flowchart.png differ diff --git a/balking/pom.xml b/balking/pom.xml index b9902e671351..818f7549c330 100644 --- a/balking/pom.xml +++ b/balking/pom.xml @@ -34,6 +34,14 @@ 4.0.0 balking + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/balking/src/main/java/com/iluwatar/balking/DelayProvider.java b/balking/src/main/java/com/iluwatar/balking/DelayProvider.java index f142c8c7bf72..f27922219ea6 100644 --- a/balking/src/main/java/com/iluwatar/balking/DelayProvider.java +++ b/balking/src/main/java/com/iluwatar/balking/DelayProvider.java @@ -26,9 +26,7 @@ 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); } diff --git a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java index 794cfbd8ae15..52ce7c593b6b 100644 --- a/balking/src/main/java/com/iluwatar/balking/WashingMachine.java +++ b/balking/src/main/java/com/iluwatar/balking/WashingMachine.java @@ -28,30 +28,26 @@ 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 WashingMachine(DelayProvider delayProvider) { 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 void wash() { 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()); } - } diff --git a/balking/src/test/java/com/iluwatar/balking/AppTest.java b/balking/src/test/java/com/iluwatar/balking/AppTest.java index b744b6786613..40beabf553d0 100644 --- a/balking/src/test/java/com/iluwatar/balking/AppTest.java +++ b/balking/src/test/java/com/iluwatar/balking/AppTest.java @@ -24,27 +24,22 @@ */ package com.iluwatar.balking; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; -/** - * 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. + *

    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); } - -} \ No newline at end of file +} diff --git a/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java index f5c1e2de60f3..9bf7ac2548a0 100644 --- a/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java +++ b/balking/src/test/java/com/iluwatar/balking/WashingMachineTest.java @@ -29,9 +29,7 @@ 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 @@ public void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task) { this.task = task; } } -} \ No newline at end of file +} diff --git a/bloc/README.md b/bloc/README.md new file mode 100644 index 000000000000..76a4a0917485 --- /dev/null +++ b/bloc/README.md @@ -0,0 +1,230 @@ +--- +title: "Bloc Pattern in Java: State Management Simplified" +shortTitle: Bloc +description: "Learn how the Bloc pattern helps manage state changes in Java applications. This guide covers dynamic listener management, real-world examples, and clean code practices for state management." +category: Architectural +language: en +tag: + - Abstraction + - Data binding + - Decoupling + - Event-driven + - Presentation + - Reactive + - Reusability + - State tracking +--- + +## Also known as + +* Business Logic Component +* Business Logic Controller + +## Intent of the Bloc Pattern + +The Bloc pattern manages the state of an object and allows for dynamically notifying interested listeners about state changes. It separates state management logic from the rest of the application, improving code organization and flexibility. + +## Detailed explanation of the Bloc pattern with real-World examples + +Real-world example + +> Consider a digital counter application where multiple parts of the UI need to be updated whenever the counter changes. For example, a label displaying the counter value and an activity log showing changes. Instead of directly modifying these UI components, the Bloc pattern manages the counter state and notifies all registered listeners about the state change. Listeners can dynamically subscribe or unsubscribe from receiving updates. + +In plain words + +> The Bloc pattern manages a single state object and dynamically notifies registered listeners whenever the state changes. + +Wikipedia says + +> While not a formalized "Gang of Four" design pattern, Bloc is widely used in state-driven applications. It centralizes state management and propagates state changes to registered observers, following principles of separation of concerns. + +Sequence diagram + +![Bloc sequence diagram](./etc/bloc-sequence-diagram.png) + +## Programmatic Example of the Bloc Pattern in Java + +This example demonstrates how to implement the Bloc pattern using Java and Swing. The pattern separates the state of the application from UI components, and provides a reactive, event-driven approach to managing updates. + +Core components of the Bloc Pattern include a `State` object, a `Bloc` class responsible for managing and updating that state, and interfaces (`StateListener` and `ListenerManager`) for subscribing to changes. + +The `State` class represents the application's data at any given time. + +```java +public record State(int value) {} +``` + +The `ListenerManager` interface declares methods to add and remove listeners, as well as retrieve them. + +```java +public interface ListenerManager { + void addListener(StateListener listener); + void removeListener(StateListener listener); + List> getListeners(); +} +``` + +The `StateListener` interface defines how a listener reacts to state changes. + +```java +public interface StateListener { + void onStateChange(T state); +} +``` + +The `Bloc` class maintains the current state and notifies listeners of updates. It provides `increment` and `decrement` methods to update state and automatically notify registered listeners. + +```java +public class Bloc implements ListenerManager { + + private State currentState; + private final List> listeners = new ArrayList<>(); + + public Bloc() { + this.currentState = new State(0); + } + + @Override + public void addListener(StateListener listener) { + listeners.add(listener); + listener.onStateChange(currentState); + } + + @Override + public void removeListener(StateListener listener) { + listeners.remove(listener); + } + + @Override + public List> getListeners() { + return Collections.unmodifiableList(listeners); + } + + private void emitState(State newState) { + currentState = newState; + for (StateListener listener : listeners) { + listener.onStateChange(currentState); + } + } + + public void increment() { + emitState(new State(currentState.value() + 1)); + } + + public void decrement() { + emitState(new State(currentState.value() - 1)); + } +} +``` + +This class demonstrates how to integrate the Bloc pattern with a simple Swing GUI. It sets up a counter, buttons to change the state, and a toggle to enable or disable the listener dynamically. + +```java +public class Main { + public static void main(String[] args) { + BlocUi blocUi = new BlocUi(); + blocUi.createAndShowUi(); + } +} + +public class BlocUi { + + public void createAndShowUi() { + final Bloc bloc = new Bloc(); + + JFrame frame = new JFrame("BloC example"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setSize(400, 300); + + JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); + counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); + + JButton decrementButton = new JButton("Decrement"); + JButton toggleListenerButton = new JButton("Disable Listener"); + JButton incrementButton = new JButton("Increment"); + + frame.setLayout(new BorderLayout()); + frame.add(counterLabel, BorderLayout.CENTER); + frame.add(incrementButton, BorderLayout.NORTH); + frame.add(decrementButton, BorderLayout.SOUTH); + frame.add(toggleListenerButton, BorderLayout.EAST); + + StateListener stateListener = state -> counterLabel.setText("Counter: " + state.value()); + + bloc.addListener(stateListener); + + toggleListenerButton.addActionListener( + e -> { + if (bloc.getListeners().contains(stateListener)) { + bloc.removeListener(stateListener); + toggleListenerButton.setText("Enable Listener"); + } else { + bloc.addListener(stateListener); + toggleListenerButton.setText("Disable Listener"); + } + }); + + incrementButton.addActionListener(e -> bloc.increment()); + decrementButton.addActionListener(e -> bloc.decrement()); + + frame.setVisible(true); + } +} +``` + +### Program Output + +- **On Increment** + `Counter: 1` + +- **On Decrement** + `Counter: 0` + +- **Dynamic Listener Toggle** + - Listener disabled: Counter stops updating. + - Listener enabled: Counter updates again. + +## When to Use the Bloc Pattern + +Use the Bloc pattern when: + +* When you want a clean separation of business logic and UI in Java applications +* When you need a reactive approach to updating UI based on state changes +* When you want to avoid coupling controllers or presenters directly to data manipulation +* When multiple UI elements need access to the same business logic + +## Real-World Applications of Bloc Pattern + +* Java-based desktop applications that require real-time UI updates +* Backend-driven Java frameworks that separate service layers from presentation +* Cross-platform applications where the logic must remain consistent regardless of the UI technology + +## Benefits and Trade-offs of Bloc Pattern + +Benefits: + +* Simplifies UI components by removing direct business logic +* Improves testability by isolating state and behavior +* Encourages code reuse by centralizing data flows +* Enhances maintainability through clear separation of concerns + +Trade-offs: + +* May introduce additional boilerplate code for managing streams or observers +* Requires careful design to avoid a monolithic “god” component +* Demands consistent reactive programming practices to be effective + +## Related Patterns + +- [Observer](https://java-design-patterns.com/patterns/observer/): Bloc is a specialized implementation of the Observer pattern. +- [Mediator](https://java-design-patterns.com/patterns/mediator/): Orchestrates interactions among multiple objects through a central component +- [MVC](https://java-design-patterns.com/patterns/model-view-controller/): Shares the idea of separating concerns between layers + +## References and Credits + +* [Bloc architecture(bloclibrary.dev)](https://bloclibrary.dev/architecture/) +* [Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://amzn.to/3UoKkaR) +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Event-Driven Programming in Java (Oracle)](https://www.oracle.com/java/) +* [Java Swing Documentation (Oracle)](https://docs.oracle.com/javase/tutorial/uiswing/) diff --git a/bloc/etc/bloc-sequence-diagram.png b/bloc/etc/bloc-sequence-diagram.png new file mode 100644 index 000000000000..588863985019 Binary files /dev/null and b/bloc/etc/bloc-sequence-diagram.png differ diff --git a/bloc/etc/bloc.png b/bloc/etc/bloc.png new file mode 100644 index 000000000000..60d6eb77c8fc Binary files /dev/null and b/bloc/etc/bloc.png differ diff --git a/bloc/etc/bloc.puml b/bloc/etc/bloc.puml new file mode 100644 index 000000000000..5991f533ae70 --- /dev/null +++ b/bloc/etc/bloc.puml @@ -0,0 +1,41 @@ +@startuml +package com.iluwatar.bloc { + + class State { + - value : int + + State(value : int) + + getValue() : int + } + + interface StateListener { + + onStateChange(state : T) + } + + interface ListenerManager { + + addListener(listener : StateListener) + + removeListener(listener : StateListener) + + getListeners() : List> + } + + class BloC { + - currentState : State + - listeners : List> + + BloC() + + addListener(listener : StateListener) + + removeListener(listener : StateListener) + + getListeners() : List> + - emitState(newState : State) + + increment() + + decrement() + } + + class Main { + + main(args : String[]) + } + + ListenerManager <|.. BloC + StateListener <|.. BloC + BloC o-- State + BloC *-- StateListener +} +@enduml diff --git a/bloc/etc/bloc.urm.puml b/bloc/etc/bloc.urm.puml new file mode 100644 index 000000000000..6408aa76e6a4 --- /dev/null +++ b/bloc/etc/bloc.urm.puml @@ -0,0 +1,32 @@ +@startuml +package com.iluwatar.bloc { + class Bloc { + - currentState : State + - listeners : List> + + Bloc() + + addListener(listener : StateListener) + + decrement() + - emitState(newState : State) + + getListeners() : List> + + increment() + + removeListener(listener : StateListener) + } + class BlocUi { + + BlocUi() + + createAndShowUi() + } + interface ListenerManager { + + addListener(StateListener) {abstract} + + getListeners() : List> {abstract} + + removeListener(StateListener) {abstract} + } + class Main { + + Main() + + main(args : String[]) {static} + } + interface StateListener { + + onStateChange(T) {abstract} + } +} +Bloc ..|> ListenerManager +@enduml \ No newline at end of file diff --git a/bloc/pom.xml b/bloc/pom.xml new file mode 100644 index 000000000000..cc52a3b99dc2 --- /dev/null +++ b/bloc/pom.xml @@ -0,0 +1,76 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + bloc + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.testng + testng + 7.11.0 + test + + + org.assertj + assertj-core + 3.27.3 + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.bloc.Main + + + + + + + + + diff --git a/bloc/src/main/java/com/iluwatar/bloc/Bloc.java b/bloc/src/main/java/com/iluwatar/bloc/Bloc.java new file mode 100644 index 000000000000..f6ab0a61cdbf --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/Bloc.java @@ -0,0 +1,98 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +import java.util.ArrayList; +import java.util.Collections; +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. + */ +public class Bloc implements ListenerManager { + + private State currentState; + private final List> listeners = new ArrayList<>(); + + /** Constructs a new Bloc instance with an initial state of value 0. */ + public Bloc() { + this.currentState = new State(0); + } + + /** + * Adds a listener to receive state change notifications. + * + * @param listener the listener to add + */ + @Override + public void addListener(StateListener listener) { + listeners.add(listener); + listener.onStateChange(currentState); + } + + /** + * Removes a listener from receiving state change notifications. + * + * @param listener the listener to remove + */ + @Override + public void removeListener(StateListener listener) { + listeners.remove(listener); + } + + /** + * Returns an unmodifiable list of all registered listeners. + * + * @return an unmodifiable list of listeners + */ + @Override + public List> getListeners() { + return Collections.unmodifiableList(listeners); + } + + /** + * Emits a new state and notifies all registered listeners of the change. + * + * @param newState the new state to emit + */ + private void emitState(State newState) { + currentState = newState; + for (StateListener listener : listeners) { + listener.onStateChange(currentState); + } + } + + /** 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. */ + public void decrement() { + emitState(new State(currentState.value() - 1)); + } +} diff --git a/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java b/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java new file mode 100644 index 000000000000..500d455d82e1 --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/BlocUi.java @@ -0,0 +1,85 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +import java.awt.BorderLayout; +import java.awt.Font; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.SwingConstants; +import javax.swing.WindowConstants; + +/** The BlocUI class handles the creation and management of the UI components. */ +public class BlocUi { + + /** Creates and shows the UI. */ + public void createAndShowUi() { + // Create a Bloc instance to manage the state + final Bloc bloc = new Bloc(); + + // setting up a frame window with a title + JFrame frame = new JFrame("BloC example"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setSize(400, 300); + + // label to display the counter value + JLabel counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); + counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); + + // buttons for increment, decrement, and toggling listener + JButton decrementButton = new JButton("Decrement"); + JButton toggleListenerButton = new JButton("Disable Listener"); + JButton incrementButton = new JButton("Increment"); + + frame.setLayout(new BorderLayout()); + frame.add(counterLabel, BorderLayout.CENTER); + frame.add(incrementButton, BorderLayout.NORTH); + frame.add(decrementButton, BorderLayout.SOUTH); + frame.add(toggleListenerButton, BorderLayout.EAST); + + // making a state listener to update the counter label when the state changes + StateListener stateListener = state -> counterLabel.setText("Counter: " + state.value()); + + // adding the listener to the Bloc instance + bloc.addListener(stateListener); + + toggleListenerButton.addActionListener( + e -> { + if (bloc.getListeners().contains(stateListener)) { + bloc.removeListener(stateListener); + toggleListenerButton.setText("Enable Listener"); + } else { + bloc.addListener(stateListener); + toggleListenerButton.setText("Disable Listener"); + } + }); + + incrementButton.addActionListener(e -> bloc.increment()); + decrementButton.addActionListener(e -> bloc.decrement()); + + frame.setVisible(true); + } +} diff --git a/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java b/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java new file mode 100644 index 000000000000..cd55b0fb320d --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/ListenerManager.java @@ -0,0 +1,56 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +import java.util.List; + +/** + * Interface for managing listeners for state changes. + * + * @param The type of state to be handled by the listeners. + */ +public interface ListenerManager { + + /** + * Adds a listener that will be notified of state changes. + * + * @param listener the listener to be added + */ + void addListener(StateListener listener); + + /** + * Removes a listener so that it no longer receives state change notifications. + * + * @param listener the listener to be removed + */ + void removeListener(StateListener listener); + + /** + * Returns a list of all listeners currently registered for state changes. + * + * @return a list of registered listeners + */ + List> getListeners(); +} diff --git a/bloc/src/main/java/com/iluwatar/bloc/Main.java b/bloc/src/main/java/com/iluwatar/bloc/Main.java new file mode 100644 index 000000000000..b7a929bcf2bd --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/Main.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +/** + * The BLoC (Business Logic Component) pattern is a software design pattern primarily used in + * Flutter applications. It facilitates the separation of business logic from UI code, making the + * application more modular, testable, and scalable. The BLoC pattern uses streams to manage the + * flow of data and state changes, allowing widgets to react to new states as they arrive. In the + * BLoC pattern, the application is divided into three key components: - Input streams: Represent + * user interactions or external events fed into the BLoC. - Business logic: Processes the input and + * determines the resulting state or actions. - Output streams: Emit the updated state for the UI to + * consume. The BLoC pattern is especially useful in reactive programming scenarios and aligns well + * with the declarative nature of Flutter. By using this pattern, developers can ensure a clear + * separation of concerns, enhance reusability, and maintain consistent state management throughout + * the application. + */ +public class Main { + + /** + * The entry point of the application. Initializes the GUI. + * + * @param args command-line arguments (not used in this example) + */ + public static void main(String[] args) { + BlocUi blocUi = new BlocUi(); + blocUi.createAndShowUi(); + } +} diff --git a/bloc/src/main/java/com/iluwatar/bloc/State.java b/bloc/src/main/java/com/iluwatar/bloc/State.java new file mode 100644 index 000000000000..430747548cd3 --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/State.java @@ -0,0 +1,31 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +/** + * The {@code State} class represents a state with an integer value. This class encapsulates the + * value and provides methods to retrieve it. + */ +public record State(int value) {} diff --git a/bloc/src/main/java/com/iluwatar/bloc/StateListener.java b/bloc/src/main/java/com/iluwatar/bloc/StateListener.java new file mode 100644 index 000000000000..77aac172e4e3 --- /dev/null +++ b/bloc/src/main/java/com/iluwatar/bloc/StateListener.java @@ -0,0 +1,42 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +/** + * The {@code StateListener} interface defines the contract for listening to state changes. + * Implementations of this interface should handle state changes and define actions to take when the + * state changes. + * + * @param the type of state that this listener will handle + */ +public interface StateListener { + + /** + * This method is called when the state has changed. + * + * @param state the updated state + */ + void onStateChange(T state); +} diff --git a/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java b/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java new file mode 100644 index 000000000000..98e34b8d4a22 --- /dev/null +++ b/bloc/src/test/java/com/iluwatar/bloc/BlocTest.java @@ -0,0 +1,85 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class BlocTest { + private Bloc bloc; + private AtomicInteger stateValue; + + @BeforeEach + void setUp() { + bloc = new Bloc(); + stateValue = new AtomicInteger(0); + } + + @Test + void initialState() { + assertTrue(bloc.getListeners().isEmpty(), "No listeners should be present initially."); + } + + @Test + void IncrementUpdateState() { + bloc.addListener(state -> stateValue.set(state.value())); + bloc.increment(); + assertEquals(1, stateValue.get(), "State should increment to 1"); + } + + @Test + void DecrementUpdateState() { + bloc.addListener(state -> stateValue.set(state.value())); + bloc.decrement(); + assertEquals(-1, stateValue.get(), "State should decrement to -1"); + } + + @Test + void addingListener() { + bloc.addListener(state -> {}); + assertEquals(1, bloc.getListeners().size(), "Listener count should be 1."); + } + + @Test + void removingListener() { + StateListener listener = state -> {}; + bloc.addListener(listener); + bloc.removeListener(listener); + assertTrue(bloc.getListeners().isEmpty(), "Listener count should be 0 after removal."); + } + + @Test + void multipleListeners() { + AtomicInteger secondValue = new AtomicInteger(); + bloc.addListener(state -> stateValue.set(state.value())); + bloc.addListener(state -> secondValue.set(state.value())); + bloc.increment(); + assertEquals(1, stateValue.get(), "First listener should receive state 1."); + assertEquals(1, secondValue.get(), "Second listener should receive state 1."); + } +} diff --git a/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java b/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java new file mode 100644 index 000000000000..f1fc73947896 --- /dev/null +++ b/bloc/src/test/java/com/iluwatar/bloc/BlocUiTest.java @@ -0,0 +1,121 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.bloc; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.awt.*; +import javax.swing.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class BlocUiTest { + + private JFrame frame; + private JLabel counterLabel; + private JButton incrementButton; + private JButton decrementButton; + private JButton toggleListenerButton; + private Bloc bloc; + private StateListener stateListener; + + @BeforeEach + void setUp() { + bloc = new Bloc(); // Re-initialize the Bloc for each test + + frame = new JFrame("BloC example"); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frame.setSize(400, 300); + + counterLabel = new JLabel("Counter: 0", SwingConstants.CENTER); + counterLabel.setFont(new Font("Arial", Font.BOLD, 20)); + + incrementButton = new JButton("Increment"); + decrementButton = new JButton("Decrement"); + toggleListenerButton = new JButton("Disable Listener"); + + frame.setLayout(new BorderLayout()); + frame.add(counterLabel, BorderLayout.CENTER); + frame.add(incrementButton, BorderLayout.NORTH); + frame.add(decrementButton, BorderLayout.SOUTH); + frame.add(toggleListenerButton, BorderLayout.EAST); + + stateListener = state -> counterLabel.setText("Counter: " + state.value()); + bloc.addListener(stateListener); + + incrementButton.addActionListener(e -> bloc.increment()); + decrementButton.addActionListener(e -> bloc.decrement()); + toggleListenerButton.addActionListener( + e -> { + if (bloc.getListeners().contains(stateListener)) { + bloc.removeListener(stateListener); + toggleListenerButton.setText("Enable Listener"); + } else { + bloc.addListener(stateListener); + toggleListenerButton.setText("Disable Listener"); + } + }); + + frame.setVisible(true); + } + + @AfterEach + void tearDown() { + frame.dispose(); + bloc = new Bloc(); // Reset Bloc state after each test to avoid state carryover + } + + @Test + void testIncrementButton() { + simulateButtonClick(incrementButton); + assertEquals("Counter: 1", counterLabel.getText()); + } + + @Test + void testDecrementButton() { + simulateButtonClick(decrementButton); + assertEquals("Counter: -1", counterLabel.getText()); + } + + @Test + void testToggleListenerButton() { + // Disable listener + simulateButtonClick(toggleListenerButton); + simulateButtonClick(incrementButton); + assertEquals("Counter: 0", counterLabel.getText()); // Listener is disabled + + // Enable listener + simulateButtonClick(toggleListenerButton); + simulateButtonClick(incrementButton); + assertEquals("Counter: 2", counterLabel.getText()); // Listener is re-enabled + } + + private void simulateButtonClick(JButton button) { + for (var listener : button.getActionListeners()) { + listener.actionPerformed(null); + } + } +} diff --git a/bridge/README.md b/bridge/README.md index 9c45078d2d64..26567b292c81 100644 --- a/bridge/README.md +++ b/bridge/README.md @@ -36,6 +36,10 @@ Wikipedia says > The bridge pattern is a design pattern used in software engineering that is meant to "decouple an abstraction from its implementation so that the two can vary independently" +Sequence diagram + +![Bridge sequence diagram](./etc/bridge-sequence-diagram.png) + ## Programmatic Example of Bridge Pattern in Java Imagine you have a weapon that can have various enchantments, and you need to combine different weapons with different enchantments. How would you handle this? Would you create multiple copies of each weapon, each with a different enchantment, or would you create separate enchantments and apply them to the weapon as needed? The Bridge pattern enables you to do the latter. @@ -203,10 +207,6 @@ The hammer is unwielded. The item's glow fades. ``` -## Bridge Pattern Class Diagram - -![Bridge](./etc/bridge.urm.png "Bridge class diagram") - ## When to Use the Bridge Pattern in Java Consider using the Bridge pattern when: diff --git a/bridge/etc/bridge-sequence-diagram.png b/bridge/etc/bridge-sequence-diagram.png new file mode 100644 index 000000000000..b797827628bc Binary files /dev/null and b/bridge/etc/bridge-sequence-diagram.png differ diff --git a/bridge/pom.xml b/bridge/pom.xml index 915aee30a3ed..3cfd33997c05 100644 --- a/bridge/pom.xml +++ b/bridge/pom.xml @@ -34,6 +34,14 @@ bridge + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/bridge/src/main/java/com/iluwatar/bridge/App.java b/bridge/src/main/java/com/iluwatar/bridge/App.java index 1550782a6f23..c3ea3d50c35a 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/App.java +++ b/bridge/src/main/java/com/iluwatar/bridge/App.java @@ -35,9 +35,9 @@ * have their own class hierarchies. The interface of the implementations can be changed without * affecting the clients. * - *

    In this example we have two class hierarchies. One of weapons and another one of - * enchantments. We can easily combine any weapon with any enchantment using composition instead of - * creating deep class hierarchy. + *

    In this example we have two class hierarchies. One of weapons and another one of enchantments. + * We can easily combine any weapon with any enchantment using composition instead of creating deep + * class hierarchy. */ @Slf4j public class App { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java b/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java index 95f4cc351a7f..4bdd4502fd47 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Enchantment.java @@ -24,9 +24,7 @@ */ package com.iluwatar.bridge; -/** - * Enchantment. - */ +/** Enchantment. */ public interface Enchantment { void onActivate(); diff --git a/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java b/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java index 530fb3e5eb37..42da3523fb31 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java +++ b/bridge/src/main/java/com/iluwatar/bridge/FlyingEnchantment.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * FlyingEnchantment. - */ +/** FlyingEnchantment. */ @Slf4j public class FlyingEnchantment implements Enchantment { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Hammer.java b/bridge/src/main/java/com/iluwatar/bridge/Hammer.java index a7a237c14ebb..328f3b79e9d6 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Hammer.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Hammer.java @@ -27,9 +27,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Hammer. - */ +/** Hammer. */ @Slf4j @AllArgsConstructor public class Hammer implements Weapon { diff --git a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java index 3c311ddbbdc5..ed22174673d3 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java +++ b/bridge/src/main/java/com/iluwatar/bridge/SoulEatingEnchantment.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * SoulEatingEnchantment. - */ +/** SoulEatingEnchantment. */ @Slf4j public class SoulEatingEnchantment implements Enchantment { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Sword.java b/bridge/src/main/java/com/iluwatar/bridge/Sword.java index 7b75ead21e78..417bc9334046 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Sword.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Sword.java @@ -27,9 +27,7 @@ import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Sword. - */ +/** Sword. */ @Slf4j @AllArgsConstructor public class Sword implements Weapon { diff --git a/bridge/src/main/java/com/iluwatar/bridge/Weapon.java b/bridge/src/main/java/com/iluwatar/bridge/Weapon.java index e9452343dcc2..a9f0ab4bbf52 100644 --- a/bridge/src/main/java/com/iluwatar/bridge/Weapon.java +++ b/bridge/src/main/java/com/iluwatar/bridge/Weapon.java @@ -24,9 +24,7 @@ */ package com.iluwatar.bridge; -/** - * Weapon. - */ +/** Weapon. */ public interface Weapon { void wield(); diff --git a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java index 5db5f3eb1eb9..d1136fc90f41 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/AppTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.bridge; -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. + *

    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[] {})); } } diff --git a/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java b/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java index d6c38300c46c..d8853647cb84 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/HammerTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for hammer - */ +/** Tests for hammer */ class HammerTest extends WeaponTest { /** @@ -43,4 +41,4 @@ void testHammer() { final var hammer = spy(new Hammer(mock(FlyingEnchantment.class))); testBasicWeaponActions(hammer); } -} \ No newline at end of file +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java b/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java index d5849e78a3d5..b021cd08d00c 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/SwordTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for sword - */ +/** Tests for sword */ class SwordTest extends WeaponTest { /** @@ -43,4 +41,4 @@ void testSword() { final var sword = spy(new Sword(mock(FlyingEnchantment.class))); testBasicWeaponActions(sword); } -} \ No newline at end of file +} diff --git a/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java b/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java index 47515f9b1b2a..67648ea6391e 100644 --- a/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java +++ b/bridge/src/test/java/com/iluwatar/bridge/WeaponTest.java @@ -28,9 +28,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -/** - * Base class for weapon tests - */ +/** Base class for weapon tests */ abstract class WeaponTest { /** @@ -54,6 +52,5 @@ final void testBasicWeaponActions(final Weapon weapon) { weapon.unwield(); verify(enchantment).onDeactivate(); verifyNoMoreInteractions(enchantment); - } } diff --git a/builder/README.md b/builder/README.md index 6a36c334c644..cf6e3242dd51 100644 --- a/builder/README.md +++ b/builder/README.md @@ -40,6 +40,10 @@ public Hero(Profession profession,String name,HairType hairType,HairColor hairCo As you can see, the number of constructor parameters can quickly become overwhelming, making it difficult to understand their arrangement. Additionally, this list of parameters might continue to grow if you decide to add more options in the future. This is known as the telescoping constructor antipattern. +Sequence diagram + +![Builder sequence diagram](./etc/builder-sequence-diagram.png) + ## Programmatic Example of Builder Pattern in Java In this Java Builder pattern example, we construct different types of `Hero` objects with varying attributes. @@ -146,10 +150,6 @@ Program output: 16:28:06.060 [main] INFO com.iluwatar.builder.App -- This is a thief named Desmond with bald head and wielding a bow. ``` -## Builder Pattern Class Diagram - -![Builder](./etc/builder.urm.png "Builder class diagram") - ## When to Use the Builder Pattern in Java Use the Builder pattern when diff --git a/builder/etc/builder-sequence-diagram.png b/builder/etc/builder-sequence-diagram.png new file mode 100644 index 000000000000..355556637870 Binary files /dev/null and b/builder/etc/builder-sequence-diagram.png differ diff --git a/builder/pom.xml b/builder/pom.xml index 37176ce89207..3677c187d5e6 100644 --- a/builder/pom.xml +++ b/builder/pom.xml @@ -34,6 +34,14 @@ builder + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/builder/src/main/java/com/iluwatar/builder/App.java b/builder/src/main/java/com/iluwatar/builder/App.java index 347798cc105c..acec73a48ee1 100644 --- a/builder/src/main/java/com/iluwatar/builder/App.java +++ b/builder/src/main/java/com/iluwatar/builder/App.java @@ -58,22 +58,27 @@ public class App { */ public static void main(String[] args) { - var mage = new Hero.Builder(Profession.MAGE, "Riobard") - .withHairColor(HairColor.BLACK) - .withWeapon(Weapon.DAGGER) - .build(); + var mage = + new Hero.Builder(Profession.MAGE, "Riobard") + .withHairColor(HairColor.BLACK) + .withWeapon(Weapon.DAGGER) + .build(); LOGGER.info(mage.toString()); - var warrior = new Hero.Builder(Profession.WARRIOR, "Amberjill") - .withHairColor(HairColor.BLOND) - .withHairType(HairType.LONG_CURLY).withArmor(Armor.CHAIN_MAIL).withWeapon(Weapon.SWORD) - .build(); + var warrior = + new Hero.Builder(Profession.WARRIOR, "Amberjill") + .withHairColor(HairColor.BLOND) + .withHairType(HairType.LONG_CURLY) + .withArmor(Armor.CHAIN_MAIL) + .withWeapon(Weapon.SWORD) + .build(); LOGGER.info(warrior.toString()); - var thief = new Hero.Builder(Profession.THIEF, "Desmond") - .withHairType(HairType.BALD) - .withWeapon(Weapon.BOW) - .build(); + var thief = + new Hero.Builder(Profession.THIEF, "Desmond") + .withHairType(HairType.BALD) + .withWeapon(Weapon.BOW) + .build(); LOGGER.info(thief.toString()); } } diff --git a/builder/src/main/java/com/iluwatar/builder/Armor.java b/builder/src/main/java/com/iluwatar/builder/Armor.java index 52cbe1a06a88..1710f569af5e 100644 --- a/builder/src/main/java/com/iluwatar/builder/Armor.java +++ b/builder/src/main/java/com/iluwatar/builder/Armor.java @@ -26,12 +26,9 @@ import lombok.AllArgsConstructor; -/** - * Armor enumeration. - */ +/** Armor enumeration. */ @AllArgsConstructor public enum Armor { - CLOTHES("clothes"), LEATHER("leather"), CHAIN_MAIL("chain mail"), diff --git a/builder/src/main/java/com/iluwatar/builder/HairColor.java b/builder/src/main/java/com/iluwatar/builder/HairColor.java index 47fa9b4a9e15..7f767c98d661 100644 --- a/builder/src/main/java/com/iluwatar/builder/HairColor.java +++ b/builder/src/main/java/com/iluwatar/builder/HairColor.java @@ -24,11 +24,8 @@ */ package com.iluwatar.builder; -/** - * HairColor enumeration. - */ +/** HairColor enumeration. */ public enum HairColor { - WHITE, BLOND, RED, @@ -39,5 +36,4 @@ public enum HairColor { public String toString() { return name().toLowerCase(); } - } diff --git a/builder/src/main/java/com/iluwatar/builder/HairType.java b/builder/src/main/java/com/iluwatar/builder/HairType.java index 45b514fb39aa..7ac31d0fa03e 100644 --- a/builder/src/main/java/com/iluwatar/builder/HairType.java +++ b/builder/src/main/java/com/iluwatar/builder/HairType.java @@ -26,12 +26,9 @@ import lombok.AllArgsConstructor; -/** - * HairType enumeration. - */ +/** HairType enumeration. */ @AllArgsConstructor public enum HairType { - BALD("bald"), SHORT("short"), CURLY("curly"), diff --git a/builder/src/main/java/com/iluwatar/builder/Hero.java b/builder/src/main/java/com/iluwatar/builder/Hero.java index 1d15ac2f0a18..a87137e51fe0 100644 --- a/builder/src/main/java/com/iluwatar/builder/Hero.java +++ b/builder/src/main/java/com/iluwatar/builder/Hero.java @@ -24,24 +24,30 @@ */ package com.iluwatar.builder; -/** - * Hero,the record class. - */ - -public record Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { +/** Hero,the record class. */ +public record Hero( + Profession profession, + String name, + HairType hairType, + HairColor hairColor, + Armor armor, + Weapon weapon) { private Hero(Builder builder) { - this(builder.profession, builder.name, builder.hairType, builder.hairColor, builder.armor, builder.weapon); + this( + builder.profession, + builder.name, + builder.hairType, + builder.hairColor, + builder.armor, + builder.weapon); } @Override public String toString() { var sb = new StringBuilder(); - sb.append("This is a ") - .append(profession) - .append(" named ") - .append(name); + sb.append("This is a ").append(profession).append(" named ").append(name); if (hairColor != null || hairType != null) { sb.append(" with "); if (hairColor != null) { @@ -62,9 +68,7 @@ public String toString() { return sb.toString(); } - /** - * The builder class. - */ + /** The builder class. */ public static class Builder { private final Profession profession; @@ -74,9 +78,7 @@ public static class Builder { private Armor armor; private Weapon weapon; - /** - * Constructor. - */ + /** Constructor. */ public Builder(Profession profession, String name) { if (profession == null || name == null) { throw new IllegalArgumentException("profession and name can not be null"); diff --git a/builder/src/main/java/com/iluwatar/builder/Profession.java b/builder/src/main/java/com/iluwatar/builder/Profession.java index 9ab05467703c..c1be949840f8 100644 --- a/builder/src/main/java/com/iluwatar/builder/Profession.java +++ b/builder/src/main/java/com/iluwatar/builder/Profession.java @@ -24,12 +24,12 @@ */ package com.iluwatar.builder; -/** - * Profession enumeration. - */ +/** Profession enumeration. */ public enum Profession { - - WARRIOR, THIEF, MAGE, PRIEST; + WARRIOR, + THIEF, + MAGE, + PRIEST; @Override public String toString() { diff --git a/builder/src/main/java/com/iluwatar/builder/Weapon.java b/builder/src/main/java/com/iluwatar/builder/Weapon.java index 060482705661..03a9565d0cbe 100644 --- a/builder/src/main/java/com/iluwatar/builder/Weapon.java +++ b/builder/src/main/java/com/iluwatar/builder/Weapon.java @@ -24,12 +24,13 @@ */ package com.iluwatar.builder; -/** - * Weapon enumeration. - */ +/** Weapon enumeration. */ public enum Weapon { - - DAGGER, SWORD, AXE, WARHAMMER, BOW; + DAGGER, + SWORD, + AXE, + WARHAMMER, + BOW; @Override public String toString() { diff --git a/builder/src/test/java/com/iluwatar/builder/AppTest.java b/builder/src/test/java/com/iluwatar/builder/AppTest.java index d7b8c2579333..367d3da1c1ac 100644 --- a/builder/src/test/java/com/iluwatar/builder/AppTest.java +++ b/builder/src/test/java/com/iluwatar/builder/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.builder; -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. + *

    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[] {})); } } diff --git a/builder/src/test/java/com/iluwatar/builder/HeroTest.java b/builder/src/test/java/com/iluwatar/builder/HeroTest.java index 48ec7db2a1d5..5f67a56aa999 100644 --- a/builder/src/test/java/com/iluwatar/builder/HeroTest.java +++ b/builder/src/test/java/com/iluwatar/builder/HeroTest.java @@ -30,41 +30,33 @@ import org.junit.jupiter.api.Test; -/** - * HeroTest - * - */ +/** HeroTest */ class HeroTest { - /** - * Test if we get the expected exception when trying to create a hero without a profession - */ + /** Test if we get the expected exception when trying to create a hero without a profession */ @Test void testMissingProfession() { assertThrows(IllegalArgumentException.class, () -> new Hero.Builder(null, "Sir without a job")); } - /** - * Test if we get the expected exception when trying to create a hero without a name - */ + /** Test if we get the expected exception when trying to create a hero without a name */ @Test void testMissingName() { assertThrows(IllegalArgumentException.class, () -> new Hero.Builder(Profession.THIEF, null)); } - /** - * Test if the hero build by the builder has the correct attributes, as requested - */ + /** Test if the hero build by the builder has the correct attributes, as requested */ @Test void testBuildHero() { final String heroName = "Sir Lancelot"; - final var hero = new Hero.Builder(Profession.WARRIOR, heroName) - .withArmor(Armor.CHAIN_MAIL) - .withWeapon(Weapon.SWORD) - .withHairType(HairType.LONG_CURLY) - .withHairColor(HairColor.BLOND) - .build(); + final var hero = + new Hero.Builder(Profession.WARRIOR, heroName) + .withArmor(Armor.CHAIN_MAIL) + .withWeapon(Weapon.SWORD) + .withHairType(HairType.LONG_CURLY) + .withHairColor(HairColor.BLOND) + .build(); assertNotNull(hero); assertNotNull(hero.toString()); @@ -74,7 +66,5 @@ void testBuildHero() { assertEquals(Weapon.SWORD, hero.weapon()); assertEquals(HairType.LONG_CURLY, hero.hairType()); assertEquals(HairColor.BLOND, hero.hairColor()); - } - -} \ No newline at end of file +} diff --git a/business-delegate/README.md b/business-delegate/README.md index 3a3203b230dc..9f83e1a1748a 100644 --- a/business-delegate/README.md +++ b/business-delegate/README.md @@ -36,6 +36,10 @@ Wikipedia says > Business Delegate is a Java EE design pattern. This pattern is directing to reduce the coupling in between business services and the connected presentation tier, and to hide the implementation details of services (including lookup and accessibility of EJB architecture). Business Delegates acts as an adaptor to invoke business objects from the presentation tier. +Sequence diagram + +![Business Delegate sequence diagram](./etc/business-delegate-sequence-diagram.png) + ## Programmatic Example of Business Delegate Pattern in Java The following Java code demonstrates how to implement the Business Delegate pattern. This pattern is particularly useful in applications requiring loose coupling and efficient service interaction. @@ -145,10 +149,6 @@ Here is the console output. 21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing ``` -## Business Delegate Pattern Class Diagram - -![Business Delegate](./etc/business-delegate.urm.png "Business Delegate") - ## When to Use the Business Delegate Pattern in Java Use the Business Delegate pattern when diff --git a/business-delegate/etc/business-delegate-sequence-diagram.png b/business-delegate/etc/business-delegate-sequence-diagram.png new file mode 100644 index 000000000000..a6279e9d1888 Binary files /dev/null and b/business-delegate/etc/business-delegate-sequence-diagram.png differ diff --git a/business-delegate/pom.xml b/business-delegate/pom.xml index 21046526f17c..a5bccb1529e7 100644 --- a/business-delegate/pom.xml +++ b/business-delegate/pom.xml @@ -34,6 +34,14 @@ business-delegate + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java index 682bf68cc41f..c23ed42caecd 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/App.java @@ -34,9 +34,9 @@ * retrieved through service lookups. The Business Delegate itself may contain business logic too * potentially tying together multiple service calls, exception handling, retrying etc. * - *

    In this example the client ({@link MobileClient}) utilizes a business delegate ( - * {@link BusinessDelegate}) to search for movies in video streaming services. The Business Delegate - * then selects the appropriate service and makes the service call. + *

    In this example the client ({@link MobileClient}) utilizes a business delegate ( {@link + * BusinessDelegate}) to search for movies in video streaming services. The Business Delegate then + * selects the appropriate service and makes the service call. */ public class App { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java index 388407fb81ff..81920c857cd8 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessDelegate.java @@ -26,9 +26,7 @@ import lombok.Setter; -/** - * BusinessDelegate separates the presentation and business tiers. - */ +/** BusinessDelegate separates the presentation and business tiers. */ @Setter public class BusinessDelegate { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java index b2d45b9e6975..81a1b2f18fd0 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/BusinessLookup.java @@ -27,9 +27,7 @@ import java.util.Locale; import lombok.Setter; -/** - * Class for performing service lookups. - */ +/** Class for performing service lookups. */ @Setter public class BusinessLookup { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java index cffc464d389a..01b5b642735b 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/MobileClient.java @@ -24,9 +24,7 @@ */ package com.iluwatar.business.delegate; -/** - * MobileClient utilizes BusinessDelegate to call the business tier. - */ +/** MobileClient utilizes BusinessDelegate to call the business tier. */ public class MobileClient { private final BusinessDelegate businessDelegate; diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java index 83b7cd46ef38..696480e678f3 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/NetflixService.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * NetflixService implementation. - */ +/** NetflixService implementation. */ @Slf4j public class NetflixService implements VideoStreamingService { diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java index 9ac09f265b76..594b51850efb 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/VideoStreamingService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.business.delegate; -/** - * Interface for video streaming service implementations. - */ +/** Interface for video streaming service implementations. */ public interface VideoStreamingService { void doProcessing(); diff --git a/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java b/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java index 9b239d585c6f..65c2e55ff6fa 100644 --- a/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java +++ b/business-delegate/src/main/java/com/iluwatar/business/delegate/YouTubeService.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * YouTubeService implementation. - */ +/** YouTubeService implementation. */ @Slf4j public class YouTubeService implements VideoStreamingService { diff --git a/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java b/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java index 1449f81bd6af..5f862bf6e5ff 100644 --- a/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java +++ b/business-delegate/src/test/java/com/iluwatar/business/delegate/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.business.delegate; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Business Delegate example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Business Delegate 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. + *

    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[] {})); } } diff --git a/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java b/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java index 8e8c8eddc73d..4b8e0ee77d5c 100644 --- a/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java +++ b/business-delegate/src/test/java/com/iluwatar/business/delegate/BusinessDelegateTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.business.delegate; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - - import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -/** - * Tests for the {@link BusinessDelegate} - */ +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Tests for the {@link BusinessDelegate} */ class BusinessDelegateTest { private NetflixService netflixService; @@ -62,8 +59,8 @@ void setup() { } /** - * In this example the client ({@link MobileClient}) utilizes a business delegate ( - * {@link BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate + * In this example the client ({@link MobileClient}) utilizes a business delegate ( {@link + * BusinessDelegate}) to execute a task. The Business Delegate then selects the appropriate * service and makes the service call. */ @Test diff --git a/bytecode/README.md b/bytecode/README.md index 889354cf3d04..793eecffcc0e 100644 --- a/bytecode/README.md +++ b/bytecode/README.md @@ -31,6 +31,10 @@ In plain words > An instruction set defines the low-level operations that can be performed. A series of instructions is encoded as a sequence of bytes. A virtual machine executes these instructions one at a time, using a stack for intermediate values. By combining instructions, complex high-level behavior can be defined. +Sequence diagram + +![Bytecode sequence diagram](./etc/bytecode-sequence-diagram.png) + ## Programmatic Example of Bytecode Pattern in Java In this programmatic example, we show how the Bytecode pattern in Java can simplify the execution of complex virtual machine instructions through a well-defined set of operations. This real-world example demonstrates how the Bytecode design pattern in Java can streamline game programming by allowing wizards' behavior to be easily adjusted through bytecode instructions. diff --git a/bytecode/etc/bytecode-sequence-diagram.png b/bytecode/etc/bytecode-sequence-diagram.png new file mode 100644 index 000000000000..c863a0e0b5cd Binary files /dev/null and b/bytecode/etc/bytecode-sequence-diagram.png differ diff --git a/bytecode/pom.xml b/bytecode/pom.xml index eabd294e4ced..2fb01fe2f914 100644 --- a/bytecode/pom.xml +++ b/bytecode/pom.xml @@ -34,6 +34,14 @@ 4.0.0 bytecode + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/App.java b/bytecode/src/main/java/com/iluwatar/bytecode/App.java index c2e3a3b10f86..9293b5876ef5 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/App.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/App.java @@ -58,9 +58,7 @@ public class App { */ public static void main(String[] args) { - var vm = new VirtualMachine( - new Wizard(45, 7, 11, 0, 0), - new Wizard(36, 18, 8, 0, 0)); + var vm = new VirtualMachine(new Wizard(45, 7, 11, 0, 0), new Wizard(36, 18, 8, 0, 0)); vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0)); vm.execute(InstructionConverterUtil.convertToByteCode(LITERAL_0)); diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java b/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java index 91d394234191..25330e73fd78 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/Instruction.java @@ -27,24 +27,21 @@ import lombok.AllArgsConstructor; import lombok.Getter; -/** - * Representation of instructions understandable by virtual machine. - */ +/** Representation of instructions understandable by virtual machine. */ @AllArgsConstructor @Getter public enum Instruction { - - LITERAL(1), // e.g. "LITERAL 0", push 0 to stack - SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health - SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom - SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility - PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound + LITERAL(1), // e.g. "LITERAL 0", push 0 to stack + SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health + SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom + SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility + PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles - GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health - GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility - GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom - ADD(10), // e.g. "ADD", pop 2 values, push their sum - DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division + GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health + GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility + GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom + ADD(10), // e.g. "ADD", pop 2 values, push their sum + DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division private final int intValue; diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java index 2133296ed155..7f835d402f15 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/VirtualMachine.java @@ -29,9 +29,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * Implementation of virtual machine. - */ +/** Implementation of virtual machine. */ @Getter @Slf4j public class VirtualMachine { @@ -40,19 +38,13 @@ public class VirtualMachine { private final Wizard[] wizards = new Wizard[2]; - /** - * No-args constructor. - */ + /** No-args constructor. */ public VirtualMachine() { - wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), - 0, 0); - wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), - 0, 0); + wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), 0, 0); + wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), 0, 0); } - /** - * Constructor taking the wizards as arguments. - */ + /** Constructor taking the wizards as arguments. */ public VirtualMachine(Wizard wizard1, Wizard wizard2) { wizards[0] = wizard1; wizards[1] = wizard2; @@ -112,7 +104,6 @@ public void execute(int[] bytecode) { case PLAY_SOUND -> { var wizard = stack.pop(); getWizards()[wizard].playSound(); - } case SPAWN_PARTICLES -> { var wizard = stack.pop(); diff --git a/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java b/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java index 6501ac9a38f5..d45a2aa55787 100644 --- a/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java +++ b/bytecode/src/main/java/com/iluwatar/bytecode/util/InstructionConverterUtil.java @@ -26,9 +26,7 @@ import com.iluwatar.bytecode.Instruction; -/** - * Utility class used for instruction validation and conversion. - */ +/** Utility class used for instruction validation and conversion. */ public class InstructionConverterUtil { /** * Converts instructions represented as String. diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java index c56e849399b0..72d00eb34fb3 100644 --- a/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java +++ b/bytecode/src/test/java/com/iluwatar/bytecode/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.bytecode; -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. + *

    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[] {})); } } diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java index b6ad5dfe6925..1d9a5539f51b 100644 --- a/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java +++ b/bytecode/src/test/java/com/iluwatar/bytecode/VirtualMachineTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * Test for {@link VirtualMachine} - */ +/** Test for {@link VirtualMachine} */ class VirtualMachineTest { @Test @@ -55,7 +53,7 @@ void testSetHealth() { bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // health amount + bytecode[3] = 50; // health amount bytecode[4] = SET_HEALTH.getIntValue(); var vm = new VirtualMachine(); @@ -71,7 +69,7 @@ void testSetAgility() { bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // agility amount + bytecode[3] = 50; // agility amount bytecode[4] = SET_AGILITY.getIntValue(); var vm = new VirtualMachine(); @@ -87,7 +85,7 @@ void testSetWisdom() { bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // wisdom amount + bytecode[3] = 50; // wisdom amount bytecode[4] = SET_WISDOM.getIntValue(); var vm = new VirtualMachine(); @@ -103,7 +101,7 @@ void testGetHealth() { bytecode[0] = LITERAL.getIntValue(); bytecode[1] = wizardNumber; bytecode[2] = LITERAL.getIntValue(); - bytecode[3] = 50; // health amount + bytecode[3] = 50; // health amount bytecode[4] = SET_HEALTH.getIntValue(); bytecode[5] = LITERAL.getIntValue(); bytecode[6] = wizardNumber; diff --git a/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java b/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java index 494fc9a2e8c4..9dadba1eaf21 100644 --- a/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java +++ b/bytecode/src/test/java/com/iluwatar/bytecode/util/InstructionConverterUtilTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * Test for {@link InstructionConverterUtil} - */ +/** Test for {@link InstructionConverterUtil} */ class InstructionConverterUtilTest { @Test @@ -44,8 +42,9 @@ void testEmptyInstruction() { @Test void testInstructions() { - var instructions = "LITERAL 35 SET_HEALTH SET_WISDOM SET_AGILITY PLAY_SOUND" - + " SPAWN_PARTICLES GET_HEALTH ADD DIVIDE"; + var instructions = + "LITERAL 35 SET_HEALTH SET_WISDOM SET_AGILITY PLAY_SOUND" + + " SPAWN_PARTICLES GET_HEALTH ADD DIVIDE"; var bytecode = InstructionConverterUtil.convertToByteCode(instructions); @@ -61,5 +60,4 @@ void testInstructions() { Assertions.assertEquals(Instruction.ADD.getIntValue(), bytecode[8]); Assertions.assertEquals(Instruction.DIVIDE.getIntValue(), bytecode[9]); } - } diff --git a/caching/README.md b/caching/README.md index b923da87b3da..2e9c3761ef8d 100644 --- a/caching/README.md +++ b/caching/README.md @@ -34,6 +34,10 @@ Wikipedia says > In computing, a cache is a hardware or software component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere. A cache hit occurs when the requested data can be found in a cache, while a cache miss occurs when it cannot. Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading from a slower data store; thus, the more requests that can be served from the cache, the faster the system performs. +Flowchart + +![Caching flowchart](./etc/caching-flowchart.png) + ## Programmatic Example of Caching Pattern in Java In this programmatic example, we demonstrate different Java caching strategies, including write-through, write-around, and write-behind, using a user account management system. diff --git a/caching/etc/caching-flowchart.png b/caching/etc/caching-flowchart.png new file mode 100644 index 000000000000..8068392cd9bd Binary files /dev/null and b/caching/etc/caching-flowchart.png differ diff --git a/caching/etc/caching.urm.puml b/caching/etc/caching.urm.puml index a9dae801eb20..f6f2e4732005 100644 --- a/caching/etc/caching.urm.puml +++ b/caching/etc/caching.urm.puml @@ -13,7 +13,7 @@ package com.iluwatar.caching { - LOGGER : Logger {static} + App() + main(args : String[]) {static} - + useCacheAsideStategy() + + useCacheAsideStrategy() + useReadAndWriteThroughStrategy() + useReadThroughAndWriteAroundStrategy() + useReadThroughAndWriteBehindStrategy() @@ -116,4 +116,4 @@ Node --> "-previous" Node AppManager --> "-cachingPolicy" CachingPolicy Node --> "-userAccount" UserAccount CacheStore --> "-cache" LruCache -@enduml \ No newline at end of file +@enduml diff --git a/caching/pom.xml b/caching/pom.xml index d7470b5e46d8..3ffce74af493 100644 --- a/caching/pom.xml +++ b/caching/pom.xml @@ -34,6 +34,14 @@ caching + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/caching/src/main/java/com/iluwatar/caching/App.java b/caching/src/main/java/com/iluwatar/caching/App.java index 3adf7e681f9a..8d6af6c090d8 100644 --- a/caching/src/main/java/com/iluwatar/caching/App.java +++ b/caching/src/main/java/com/iluwatar/caching/App.java @@ -29,59 +29,44 @@ import lombok.extern.slf4j.Slf4j; /** - * The Caching pattern describes how to avoid expensive re-acquisition of - * resources by not releasing the resources immediately after their use. - * The resources retain their identity, are kept in some fast-access storage, - * and are re-used to avoid having to acquire them again. There are four main - * caching strategies/techniques in this pattern; each with their own pros and - * cons. They are write-through which writes data to the cache and - * DB in a single transaction, write-around which writes data - * immediately into the DB instead of the cache, write-behind - * which writes data into the cache initially whilst the data is only - * written into the DB when the cache is full, and cache-aside - * which pushes the responsibility of keeping the data synchronized in both - * data sources to the application itself. The read-through - * strategy is also included in the mentioned four strategies -- - * returns data from the cache to the caller if it exists else - * queries from DB and stores it into the cache for future use. These strategies - * determine when the data in the cache should be written back to the backing - * store (i.e. Database) and help keep both data sources - * synchronized/up-to-date. This pattern can improve performance and also helps - * to maintainconsistency between data held in the cache and the data in - * the underlying data store. + * The Caching pattern describes how to avoid expensive re-acquisition of resources by not releasing + * the resources immediately after their use. The resources retain their identity, are kept in some + * fast-access storage, and are re-used to avoid having to acquire them again. There are four main + * caching strategies/techniques in this pattern; each with their own pros and cons. They are + * write-through which writes data to the cache and DB in a single transaction, + * write-around which writes data immediately into the DB instead of the cache, + * write-behind which writes data into the cache initially whilst the data is only written + * into the DB when the cache is full, and cache-aside which pushes the responsibility + * of keeping the data synchronized in both data sources to the application itself. The + * read-through strategy is also included in the mentioned four strategies -- returns data + * from the cache to the caller if it exists else queries from DB and stores it into + * the cache for future use. These strategies determine when the data in the cache should be written + * back to the backing store (i.e. Database) and help keep both data sources + * synchronized/up-to-date. This pattern can improve performance and also helps to + * maintainconsistency between data held in the cache and the data in the underlying data store. * - *

    In this example, the user account ({@link UserAccount}) entity is used - * as the underlying application data. The cache itself is implemented as an - * internal (Java) data structure. It adopts a Least-Recently-Used (LRU) - * strategy for evicting data from itself when its full. The four - * strategies are individually tested. The testing of the cache is restricted - * towards saving and querying of user accounts from the - * underlying data store( {@link DbManager}). The main class ( {@link App} - * is not aware of the underlying mechanics of the application - * (i.e. save and query) and whether the data is coming from the cache or the - * DB (i.e. separation of concern). The AppManager ({@link AppManager}) handles - * the transaction of data to-and-from the underlying data store (depending on - * the preferred caching policy/strategy). - *

    - * {@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> - * DBManager} - *

    + *

    In this example, the user account ({@link UserAccount}) entity is used as the underlying + * application data. The cache itself is implemented as an internal (Java) data structure. It adopts + * a Least-Recently-Used (LRU) strategy for evicting data from itself when its full. The four + * strategies are individually tested. The testing of the cache is restricted towards saving and + * querying of user accounts from the underlying data store( {@link DbManager}). The main class ( + * {@link App} is not aware of the underlying mechanics of the application (i.e. save and query) and + * whether the data is coming from the cache or the DB (i.e. separation of concern). The AppManager + * ({@link AppManager}) handles the transaction of data to-and-from the underlying data store + * (depending on the preferred caching policy/strategy). * - *

    - * There are 2 ways to launch the application. - * - to use "in Memory" database. - * - to use the MongoDb as a database + *

    {@literal App --> AppManager --> CacheStore/LRUCache/CachingPolicy --> DBManager} * - * To run the application with "in Memory" database, just launch it without parameters - * Example: 'java -jar app.jar' + *

    There are 2 ways to launch the application. - to use "in Memory" database. - to use the + * MongoDb as a database * - * To run the application with MongoDb you need to be installed the MongoDb - * in your system, or to launch it in the docker container. - * You may launch docker container from the root of current module with command: - * 'docker-compose up' - * Then you can start the application with parameter --mongo - * Example: 'java -jar app.jar --mongo' - *

    + *

    To run the application with "in Memory" database, just launch it without parameters Example: + * 'java -jar app.jar' + * + *

    To run the application with MongoDb you need to be installed the MongoDb in your system, or to + * launch it in the docker container. You may launch docker container from the root of current + * module with command: 'docker-compose up' Then you can start the application with parameter + * --mongo Example: 'java -jar app.jar --mongo' * * @see CacheStore * @see LruCache @@ -89,13 +74,10 @@ */ @Slf4j public class App { - /** - * Constant parameter name to use mongoDB. - */ + /** Constant parameter name to use mongoDB. */ private static final String USE_MONGO_DB = "--mongo"; - /** - * Application manager. - */ + + /** Application manager. */ private final AppManager appManager; /** @@ -133,7 +115,7 @@ public static void main(final String[] args) { LOGGER.info(splitLine); app.useReadThroughAndWriteBehindStrategy(); LOGGER.info(splitLine); - app.useCacheAsideStategy(); + app.useCacheAsideStrategy(); LOGGER.info(splitLine); } @@ -152,9 +134,7 @@ private static boolean isDbMongo(final String[] args) { return false; } - /** - * Read-through and write-through. - */ + /** Read-through and write-through. */ public void useReadAndWriteThroughStrategy() { LOGGER.info("# CachingPolicy.THROUGH"); appManager.initCachingPolicy(CachingPolicy.THROUGH); @@ -167,9 +147,7 @@ public void useReadAndWriteThroughStrategy() { appManager.find("001"); } - /** - * Read-through and write-around. - */ + /** Read-through and write-around. */ public void useReadThroughAndWriteAroundStrategy() { LOGGER.info("# CachingPolicy.AROUND"); appManager.initCachingPolicy(CachingPolicy.AROUND); @@ -189,22 +167,14 @@ public void useReadThroughAndWriteAroundStrategy() { appManager.find("002"); } - /** - * Read-through and write-behind. - */ + /** Read-through and write-behind. */ public void useReadThroughAndWriteBehindStrategy() { LOGGER.info("# CachingPolicy.BEHIND"); appManager.initCachingPolicy(CachingPolicy.BEHIND); - var userAccount3 = new UserAccount("003", - "Adam", - "He likes food."); - var userAccount4 = new UserAccount("004", - "Rita", - "She hates cats."); - var userAccount5 = new UserAccount("005", - "Isaac", - "He is allergic to mustard."); + var userAccount3 = new UserAccount("003", "Adam", "He likes food."); + var userAccount4 = new UserAccount("004", "Rita", "She hates cats."); + var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard."); appManager.save(userAccount3); appManager.save(userAccount4); @@ -212,32 +182,22 @@ public void useReadThroughAndWriteBehindStrategy() { LOGGER.info(appManager.printCacheContent()); appManager.find("003"); LOGGER.info(appManager.printCacheContent()); - UserAccount userAccount6 = new UserAccount("006", - "Yasha", - "She is an only child."); + UserAccount userAccount6 = new UserAccount("006", "Yasha", "She is an only child."); appManager.save(userAccount6); LOGGER.info(appManager.printCacheContent()); appManager.find("004"); LOGGER.info(appManager.printCacheContent()); } - /** - * Cache-Aside. - */ - public void useCacheAsideStategy() { + /** Cache-Aside. */ + public void useCacheAsideStrategy() { LOGGER.info("# CachingPolicy.ASIDE"); appManager.initCachingPolicy(CachingPolicy.ASIDE); LOGGER.info(appManager.printCacheContent()); - var userAccount3 = new UserAccount("003", - "Adam", - "He likes food."); - var userAccount4 = new UserAccount("004", - "Rita", - "She hates cats."); - var userAccount5 = new UserAccount("005", - "Isaac", - "He is allergic to mustard."); + var userAccount3 = new UserAccount("003", "Adam", "He likes food."); + var userAccount4 = new UserAccount("004", "Rita", "She hates cats."); + var userAccount5 = new UserAccount("005", "Isaac", "He is allergic to mustard."); appManager.save(userAccount3); appManager.save(userAccount4); appManager.save(userAccount5); diff --git a/caching/src/main/java/com/iluwatar/caching/AppManager.java b/caching/src/main/java/com/iluwatar/caching/AppManager.java index 3d298537521f..c1d21fea33fe 100644 --- a/caching/src/main/java/com/iluwatar/caching/AppManager.java +++ b/caching/src/main/java/com/iluwatar/caching/AppManager.java @@ -29,26 +29,21 @@ import lombok.extern.slf4j.Slf4j; /** - * AppManager helps to bridge the gap in communication between the main class - * and the application's back-end. DB connection is initialized through this - * class. The chosen caching strategy/policy is also initialized here. - * Before the cache can be used, the size of the cache has to be set. - * Depending on the chosen caching policy, AppManager will call the - * appropriate function in the CacheStore class. + * AppManager helps to bridge the gap in communication between the main class and the application's + * back-end. DB connection is initialized through this class. The chosen caching strategy/policy is + * also initialized here. Before the cache can be used, the size of the cache has to be set. + * Depending on the chosen caching policy, AppManager will call the appropriate function in the + * CacheStore class. */ @Slf4j public class AppManager { - /** - * Caching Policy. - */ + /** Caching Policy. */ private CachingPolicy cachingPolicy; - /** - * Database Manager. - */ + + /** Database Manager. */ private final DbManager dbManager; - /** - * Cache Store. - */ + + /** Cache Store. */ private final CacheStore cacheStore; /** @@ -62,9 +57,9 @@ public AppManager(final DbManager newDbManager) { } /** - * Developer/Tester is able to choose whether the application should use - * MongoDB as its underlying data storage or a simple Java data structure - * to (temporarily) store the data/objects during runtime. + * Developer/Tester is able to choose whether the application should use MongoDB as its underlying + * data storage or a simple Java data structure to (temporarily) store the data/objects during + * runtime. */ public void initDb() { dbManager.connect(); @@ -91,8 +86,7 @@ public void initCachingPolicy(final CachingPolicy policy) { */ public UserAccount find(final String userId) { LOGGER.info("Trying to find {} in cache", userId); - if (cachingPolicy == CachingPolicy.THROUGH - || cachingPolicy == CachingPolicy.AROUND) { + if (cachingPolicy == CachingPolicy.THROUGH || cachingPolicy == CachingPolicy.AROUND) { return cacheStore.readThrough(userId); } else if (cachingPolicy == CachingPolicy.BEHIND) { return cacheStore.readThroughWithWriteBackPolicy(userId); @@ -147,12 +141,12 @@ private void saveAside(final UserAccount userAccount) { */ private UserAccount findAside(final String userId) { return Optional.ofNullable(cacheStore.get(userId)) - .or(() -> { - Optional userAccount = - Optional.ofNullable(dbManager.readFromDb(userId)); + .or( + () -> { + Optional userAccount = Optional.ofNullable(dbManager.readFromDb(userId)); userAccount.ifPresent(account -> cacheStore.set(userId, account)); return userAccount; }) - .orElse(null); + .orElse(null); } } diff --git a/caching/src/main/java/com/iluwatar/caching/CacheStore.java b/caching/src/main/java/com/iluwatar/caching/CacheStore.java index 2c7184f57b3f..b26c52b22159 100644 --- a/caching/src/main/java/com/iluwatar/caching/CacheStore.java +++ b/caching/src/main/java/com/iluwatar/caching/CacheStore.java @@ -30,27 +30,21 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -/** - * The caching strategies are implemented in this class. - */ +/** The caching strategies are implemented in this class. */ @Slf4j public class CacheStore { - /** - * Cache capacity. - */ + /** Cache capacity. */ private static final int CAPACITY = 3; - /** - * Lru cache see {@link LruCache}. - */ + /** Lru cache see {@link LruCache}. */ private LruCache cache; - /** - * DbManager. - */ + + /** DbManager. */ private final DbManager dbManager; /** * Cache Store. + * * @param dataBaseManager {@link DbManager} */ public CacheStore(final DbManager dataBaseManager) { @@ -60,6 +54,7 @@ public CacheStore(final DbManager dataBaseManager) { /** * Init cache capacity. + * * @param capacity int */ public void initCapacity(final int capacity) { @@ -72,6 +67,7 @@ public void initCapacity(final int capacity) { /** * Get user account using read-through cache. + * * @param userId {@link String} * @return {@link UserAccount} */ @@ -88,6 +84,7 @@ public UserAccount readThrough(final String userId) { /** * Get user account using write-through cache. + * * @param userAccount {@link UserAccount} */ public void writeThrough(final UserAccount userAccount) { @@ -101,6 +98,7 @@ public void writeThrough(final UserAccount userAccount) { /** * Get user account using write-around cache. + * * @param userAccount {@link UserAccount} */ public void writeAround(final UserAccount userAccount) { @@ -116,6 +114,7 @@ public void writeAround(final UserAccount userAccount) { /** * Get user account using read-through cache with write-back policy. + * * @param userId {@link String} * @return {@link UserAccount} */ @@ -137,6 +136,7 @@ public UserAccount readThroughWithWriteBackPolicy(final String userId) { /** * Set user account. + * * @param userAccount {@link UserAccount} */ public void writeBehind(final UserAccount userAccount) { @@ -148,18 +148,14 @@ public void writeBehind(final UserAccount userAccount) { cache.set(userAccount.getUserId(), userAccount); } - /** - * Clears cache. - */ + /** Clears cache. */ public void clearCache() { if (cache != null) { cache.clear(); } } - /** - * Writes remaining content in the cache into the DB. - */ + /** Writes remaining content in the cache into the DB. */ public void flushCache() { LOGGER.info("# flushCache..."); Optional.ofNullable(cache) @@ -171,6 +167,7 @@ public void flushCache() { /** * Print user accounts. + * * @return {@link String} */ public String print() { @@ -184,6 +181,7 @@ public String print() { /** * Delegate to backing cache store. + * * @param userId {@link String} * @return {@link UserAccount} */ @@ -193,6 +191,7 @@ public UserAccount get(final String userId) { /** * Delegate to backing cache store. + * * @param userId {@link String} * @param userAccount {@link UserAccount} */ @@ -202,6 +201,7 @@ public void set(final String userId, final UserAccount userAccount) { /** * Delegate to backing cache store. + * * @param userId {@link String} */ public void invalidate(final String userId) { diff --git a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java index dcd5711dbf6e..0ec07ced7b8f 100644 --- a/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java +++ b/caching/src/main/java/com/iluwatar/caching/CachingPolicy.java @@ -27,31 +27,19 @@ import lombok.AllArgsConstructor; import lombok.Getter; -/** - * Enum class containing the four caching strategies implemented in the pattern. - */ +/** Enum class containing the four caching strategies implemented in the pattern. */ @AllArgsConstructor @Getter public enum CachingPolicy { - /** - * Through. - */ + /** Through. */ THROUGH("through"), - /** - * AROUND. - */ + /** AROUND. */ AROUND("around"), - /** - * BEHIND. - */ + /** BEHIND. */ BEHIND("behind"), - /** - * ASIDE. - */ + /** ASIDE. */ ASIDE("aside"); - /** - * Policy value. - */ + /** Policy value. */ private final String policy; } diff --git a/caching/src/main/java/com/iluwatar/caching/LruCache.java b/caching/src/main/java/com/iluwatar/caching/LruCache.java index ef94bf482161..9c9107de6f88 100644 --- a/caching/src/main/java/com/iluwatar/caching/LruCache.java +++ b/caching/src/main/java/com/iluwatar/caching/LruCache.java @@ -30,42 +30,33 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; - /** - * Data structure/implementation of the application's cache. The data structure - * consists of a hash table attached with a doubly linked-list. The linked-list - * helps in capturing and maintaining the LRU data in the cache. When a data is - * queried (from the cache), added (to the cache), or updated, the data is - * moved to the front of the list to depict itself as the most-recently-used - * data. The LRU data is always at the end of the list. + * Data structure/implementation of the application's cache. The data structure consists of a hash + * table attached with a doubly linked-list. The linked-list helps in capturing and maintaining the + * LRU data in the cache. When a data is queried (from the cache), added (to the cache), or updated, + * the data is moved to the front of the list to depict itself as the most-recently-used data. The + * LRU data is always at the end of the list. */ @Slf4j public class LruCache { - /** - * Static class Node. - */ + /** Static class Node. */ static class Node { - /** - * user id. - */ + /** user id. */ private final String userId; - /** - * User Account. - */ + + /** User Account. */ private UserAccount userAccount; - /** - * previous. - */ + + /** previous. */ private Node previous; - /** - * next. - */ + + /** next. */ private Node next; /** * Node definition. * - * @param id String + * @param id String * @param account {@link UserAccount} */ Node(final String id, final UserAccount account) { @@ -74,21 +65,16 @@ static class Node { } } - /** - * Capacity of Cache. - */ + /** Capacity of Cache. */ private int capacity; - /** - * Cache {@link HashMap}. - */ + + /** Cache {@link HashMap}. */ private Map cache = new HashMap<>(); - /** - * Head. - */ + + /** Head. */ private Node head; - /** - * End. - */ + + /** End. */ private Node end; /** @@ -155,7 +141,7 @@ public void setHead(final Node node) { * Set user account. * * @param userAccount {@link UserAccount} - * @param userId {@link String} + * @param userId {@link String} */ public void set(final String userId, final UserAccount userAccount) { if (cache.containsKey(userId)) { @@ -195,14 +181,14 @@ public boolean contains(final String userId) { public void invalidate(final String userId) { var toBeRemoved = cache.remove(userId); if (toBeRemoved != null) { - LOGGER.info("# {} has been updated! " - + "Removing older version from cache...", userId); + LOGGER.info("# {} has been updated! " + "Removing older version from cache...", userId); remove(toBeRemoved); } } /** * Check if the cache is full. + * * @return boolean */ public boolean isFull() { @@ -218,9 +204,7 @@ public UserAccount getLruData() { return end.userAccount; } - /** - * Clear cache. - */ + /** Clear cache. */ public void clear() { head = null; end = null; diff --git a/caching/src/main/java/com/iluwatar/caching/UserAccount.java b/caching/src/main/java/com/iluwatar/caching/UserAccount.java index 73100c0a6c24..561c942ac781 100644 --- a/caching/src/main/java/com/iluwatar/caching/UserAccount.java +++ b/caching/src/main/java/com/iluwatar/caching/UserAccount.java @@ -29,24 +29,18 @@ import lombok.EqualsAndHashCode; import lombok.ToString; -/** - * Entity class (stored in cache and DB) used in the application. - */ +/** Entity class (stored in cache and DB) used in the application. */ @Data @AllArgsConstructor @ToString @EqualsAndHashCode public class UserAccount { - /** - * User Id. - */ + /** User Id. */ private String userId; - /** - * User Name. - */ + + /** User Name. */ private String userName; - /** - * Additional Info. - */ + + /** Additional Info. */ private String additionalInfo; } diff --git a/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java b/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java index 908fca8ea0e3..5e8fc415df4c 100644 --- a/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java +++ b/caching/src/main/java/com/iluwatar/caching/constants/CachingConstants.java @@ -24,30 +24,20 @@ */ package com.iluwatar.caching.constants; -/** - * Constant class for defining constants. - */ +/** Constant class for defining constants. */ public final class CachingConstants { - /** - * User Account. - */ + /** User Account. */ public static final String USER_ACCOUNT = "user_accounts"; - /** - * User ID. - */ + + /** User ID. */ public static final String USER_ID = "userID"; - /** - * User Name. - */ + + /** User Name. */ public static final String USER_NAME = "userName"; - /** - * Additional Info. - */ + + /** Additional Info. */ public static final String ADD_INFO = "additionalInfo"; - /** - * Constructor. - */ - private CachingConstants() { - } + /** Constructor. */ + private CachingConstants() {} } diff --git a/caching/src/main/java/com/iluwatar/caching/constants/package-info.java b/caching/src/main/java/com/iluwatar/caching/constants/package-info.java index 9356e7e7b6c3..b94476cbabeb 100644 --- a/caching/src/main/java/com/iluwatar/caching/constants/package-info.java +++ b/caching/src/main/java/com/iluwatar/caching/constants/package-info.java @@ -22,7 +22,5 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -/** - * Constants. - */ +/** Constants. */ package com.iluwatar.caching.constants; diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java index 16b60fc82d63..14b98a66b52b 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/DbManager.java +++ b/caching/src/main/java/com/iluwatar/caching/database/DbManager.java @@ -27,19 +27,15 @@ import com.iluwatar.caching.UserAccount; /** - *

    DBManager handles the communication with the underlying data store i.e. - * Database. It contains the implemented methods for querying, inserting, - * and updating data. MongoDB was used as the database for the application.

    + * DBManager handles the communication with the underlying data store i.e. Database. It contains the + * implemented methods for querying, inserting, and updating data. MongoDB was used as the database + * for the application. */ public interface DbManager { - /** - * Connect to DB. - */ + /** Connect to DB. */ void connect(); - /** - * Disconnect from DB. - */ + /** Disconnect from DB. */ void disconnect(); /** diff --git a/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java index ee7a4a04b936..92031b7c95b0 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java +++ b/caching/src/main/java/com/iluwatar/caching/database/DbManagerFactory.java @@ -24,15 +24,10 @@ */ package com.iluwatar.caching.database; -/** - * Creates the database connection according the input parameter. - */ +/** Creates the database connection according the input parameter. */ public final class DbManagerFactory { - /** - * Private constructor. - */ - private DbManagerFactory() { - } + /** Private constructor. */ + private DbManagerFactory() {} /** * Init database. diff --git a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java index f2fa696cca5e..e47eef55cd8c 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java +++ b/caching/src/main/java/com/iluwatar/caching/database/MongoDb.java @@ -40,10 +40,7 @@ import lombok.extern.slf4j.Slf4j; import org.bson.Document; -/** - * Implementation of DatabaseManager. - * implements base methods to work with MongoDb. - */ +/** Implementation of DatabaseManager. implements base methods to work with MongoDb. */ @Slf4j public class MongoDb implements DbManager { private static final String DATABASE_NAME = "admin"; @@ -56,14 +53,11 @@ void setDb(MongoDatabase db) { this.db = db; } - /** - * Connect to Db. Check th connection - */ + /** Connect to Db. Check th connection */ @Override public void connect() { - MongoCredential mongoCredential = MongoCredential.createCredential(MONGO_USER, - DATABASE_NAME, - MONGO_PASSWORD.toCharArray()); + MongoCredential mongoCredential = + MongoCredential.createCredential(MONGO_USER, DATABASE_NAME, MONGO_PASSWORD.toCharArray()); MongoClientOptions options = MongoClientOptions.builder().build(); client = new MongoClient(new ServerAddress(), mongoCredential, options); db = client.getDatabase(DATABASE_NAME); @@ -82,9 +76,8 @@ public void disconnect() { */ @Override public UserAccount readFromDb(final String userId) { - var iterable = db - .getCollection(CachingConstants.USER_ACCOUNT) - .find(new Document(USER_ID, userId)); + var iterable = + db.getCollection(CachingConstants.USER_ACCOUNT).find(new Document(USER_ID, userId)); if (iterable.first() == null) { return null; } @@ -106,11 +99,11 @@ public UserAccount readFromDb(final String userId) { */ @Override public UserAccount writeToDb(final UserAccount userAccount) { - db.getCollection(USER_ACCOUNT).insertOne( + db.getCollection(USER_ACCOUNT) + .insertOne( new Document(USER_ID, userAccount.getUserId()) - .append(USER_NAME, userAccount.getUserName()) - .append(ADD_INFO, userAccount.getAdditionalInfo()) - ); + .append(USER_NAME, userAccount.getUserName()) + .append(ADD_INFO, userAccount.getAdditionalInfo())); return userAccount; } @@ -123,10 +116,10 @@ public UserAccount writeToDb(final UserAccount userAccount) { @Override public UserAccount updateDb(final UserAccount userAccount) { Document id = new Document(USER_ID, userAccount.getUserId()); - Document dataSet = new Document(USER_NAME, userAccount.getUserName()) + Document dataSet = + new Document(USER_NAME, userAccount.getUserName()) .append(ADD_INFO, userAccount.getAdditionalInfo()); - db.getCollection(CachingConstants.USER_ACCOUNT) - .updateOne(id, new Document("$set", dataSet)); + db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(id, new Document("$set", dataSet)); return userAccount; } @@ -141,15 +134,15 @@ public UserAccount upsertDb(final UserAccount userAccount) { String userId = userAccount.getUserId(); String userName = userAccount.getUserName(); String additionalInfo = userAccount.getAdditionalInfo(); - db.getCollection(CachingConstants.USER_ACCOUNT).updateOne( + db.getCollection(CachingConstants.USER_ACCOUNT) + .updateOne( new Document(USER_ID, userId), - new Document("$set", - new Document(USER_ID, userId) - .append(USER_NAME, userName) - .append(ADD_INFO, additionalInfo) - ), - new UpdateOptions().upsert(true) - ); + new Document( + "$set", + new Document(USER_ID, userId) + .append(USER_NAME, userName) + .append(ADD_INFO, additionalInfo)), + new UpdateOptions().upsert(true)); return userAccount; } } diff --git a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java index 6155e1d69ee6..6040ca174a21 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java +++ b/caching/src/main/java/com/iluwatar/caching/database/VirtualDb.java @@ -28,19 +28,12 @@ import java.util.HashMap; import java.util.Map; -/** - * Implementation of DatabaseManager. - * implements base methods to work with hashMap as database. - */ +/** Implementation of DatabaseManager. implements base methods to work with hashMap as database. */ public class VirtualDb implements DbManager { - /** - * Virtual DataBase. - */ + /** Virtual DataBase. */ private Map db; - /** - * Creates new HashMap. - */ + /** Creates new HashMap. */ @Override public void connect() { db = new HashMap<>(); diff --git a/caching/src/main/java/com/iluwatar/caching/database/package-info.java b/caching/src/main/java/com/iluwatar/caching/database/package-info.java index 535771a7d275..631cb4c584cd 100644 --- a/caching/src/main/java/com/iluwatar/caching/database/package-info.java +++ b/caching/src/main/java/com/iluwatar/caching/database/package-info.java @@ -22,7 +22,5 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -/** - * Database classes. - */ +/** Database classes. */ package com.iluwatar.caching.database; diff --git a/caching/src/test/java/com/iluwatar/caching/AppTest.java b/caching/src/test/java/com/iluwatar/caching/AppTest.java index 510b3a256f5f..35e01edbc37e 100644 --- a/caching/src/test/java/com/iluwatar/caching/AppTest.java +++ b/caching/src/test/java/com/iluwatar/caching/AppTest.java @@ -24,23 +24,20 @@ */ package com.iluwatar.caching; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Caching example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Caching 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. + * + *

    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[] {})); } } diff --git a/caching/src/test/java/com/iluwatar/caching/CachingTest.java b/caching/src/test/java/com/iluwatar/caching/CachingTest.java index 0bcb83897d59..d17cff5bd2ef 100644 --- a/caching/src/test/java/com/iluwatar/caching/CachingTest.java +++ b/caching/src/test/java/com/iluwatar/caching/CachingTest.java @@ -24,20 +24,16 @@ */ package com.iluwatar.caching; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -/** - * Application test - */ +/** Application test */ class CachingTest { private App app; - /** - * Setup of application test includes: initializing DB connection and cache size/capacity. - */ + /** Setup of application test includes: initializing DB connection and cache size/capacity. */ @BeforeEach void setUp() { // VirtualDB (instead of MongoDB) was used in running the JUnit tests @@ -68,6 +64,6 @@ void testReadThroughAndWriteBehindStrategy() { @Test void testCacheAsideStrategy() { assertNotNull(app); - app.useCacheAsideStategy(); + app.useCacheAsideStrategy(); } } diff --git a/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java b/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java index 5cb130a34e33..87cc1ed6f587 100644 --- a/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java +++ b/caching/src/test/java/com/iluwatar/caching/database/MongoDbTest.java @@ -24,6 +24,13 @@ */ package com.iluwatar.caching.database; +import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO; +import static com.iluwatar.caching.constants.CachingConstants.USER_ID; +import static com.iluwatar.caching.constants.CachingConstants.USER_NAME; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + import com.iluwatar.caching.UserAccount; import com.iluwatar.caching.constants.CachingConstants; import com.mongodb.client.FindIterable; @@ -34,20 +41,12 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; -import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO; -import static com.iluwatar.caching.constants.CachingConstants.USER_ID; -import static com.iluwatar.caching.constants.CachingConstants.USER_NAME; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; - class MongoDbTest { private static final String ID = "123"; private static final String NAME = "Some user"; private static final String ADDITIONAL_INFO = "Some app Info"; - @Mock - MongoDatabase db; + @Mock MongoDatabase db; private MongoDb mongoDb = new MongoDb(); private UserAccount userAccount; @@ -66,9 +65,8 @@ void connect() { @Test void readFromDb() { - Document document = new Document(USER_ID, ID) - .append(USER_NAME, NAME) - .append(ADD_INFO, ADDITIONAL_INFO); + Document document = + new Document(USER_ID, ID).append(USER_NAME, NAME).append(ADD_INFO, ADDITIONAL_INFO); MongoCollection mongoCollection = mock(MongoCollection.class); when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection); @@ -77,27 +75,36 @@ void readFromDb() { when(findIterable.first()).thenReturn(document); - assertEquals(mongoDb.readFromDb(ID),userAccount); + assertEquals(mongoDb.readFromDb(ID), userAccount); } @Test void writeToDb() { MongoCollection mongoCollection = mock(MongoCollection.class); when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection); - assertDoesNotThrow(()-> {mongoDb.writeToDb(userAccount);}); + assertDoesNotThrow( + () -> { + mongoDb.writeToDb(userAccount); + }); } @Test void updateDb() { MongoCollection mongoCollection = mock(MongoCollection.class); when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection); - assertDoesNotThrow(()-> {mongoDb.updateDb(userAccount);}); + assertDoesNotThrow( + () -> { + mongoDb.updateDb(userAccount); + }); } @Test void upsertDb() { MongoCollection mongoCollection = mock(MongoCollection.class); when(db.getCollection(CachingConstants.USER_ACCOUNT)).thenReturn(mongoCollection); - assertDoesNotThrow(()-> {mongoDb.upsertDb(userAccount);}); + assertDoesNotThrow( + () -> { + mongoDb.upsertDb(userAccount); + }); } -} \ No newline at end of file +} diff --git a/callback/README.md b/callback/README.md index 1c90cdcea5f2..923132ac5b6a 100644 --- a/callback/README.md +++ b/callback/README.md @@ -37,6 +37,10 @@ Wikipedia says > In computer programming, a callback, also known as a "call-after" function, is any executable code that is passed as an argument to other code; that other code is expected to call back (execute) the argument at a given time. +Sequence diagram + +![Callback sequence diagram](./etc/callback-sequence-diagram.png) + ## Programmatic Example of Callback Pattern in Java We need to be notified after the executing task has finished. We pass a callback method for the executor and wait for it to call back on us. diff --git a/callback/etc/callback-sequence-diagram.png b/callback/etc/callback-sequence-diagram.png new file mode 100644 index 000000000000..5922734d8fd2 Binary files /dev/null and b/callback/etc/callback-sequence-diagram.png differ diff --git a/callback/pom.xml b/callback/pom.xml index cdae8e87044e..772615f457f9 100644 --- a/callback/pom.xml +++ b/callback/pom.xml @@ -34,6 +34,14 @@ callback + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/callback/src/main/java/com/iluwatar/callback/App.java b/callback/src/main/java/com/iluwatar/callback/App.java index a574126c4cc9..7b630f8da247 100644 --- a/callback/src/main/java/com/iluwatar/callback/App.java +++ b/callback/src/main/java/com/iluwatar/callback/App.java @@ -34,12 +34,9 @@ @Slf4j public final class App { - private App() { - } + private App() {} - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(final String[] args) { var task = new SimpleTask(); task.executeWith(() -> LOGGER.info("I'm done now.")); diff --git a/callback/src/main/java/com/iluwatar/callback/Callback.java b/callback/src/main/java/com/iluwatar/callback/Callback.java index 14de46b21386..7b75b5c71077 100644 --- a/callback/src/main/java/com/iluwatar/callback/Callback.java +++ b/callback/src/main/java/com/iluwatar/callback/Callback.java @@ -24,9 +24,7 @@ */ package com.iluwatar.callback; -/** - * Callback interface. - */ +/** Callback interface. */ public interface Callback { void call(); diff --git a/callback/src/main/java/com/iluwatar/callback/SimpleTask.java b/callback/src/main/java/com/iluwatar/callback/SimpleTask.java index a7ac0a9394e5..bbf060a6fc9f 100644 --- a/callback/src/main/java/com/iluwatar/callback/SimpleTask.java +++ b/callback/src/main/java/com/iluwatar/callback/SimpleTask.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Implementation of task that need to be executed. - */ +/** Implementation of task that need to be executed. */ @Slf4j public final class SimpleTask extends Task { diff --git a/callback/src/main/java/com/iluwatar/callback/Task.java b/callback/src/main/java/com/iluwatar/callback/Task.java index 30481f747cde..d69697454dff 100644 --- a/callback/src/main/java/com/iluwatar/callback/Task.java +++ b/callback/src/main/java/com/iluwatar/callback/Task.java @@ -26,14 +26,10 @@ import java.util.Optional; -/** - * Template-method class for callback hook execution. - */ +/** Template-method class for callback hook execution. */ public abstract class Task { - /** - * Execute with callback. - */ + /** Execute with callback. */ final void executeWith(Callback callback) { execute(); Optional.ofNullable(callback).ifPresent(Callback::call); diff --git a/callback/src/test/java/com/iluwatar/callback/AppTest.java b/callback/src/test/java/com/iluwatar/callback/AppTest.java index 26c5df95f698..ca0e93072f0f 100644 --- a/callback/src/test/java/com/iluwatar/callback/AppTest.java +++ b/callback/src/test/java/com/iluwatar/callback/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.callback; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Callback example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Callback 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. + *

    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[] {})); } } diff --git a/callback/src/test/java/com/iluwatar/callback/CallbackTest.java b/callback/src/test/java/com/iluwatar/callback/CallbackTest.java index d8dab98e676d..99939d491f4e 100644 --- a/callback/src/test/java/com/iluwatar/callback/CallbackTest.java +++ b/callback/src/test/java/com/iluwatar/callback/CallbackTest.java @@ -31,8 +31,8 @@ /** * Add a field as a counter. Every time the callback method is called increment this field. Unit * test checks that the field is being incremented. - *

    - * Could be done with mock objects as well where the call method call is verified. + * + *

    Could be done with mock objects as well where the call method call is verified. */ class CallbackTest { @@ -53,6 +53,5 @@ void test() { task.executeWith(callback); assertEquals(Integer.valueOf(2), callingCount, "Callback called twice"); - } } diff --git a/chain-of-responsibility/README.md b/chain-of-responsibility/README.md index f8c926973f67..454147dcd5d3 100644 --- a/chain-of-responsibility/README.md +++ b/chain-of-responsibility/README.md @@ -35,6 +35,10 @@ Wikipedia says > In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. +Flowchart + +![Chain of Responsibility flowchart](./etc/chain-of-responsibility-flowchart.png) + ## Programmatic Example of Chain of Responsibility Pattern In this Java example, the Orc King gives orders which are processed by a chain of command representing the Chain of Responsibility pattern. Learn how to implement this design pattern in Java with the following code snippet. @@ -159,10 +163,6 @@ Orc officer handling request "torture prisoner" Orc soldier handling request "collect tax" ``` -## Chain of Responsibility Pattern Class Diagram - -![Chain of Responsibility](./etc/chain-of-responsibility.urm.png "Chain of Responsibility class diagram") - ## When to Use the Chain of Responsibility Pattern in Java Use Chain of Responsibility when diff --git a/chain-of-responsibility/etc/chain-of-responsibility-flowchart.png b/chain-of-responsibility/etc/chain-of-responsibility-flowchart.png new file mode 100644 index 000000000000..875507374086 Binary files /dev/null and b/chain-of-responsibility/etc/chain-of-responsibility-flowchart.png differ diff --git a/chain-of-responsibility/pom.xml b/chain-of-responsibility/pom.xml index f72502045af9..e6a7fb974be7 100644 --- a/chain-of-responsibility/pom.xml +++ b/chain-of-responsibility/pom.xml @@ -34,6 +34,14 @@ chain-of-responsibility + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java index 70ea09463507..ad3749c985ca 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcCommander.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * OrcCommander. - */ +/** OrcCommander. */ @Slf4j public class OrcCommander implements RequestHandler { @Override diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java index c01aa151a5be..7500ebf3af77 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcKing.java @@ -28,9 +28,7 @@ import java.util.Comparator; import java.util.List; -/** - * OrcKing makes requests that are handled by the chain. - */ +/** OrcKing makes requests that are handled by the chain. */ public class OrcKing { private List handlers; @@ -43,12 +41,9 @@ private void buildChain() { handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier()); } - /** - * Handle request by the chain. - */ + /** Handle request by the chain. */ public void makeRequest(Request req) { - handlers - .stream() + handlers.stream() .sorted(Comparator.comparing(RequestHandler::getPriority)) .filter(handler -> handler.canHandleRequest(req)) .findFirst() diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java index 7138a001ce55..0edb579119e1 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcOfficer.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * OrcOfficer. - */ +/** OrcOfficer. */ @Slf4j public class OrcOfficer implements RequestHandler { @Override @@ -52,4 +50,3 @@ public String name() { return "Orc officer"; } } - diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java index 6650b34746ca..7398844cd0e2 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/OrcSoldier.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * OrcSoldier. - */ +/** OrcSoldier. */ @Slf4j public class OrcSoldier implements RequestHandler { @Override diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java index 05c0d88d5961..2f94422647a4 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/Request.java @@ -27,9 +27,7 @@ import java.util.Objects; import lombok.Getter; -/** - * Request. - */ +/** Request. */ @Getter public class Request { @@ -39,9 +37,7 @@ public class Request { */ private final RequestType requestType; - /** - * A description of the request. - */ + /** A description of the request. */ private final String requestDescription; /** @@ -53,7 +49,7 @@ public class Request { /** * Create a new request of the given type and accompanied description. * - * @param requestType The type of request + * @param requestType The type of request * @param requestDescription The description of the request */ public Request(final RequestType requestType, final String requestDescription) { @@ -61,9 +57,7 @@ public Request(final RequestType requestType, final String requestDescription) { this.requestDescription = Objects.requireNonNull(requestDescription); } - /** - * Mark the request as handled. - */ + /** Mark the request as handled. */ public void markHandled() { this.handled = true; } @@ -72,5 +66,4 @@ public void markHandled() { public String toString() { return getRequestDescription(); } - } diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java index ca46c44bbc2d..89eafbdcb43c 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestHandler.java @@ -24,9 +24,7 @@ */ package com.iluwatar.chain; -/** - * RequestHandler. - */ +/** RequestHandler. */ public interface RequestHandler { boolean canHandleRequest(Request req); diff --git a/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestType.java b/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestType.java index 17277cad36a2..f3cc73c97a08 100644 --- a/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestType.java +++ b/chain-of-responsibility/src/main/java/com/iluwatar/chain/RequestType.java @@ -24,13 +24,9 @@ */ package com.iluwatar.chain; -/** - * RequestType enumeration. - */ +/** RequestType enumeration. */ public enum RequestType { - DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX - } diff --git a/chain-of-responsibility/src/test/java/com/iluwatar/chain/AppTest.java b/chain-of-responsibility/src/test/java/com/iluwatar/chain/AppTest.java index 702d58dcb3fd..4d2cd68991d9 100644 --- a/chain-of-responsibility/src/test/java/com/iluwatar/chain/AppTest.java +++ b/chain-of-responsibility/src/test/java/com/iluwatar/chain/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.chain; -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. + *

    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[] {})); } } diff --git a/chain-of-responsibility/src/test/java/com/iluwatar/chain/OrcKingTest.java b/chain-of-responsibility/src/test/java/com/iluwatar/chain/OrcKingTest.java index 7bb9de901640..ec53a411787a 100644 --- a/chain-of-responsibility/src/test/java/com/iluwatar/chain/OrcKingTest.java +++ b/chain-of-responsibility/src/test/java/com/iluwatar/chain/OrcKingTest.java @@ -29,32 +29,26 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * OrcKingTest - * - */ +/** OrcKingTest */ class OrcKingTest { - /** - * All possible requests - */ - private static final List REQUESTS = List.of( - new Request(RequestType.DEFEND_CASTLE, "Don't let the barbarians enter my castle!!"), - new Request(RequestType.TORTURE_PRISONER, "Don't just stand there, tickle him!"), - new Request(RequestType.COLLECT_TAX, "Don't steal, the King hates competition ...") - ); + /** All possible requests */ + private static final List REQUESTS = + List.of( + new Request(RequestType.DEFEND_CASTLE, "Don't let the barbarians enter my castle!!"), + new Request(RequestType.TORTURE_PRISONER, "Don't just stand there, tickle him!"), + new Request(RequestType.COLLECT_TAX, "Don't steal, the King hates competition ...")); @Test void testMakeRequest() { final var king = new OrcKing(); - REQUESTS.forEach(request -> { - king.makeRequest(request); - assertTrue( - request.isHandled(), - "Expected all requests from King to be handled, but [" + request + "] was not!" - ); - }); + REQUESTS.forEach( + request -> { + king.makeRequest(request); + assertTrue( + request.isHandled(), + "Expected all requests from King to be handled, but [" + request + "] was not!"); + }); } - -} \ No newline at end of file +} diff --git a/circuit-breaker/README.md b/circuit-breaker/README.md index b8e0ff248122..99c1b7b4d398 100644 --- a/circuit-breaker/README.md +++ b/circuit-breaker/README.md @@ -33,6 +33,10 @@ Wikipedia says > Circuit breaker is a design pattern used in modern software development. It is used to detect failures and encapsulates the logic of preventing a failure from constantly recurring, during maintenance, temporary external system failure or unexpected system difficulties. +Flowchart + +![Circuit Breaker flowchart](./etc/circuit-breaker-flowchart.png) + ## Programmatic Example of Circuit Breaker Pattern in Java This Java example demonstrates how the Circuit Breaker pattern can manage remote service failures and maintain system stability. diff --git a/circuit-breaker/etc/circuit-breaker-flowchart.png b/circuit-breaker/etc/circuit-breaker-flowchart.png new file mode 100644 index 000000000000..6e0af83e0eee Binary files /dev/null and b/circuit-breaker/etc/circuit-breaker-flowchart.png differ diff --git a/circuit-breaker/pom.xml b/circuit-breaker/pom.xml index eda24a4f4d5a..5b2be0c5605a 100644 --- a/circuit-breaker/pom.xml +++ b/circuit-breaker/pom.xml @@ -34,6 +34,14 @@ circuit-breaker + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java index a29b6d769826..6011aa9d10e6 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/App.java @@ -27,33 +27,29 @@ import lombok.extern.slf4j.Slf4j; /** - *

    * The intention of the Circuit Builder pattern is to handle remote failures robustly, which is to * mean that if a service is dependent on n number of other services, and m of them fail, we should * be able to recover from that failure by ensuring that the user can still use the services that * are actually functional, and resources are not tied up by uselessly by the services which are not * working. However, we should also be able to detect when any of the m failing services become * operational again, so that we can use it - *

    - *

    - * In this example, the circuit breaker pattern is demonstrated by using three services: {@link + * + *

    In this example, the circuit breaker pattern is demonstrated by using three services: {@link * DelayedRemoteService}, {@link QuickRemoteService} and {@link MonitoringService}. The monitoring - * service is responsible for calling three services: a local service, a quick remove service - * {@link QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by - * using the circuit breaker construction we ensure that if the call to remote service is going to - * fail, we are going to save our resources and not make the function call at all, by wrapping our - * call to the remote services in the {@link DefaultCircuitBreaker} implementation object. - *

    - *

    - * This works as follows: The {@link DefaultCircuitBreaker} object can be in one of three states: - * Open, Closed and Half-Open, which represents the real world circuits. If - * the state is closed (initial), we assume everything is alright and perform the function call. + * service is responsible for calling three services: a local service, a quick remove service {@link + * QuickRemoteService} and a delayed remote service {@link DelayedRemoteService} , and by using the + * circuit breaker construction we ensure that if the call to remote service is going to fail, we + * are going to save our resources and not make the function call at all, by wrapping our call to + * the remote services in the {@link DefaultCircuitBreaker} implementation object. + * + *

    This works as follows: The {@link DefaultCircuitBreaker} object can be in one of three states: + * Open, Closed and Half-Open, which represents the real world circuits. If the + * state is closed (initial), we assume everything is alright and perform the function call. * However, every time the call fails, we note it and once it crosses a threshold, we set the state * to Open, preventing any further calls to the remote server. Then, after a certain retry period * (during which we expect thee service to recover), we make another call to the remote server and * this state is called the Half-Open state, where it stays till the service is down, and once it * recovers, it goes back to the closed state and the cycle continues. - *

    */ @Slf4j public class App { @@ -68,45 +64,45 @@ public static void main(String[] args) { var serverStartTime = System.nanoTime(); var delayedService = new DelayedRemoteService(serverStartTime, 5); - var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2, - 2000 * 1000 * 1000); + var delayedServiceCircuitBreaker = + new DefaultCircuitBreaker(delayedService, 3000, 2, 2000 * 1000 * 1000); var quickService = new QuickRemoteService(); - var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2, - 2000 * 1000 * 1000); + var quickServiceCircuitBreaker = + new DefaultCircuitBreaker(quickService, 3000, 2, 2000 * 1000 * 1000); - //Create an object of monitoring service which makes both local and remote calls - var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, - quickServiceCircuitBreaker); + // Create an object of monitoring service which makes both local and remote calls + var monitoringService = + new MonitoringService(delayedServiceCircuitBreaker, quickServiceCircuitBreaker); - //Fetch response from local resource + // Fetch response from local resource LOGGER.info(monitoringService.localResourceResponse()); - //Fetch response from delayed service 2 times, to meet the failure threshold + // Fetch response from delayed service 2 times, to meet the failure threshold LOGGER.info(monitoringService.delayedServiceResponse()); LOGGER.info(monitoringService.delayedServiceResponse()); - //Fetch current state of delayed service circuit breaker after crossing failure threshold limit - //which is OPEN now + // Fetch current state of delayed service circuit breaker after crossing failure threshold limit + // which is OPEN now LOGGER.info(delayedServiceCircuitBreaker.getState()); - //Meanwhile, the delayed service is down, fetch response from the healthy quick service + // Meanwhile, the delayed service is down, fetch response from the healthy quick service LOGGER.info(monitoringService.quickServiceResponse()); LOGGER.info(quickServiceCircuitBreaker.getState()); - //Wait for the delayed service to become responsive + // Wait for the delayed service to become responsive try { LOGGER.info("Waiting for delayed service to become responsive"); Thread.sleep(5000); } catch (InterruptedException e) { LOGGER.error("An error occurred: ", e); } - //Check the state of delayed circuit breaker, should be HALF_OPEN + // Check the state of delayed circuit breaker, should be HALF_OPEN LOGGER.info(delayedServiceCircuitBreaker.getState()); - //Fetch response from delayed service, which should be healthy by now + // Fetch response from delayed service, which should be healthy by now LOGGER.info(monitoringService.delayedServiceResponse()); - //As successful response is fetched, it should be CLOSED again. + // As successful response is fetched, it should be CLOSED again. LOGGER.info(delayedServiceCircuitBreaker.getState()); } } diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java index aaccd65b1dce..31e11751a4ea 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/CircuitBreaker.java @@ -24,9 +24,7 @@ */ package com.iluwatar.circuitbreaker; -/** - * The Circuit breaker interface. - */ +/** The Circuit breaker interface. */ public interface CircuitBreaker { // Success response. Reset everything to defaults diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java index 18febbb6b1b7..762c04d6b589 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DefaultCircuitBreaker.java @@ -45,14 +45,14 @@ public class DefaultCircuitBreaker implements CircuitBreaker { /** * Constructor to create an instance of Circuit Breaker. * - * @param timeout Timeout for the API request. Not necessary for this simple example + * @param timeout Timeout for the API request. Not necessary for this simple example * @param failureThreshold Number of failures we receive from the depended on service before - * changing state to 'OPEN' - * @param retryTimePeriod Time, in nanoseconds, period after which a new request is made to - * remote service for status check. + * changing state to 'OPEN' + * @param retryTimePeriod Time, in nanoseconds, period after which a new request is made to remote + * service for status check. */ - DefaultCircuitBreaker(RemoteService serviceToCall, long timeout, int failureThreshold, - long retryTimePeriod) { + DefaultCircuitBreaker( + RemoteService serviceToCall, long timeout, int failureThreshold, long retryTimePeriod) { this.service = serviceToCall; // We start in a closed state hoping that everything is fine this.state = State.CLOSED; @@ -61,7 +61,7 @@ public class DefaultCircuitBreaker implements CircuitBreaker { // Used to break the calls made to remote resource if it exceeds the limit this.timeout = timeout; this.retryTimePeriod = retryTimePeriod; - //An absurd amount of time in future which basically indicates the last failure never happened + // An absurd amount of time in future which basically indicates the last failure never happened this.lastFailureTime = System.nanoTime() + futureTime; this.failureCount = 0; } @@ -84,16 +84,16 @@ public void recordFailure(String response) { // Evaluate the current state based on failureThreshold, failureCount and lastFailureTime. protected void evaluateState() { - if (failureCount >= failureThreshold) { //Then something is wrong with remote service + if (failureCount >= failureThreshold) { // Then something is wrong with remote service if ((System.nanoTime() - lastFailureTime) > retryTimePeriod) { - //We have waited long enough and should try checking if service is up + // We have waited long enough and should try checking if service is up state = State.HALF_OPEN; } else { - //Service would still probably be down + // Service would still probably be down state = State.OPEN; } } else { - //Everything is working fine + // Everything is working fine state = State.CLOSED; } } @@ -140,9 +140,9 @@ public String attemptRequest() throws RemoteServiceException { } else { // Make the API request if the circuit is not OPEN try { - //In a real application, this would be run in a thread and the timeout - //parameter of the circuit breaker would be utilized to know if service - //is working. Here, we simulate that based on server response itself + // In a real application, this would be run in a thread and the timeout + // parameter of the circuit breaker would be utilized to know if service + // is working. Here, we simulate that based on server response itself var response = service.call(); // Yay!! the API responded fine. Let's reset everything. recordSuccess(); diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java index 4db2988146f4..ad87f1a6e71d 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/DelayedRemoteService.java @@ -56,12 +56,12 @@ public DelayedRemoteService() { @Override public String call() throws RemoteServiceException { var currentTime = System.nanoTime(); - //Since currentTime and serverStartTime are both in nanoseconds, we convert it to - //seconds by diving by 10e9 and ensure floating point division by multiplying it - //with 1.0 first. We then check if it is greater or less than specified delay and then - //send the reply + // Since currentTime and serverStartTime are both in nanoseconds, we convert it to + // seconds by diving by 10e9 and ensure floating point division by multiplying it + // with 1.0 first. We then check if it is greater or less than specified delay and then + // send the reply if ((currentTime - serverStartTime) * 1.0 / (1000 * 1000 * 1000) < delay) { - //Can use Thread.sleep() here to block and simulate a hung server + // Can use Thread.sleep() here to block and simulate a hung server throw new RemoteServiceException("Delayed service is down"); } return "Delayed service is working"; diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java index 33564acc16ff..3fa5cd776d8c 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/MonitoringService.java @@ -39,7 +39,7 @@ public MonitoringService(CircuitBreaker delayedService, CircuitBreaker quickServ this.quickService = quickService; } - //Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic + // Assumption: Local service won't fail, no need to wrap it in a circuit breaker logic public String localResourceResponse() { return "Local Service is working"; } @@ -69,4 +69,4 @@ public String quickServiceResponse() { return e.getMessage(); } } -} \ No newline at end of file +} diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java index 404f1c05b4c7..2367e49233ae 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/QuickRemoteService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.circuitbreaker; -/** - * A quick response remote service, that responds healthy without any delay or failure. - */ +/** A quick response remote service, that responds healthy without any delay or failure. */ public class QuickRemoteService implements RemoteService { @Override diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java index dac616f03414..ced5d3ac9d47 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteService.java @@ -30,6 +30,6 @@ */ public interface RemoteService { - //Fetch response from remote service. + // Fetch response from remote service. String call() throws RemoteServiceException; } diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java index eb033cd8ebfa..48deec756b75 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/RemoteServiceException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.circuitbreaker; -/** - * Exception thrown when {@link RemoteService} does not respond successfully. - */ +/** Exception thrown when {@link RemoteService} does not respond successfully. */ public class RemoteServiceException extends Exception { public RemoteServiceException(String message) { diff --git a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java index e59b6ce8b41f..f2668281eb57 100644 --- a/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java +++ b/circuit-breaker/src/main/java/com/iluwatar/circuitbreaker/State.java @@ -24,11 +24,9 @@ */ package com.iluwatar.circuitbreaker; -/** - * Enumeration for states the circuit breaker could be in. - */ +/** Enumeration for states the circuit breaker could be in. */ public enum State { CLOSED, OPEN, HALF_OPEN -} \ No newline at end of file +} diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java index 108695706a07..3cbeadcd13de 100644 --- a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/AppTest.java @@ -31,20 +31,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * App Test showing usage of circuit breaker. - */ +/** App Test showing usage of circuit breaker. */ class AppTest { private static final Logger LOGGER = LoggerFactory.getLogger(AppTest.class); - //Startup delay for delayed service (in seconds) + // Startup delay for delayed service (in seconds) private static final int STARTUP_DELAY = 4; - //Number of failed requests for circuit breaker to open + // Number of failed requests for circuit breaker to open private static final int FAILURE_THRESHOLD = 1; - //Time period in seconds for circuit breaker to retry service + // Time period in seconds for circuit breaker to retry service private static final int RETRY_PERIOD = 2; private MonitoringService monitoringService; @@ -62,75 +60,75 @@ class AppTest { @BeforeEach void setupCircuitBreakers() { var delayedService = new DelayedRemoteService(System.nanoTime(), STARTUP_DELAY); - //Set the circuit Breaker parameters - delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, - FAILURE_THRESHOLD, - RETRY_PERIOD * 1000 * 1000 * 1000); + // Set the circuit Breaker parameters + delayedServiceCircuitBreaker = + new DefaultCircuitBreaker( + delayedService, 3000, FAILURE_THRESHOLD, RETRY_PERIOD * 1000 * 1000 * 1000); var quickService = new QuickRemoteService(); - //Set the circuit Breaker parameters - quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, FAILURE_THRESHOLD, - RETRY_PERIOD * 1000 * 1000 * 1000); - - monitoringService = new MonitoringService(delayedServiceCircuitBreaker, - quickServiceCircuitBreaker); + // Set the circuit Breaker parameters + quickServiceCircuitBreaker = + new DefaultCircuitBreaker( + quickService, 3000, FAILURE_THRESHOLD, RETRY_PERIOD * 1000 * 1000 * 1000); + monitoringService = + new MonitoringService(delayedServiceCircuitBreaker, quickServiceCircuitBreaker); } @Test void testFailure_OpenStateTransition() { - //Calling delayed service, which will be unhealthy till 4 seconds + // Calling delayed service, which will be unhealthy till 4 seconds assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); - //As failure threshold is "1", the circuit breaker is changed to OPEN + // As failure threshold is "1", the circuit breaker is changed to OPEN assertEquals("OPEN", delayedServiceCircuitBreaker.getState()); - //As circuit state is OPEN, we expect a quick fallback response from circuit breaker. + // As circuit state is OPEN, we expect a quick fallback response from circuit breaker. assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); - //Meanwhile, the quick service is responding and the circuit state is CLOSED + // Meanwhile, the quick service is responding and the circuit state is CLOSED assertEquals("Quick Service is working", monitoringService.quickServiceResponse()); assertEquals("CLOSED", quickServiceCircuitBreaker.getState()); - } @Test void testFailure_HalfOpenStateTransition() { - //Calling delayed service, which will be unhealthy till 4 seconds + // Calling delayed service, which will be unhealthy till 4 seconds assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); - //As failure threshold is "1", the circuit breaker is changed to OPEN + // As failure threshold is "1", the circuit breaker is changed to OPEN assertEquals("OPEN", delayedServiceCircuitBreaker.getState()); - //Waiting for recovery period of 2 seconds for circuit breaker to retry service. + // Waiting for recovery period of 2 seconds for circuit breaker to retry service. try { LOGGER.info("Waiting 2s for delayed service to become responsive"); Thread.sleep(2000); } catch (InterruptedException e) { LOGGER.error("An error occurred: ", e); } - //After 2 seconds, the circuit breaker should move to "HALF_OPEN" state and retry fetching response from service again + // After 2 seconds, the circuit breaker should move to "HALF_OPEN" state and retry fetching + // response from service again assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState()); - } @Test void testRecovery_ClosedStateTransition() { - //Calling delayed service, which will be unhealthy till 4 seconds + // Calling delayed service, which will be unhealthy till 4 seconds assertEquals("Delayed service is down", monitoringService.delayedServiceResponse()); - //As failure threshold is "1", the circuit breaker is changed to OPEN + // As failure threshold is "1", the circuit breaker is changed to OPEN assertEquals("OPEN", delayedServiceCircuitBreaker.getState()); - //Waiting for 4 seconds, which is enough for DelayedService to become healthy and respond successfully. + // Waiting for 4 seconds, which is enough for DelayedService to become healthy and respond + // successfully. try { LOGGER.info("Waiting 4s for delayed service to become responsive"); Thread.sleep(4000); } catch (InterruptedException e) { LOGGER.error("An error occurred: ", e); } - //As retry period is 2 seconds (<4 seconds of wait), hence the circuit breaker should be back in HALF_OPEN state. + // As retry period is 2 seconds (<4 seconds of wait), hence the circuit breaker should be back + // in HALF_OPEN state. assertEquals("HALF_OPEN", delayedServiceCircuitBreaker.getState()); - //Check the success response from delayed service. + // Check the success response from delayed service. assertEquals("Delayed service is working", monitoringService.delayedServiceResponse()); - //As the response is success, the state should be CLOSED + // As the response is success, the state should be CLOSED assertEquals("CLOSED", delayedServiceCircuitBreaker.getState()); } - } diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java index 465371a3aad3..c184a8376220 100644 --- a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/DefaultCircuitBreakerTest.java @@ -28,29 +28,27 @@ import org.junit.jupiter.api.Test; -/** - * Circuit Breaker test - */ +/** Circuit Breaker test */ class DefaultCircuitBreakerTest { - //long timeout, int failureThreshold, long retryTimePeriod + // long timeout, int failureThreshold, long retryTimePeriod @Test void testEvaluateState() { var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 100); - //Right now, failureCountfailureThreshold, and lastFailureTime is nearly equal to current time, - //state should be half-open + // Since failureCount>failureThreshold, and lastFailureTime is nearly equal to current time, + // state should be half-open assertEquals(circuitBreaker.getState(), "HALF_OPEN"); - //Since failureCount>failureThreshold, and lastFailureTime is much lesser current time, - //state should be open + // Since failureCount>failureThreshold, and lastFailureTime is much lesser current time, + // state should be open circuitBreaker.lastFailureTime = System.nanoTime() - 1000 * 1000 * 1000 * 1000; circuitBreaker.evaluateState(); assertEquals(circuitBreaker.getState(), "OPEN"); - //Now set it back again to closed to test idempotency + // Now set it back again to closed to test idempotency circuitBreaker.failureCount = 0; circuitBreaker.evaluateState(); assertEquals(circuitBreaker.getState(), "CLOSED"); @@ -59,23 +57,24 @@ void testEvaluateState() { @Test void testSetStateForBypass() { var circuitBreaker = new DefaultCircuitBreaker(null, 1, 1, 2000 * 1000 * 1000); - //Right now, failureCount { - var obj = new DelayedRemoteService(); - obj.call(); - }); + Assertions.assertThrows( + RemoteServiceException.class, + () -> { + var obj = new DelayedRemoteService(); + obj.call(); + }); } /** @@ -54,7 +54,7 @@ void testDefaultConstructor() throws RemoteServiceException { */ @Test void testParameterizedConstructor() throws RemoteServiceException { - var obj = new DelayedRemoteService(System.nanoTime()-2000*1000*1000,1); - assertEquals("Delayed service is working",obj.call()); + var obj = new DelayedRemoteService(System.nanoTime() - 2000 * 1000 * 1000, 1); + assertEquals("Delayed service is working", obj.call()); } } diff --git a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java index 51dde10ece8b..f7781fd1c58d 100644 --- a/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java +++ b/circuit-breaker/src/test/java/com/iluwatar/circuitbreaker/MonitoringServiceTest.java @@ -28,28 +28,25 @@ import org.junit.jupiter.api.Test; -/** - * Monitoring Service test - */ +/** Monitoring Service test */ class MonitoringServiceTest { - //long timeout, int failureThreshold, long retryTimePeriod + // long timeout, int failureThreshold, long retryTimePeriod @Test void testLocalResponse() { - var monitoringService = new MonitoringService(null,null); + var monitoringService = new MonitoringService(null, null); var response = monitoringService.localResourceResponse(); assertEquals(response, "Local Service is working"); } @Test void testDelayedRemoteResponseSuccess() { - var delayedService = new DelayedRemoteService(System.nanoTime()-2*1000*1000*1000, 2); - var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, - 1, - 2 * 1000 * 1000 * 1000); + var delayedService = new DelayedRemoteService(System.nanoTime() - 2 * 1000 * 1000 * 1000, 2); + var delayedServiceCircuitBreaker = + new DefaultCircuitBreaker(delayedService, 3000, 1, 2 * 1000 * 1000 * 1000); - var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null); - //Set time in past to make the server work + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, null); + // Set time in past to make the server work var response = monitoringService.delayedServiceResponse(); assertEquals(response, "Delayed service is working"); } @@ -57,11 +54,10 @@ void testDelayedRemoteResponseSuccess() { @Test void testDelayedRemoteResponseFailure() { var delayedService = new DelayedRemoteService(System.nanoTime(), 2); - var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, - 1, - 2 * 1000 * 1000 * 1000); - var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null); - //Set time as current time as initially server fails + var delayedServiceCircuitBreaker = + new DefaultCircuitBreaker(delayedService, 3000, 1, 2 * 1000 * 1000 * 1000); + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, null); + // Set time as current time as initially server fails var response = monitoringService.delayedServiceResponse(); assertEquals(response, "Delayed service is down"); } @@ -69,11 +65,10 @@ void testDelayedRemoteResponseFailure() { @Test void testQuickRemoteServiceResponse() { var delayedService = new QuickRemoteService(); - var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, - 1, - 2 * 1000 * 1000 * 1000); - var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,null); - //Set time as current time as initially server fails + var delayedServiceCircuitBreaker = + new DefaultCircuitBreaker(delayedService, 3000, 1, 2 * 1000 * 1000 * 1000); + var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, null); + // Set time as current time as initially server fails var response = monitoringService.delayedServiceResponse(); assertEquals(response, "Quick Service is working"); } diff --git a/clean-architecture/README.md b/clean-architecture/README.md new file mode 100644 index 000000000000..599ae19ce13e --- /dev/null +++ b/clean-architecture/README.md @@ -0,0 +1,291 @@ +--- +title: "Clean Architecture - A Software Maintainable Architectural style." +shortTitle: Clean Architecture +description: "Learn the Clean Architecture Style in Java with real-world examples, code snippets, and class diagrams. Enhance your coding skills with our detailed explanations." +category: Architectural +language: en +tag: + - Architecture + - Decoupling + - Domain + - Inversion of control + - Layered architecture + - Modularity + - Testing +--- + +## Intent of Clean Architecture. + +To organize the system so that the core business logic remains independent from external concerns and frameworks. + +## Detailed Explanation of Clean Architecture Pattern with Real-World Examples + +Real-world example + +> Imagine a large pizza chain with multiple ordering channels—web, mobile app, phone calls, and in-store kiosks. The core “pizza domain” logic (calculating prices, preparing orders, managing loyalty points) is kept entirely separate from the user interfaces and storage mechanisms. As a result, the chain can add or change the ordering channel (for example, introducing a chatbot or swapping out the database) without altering the fundamental pizza-ordering rules, thanks to the layered boundaries and strict dependency rules of Clean Architecture. + +In plain words + +> Clean Architecture is a software design approach that isolates the core business logic from external concerns (like databases, frameworks, or UI) through strict layering and clear boundaries, ensuring that changes in one layer don't ripple through the entire system. + +Wikipedia says + +> The clean architecture proposed by Robert C. Martin in 2012 combines the principles of the hexagonal architecture, the onion architecture and several other variants. It provides additional levels of detail of the component, which are presented as concentric rings. It isolates adapters and interfaces (user interface, databases, external systems, devices) in the outer rings of the architecture and leaves the inner rings for use cases and entities. The clean architecture uses the principle of dependency inversion with the strict rule that dependencies shall only exist between an outer ring to an inner ring and never the contrary. + +Mind map + +![Clean Architecture Mind Map](./etc/clean-architecture-mind-map.png) + +Flowchart + +![Clean Architecture Flowchart](./etc/clean-architecture-flowchart.png) + +## Programmatic Example of Clean Architecture Pattern + +First, we define the core domain entities: `Product`, `Order`, and `Cart`. These classes capture the fundamental business logic and state. + +```java +public class Product { + private String id; + private String name; + private double price; + + public Product(String id, String name, double price) { + this.id = id; + this.name = name; + this.price = price; + } +} +``` + +```java +public class Cart { + private Product product; + private int quantity; + + public CartItem(Product product, int quantity) { + this.product = product; + this.quantity = quantity; + } + + public double getTotalPrice() { + return product.getPrice() * quantity; + } +} +``` + +```java +public class Order { + private String orderId; + private List items; + private double totalPrice; + + public Order(String orderId, List items) { + this.orderId = orderId; + this.items = items; + this.totalPrice = items.stream().mapToDouble(CartItem::getTotalPrice).sum(); + } +} +``` + +The repository interfaces are created to abstract data operations for each domain object, allowing us to switch out storage or persistence mechanisms without changing higher-level logic. + +```java +public interface CartRepository { + void addItemToCart(String userId, Product product, int quantity); + void removeItemFromCart(String userId, String productId); + List getItemsInCart(String userId); + double calculateTotal(String userId); + void clearCart(String userId); +} +``` +```java +public interface ProductRepository { + Product getProductById(String productId); +} +``` +```java +public interface OrderRepository { + void saveOrder(Order order); +} +``` + +The in-memory data store implementations use simple collections to hold state. They demonstrate how easily we can replace or extend the data layer (e.g., swapping in a database) without affecting the domain logic. + +```java +public class InMemoryCartRepository implements CartRepository { + private final Map> userCarts = new HashMap<>(); + + @Override + public void addItemToCart(String userId, Product product, int quantity) { + List cart = userCarts.getOrDefault(userId, new ArrayList<>()); + cart.add(new Cart(product, quantity)); + userCarts.put(userId, cart); + } + + @Override + public void removeItemFromCart(String userId, String productId) { + List cart = userCarts.get(userId); + if (cart != null) { + cart.removeIf(item -> item.getProduct().getId().equals(productId)); + } + } + + @Override + public List getItemsInCart(String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()); + } + + @Override + public double calculateTotal(String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()) + .stream() + .mapToDouble(Cart::getTotalPrice) + .sum(); + } + + @Override + public void clearCart(String userId) { + userCarts.remove(userId); + } +} +``` +```java +public class InMemoryOrderRepository implements OrderRepository { + private final List orders = new ArrayList<>(); + + @Override + public void saveOrder(Order order) { + orders.add(order); + } +} +``` + +```java +public class InMemoryProductRepository implements ProductRepository { + private final Map products = new HashMap<>(); + + public InMemoryProductRepository() { + products.put("1", new Product("1", "Laptop", 1000.0)); + products.put("2", new Product("2", "Smartphone", 500.0)); + } + + @Override + public Product getProductById(String productId) { + return products.get(productId); + } +} +``` + +The order controller coordinates the checkout process by using the use-case or service layer (`ShoppingCartService`). + +```java +public class OrderController{ + private final ShoppingCartService shoppingCartUseCase; + + public OrderController(ShoppingCartService shoppingCartUseCase) { + this.shoppingCartUseCase = shoppingCartUseCase; + } + + public Order checkout(String userId) { + return shoppingCartUseCase.checkout(userId); + } +} +``` + +The cart controller focuses on cart-related actions like adding or removing items and calculating totals. + + +```java +public class CartController { + private final ShoppingCartService shoppingCartUseCase; + + public CartController(ShoppingCartService shoppingCartUseCase) { + this.shoppingCartUseCase = shoppingCartUseCase; + } + + public void addItemToCart(String userId, String productId, int quantity) { + shoppingCartUseCase.addItemToCart(userId, productId, quantity); + } + + public void removeItemFromCart(String userId, String productId) { + shoppingCartUseCase.removeItemFromCart(userId, productId); + } + + public double calculateTotal(String userId) { + return shoppingCartUseCase.calculateTotal(userId); + } +} +``` + +The clean architecture in action. In the `main` method, we wire up everything, simulating a typical user flow: items are added to the cart, the total is calculated, and finally an order is placed. + +```java +public static void main(String[] args) { + + ProductRepository productRepository = new InMemoryProductRepository(); + CartRepository cartRepository = new InMemoryCartRepository(); + OrderRepository orderRepository = new InMemoryOrderRepository(); + + ShoppingCartService shoppingCartUseCase = + new ShoppingCartService(productRepository, cartRepository, orderRepository); + + CartController cartController = new CartController(shoppingCartUseCase); + OrderController orderController = new OrderController(shoppingCartUseCase); + + String userId = "user123"; + cartController.addItemToCart(userId, "1", 1); + cartController.addItemToCart(userId, "2", 2); + + System.out.println("Total: $" + cartController.calculateTotal(userId)); + + Order order = orderController.checkout(userId); + System.out.println( + "Order placed! Order ID: " + order.getOrderId() + ", Total: $" + order.getTotalPrice()); + } +``` + +The output of the code. + +```md +Total: $2000.0 +Order placed! Order ID: ORDER-1743349969254, Total: $2000.0 +``` + +## When to Use the Clean Architecture Pattern in Java + +* When you want to keep business rules independent of UI, database, or any other external agency +* When you need a high level of maintainability and testability in large Java applications +* When you aim to enforce clear boundaries among application layers + +## Real-World Applications of Clean Architecture Pattern in Java + +* Large-scale enterprise systems in finance and insurance domains +* Microservices-based architectures that prioritize decoupling and modular design +* Java systems requiring stringent separation of concerns and domain-centered design + +## Benefits and Trade-offs of Clean Architecture Pattern + +Benefits: + +* High maintainability by isolating core logic from infrastructure details +* Enhanced testability through clear boundaries around the domain model +* Flexibility in replacing or upgrading external components without affecting core logic + +Trade-Offs: + +* Initial complexity from enforcing strict layers and boundaries +* Potential overhead in smaller projects not requiring such rigid separation +* Requires disciplined team adherence to architecture rules + +## Related Java Design Patterns + +* [Dependency Injection](https://java-design-patterns.com/patterns/dependency-injection/): Facilitates decoupling layers by injecting dependencies rather than hard-coding them +* [Layered Architecture](https://java-design-patterns.com/patterns/layered-architecture/): Both separate concerns into distinct tiers but Clean Architecture emphasizes strict dependency rules +* [Hexagonal Architecture](https://java-design-patterns.com/patterns/hexagonal-architecture/): Similar focus on isolating core logic with ports and adapters + +## References and Credits + +* [Clean Architecture: A Craftsman's Guide to Software Structure and Design](https://amzn.to/3UoKkaR) +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/3wRnjp5) +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) diff --git a/clean-architecture/etc/clean-architecture-flowchart.png b/clean-architecture/etc/clean-architecture-flowchart.png new file mode 100644 index 000000000000..043827f08844 Binary files /dev/null and b/clean-architecture/etc/clean-architecture-flowchart.png differ diff --git a/clean-architecture/etc/clean-architecture-mind-map.png b/clean-architecture/etc/clean-architecture-mind-map.png new file mode 100644 index 000000000000..59786d4a7c3e Binary files /dev/null and b/clean-architecture/etc/clean-architecture-mind-map.png differ diff --git a/clean-architecture/etc/cleanArchitectureUMLDiagram.PNG b/clean-architecture/etc/cleanArchitectureUMLDiagram.PNG new file mode 100644 index 000000000000..f107d34a14d8 Binary files /dev/null and b/clean-architecture/etc/cleanArchitectureUMLDiagram.PNG differ diff --git a/clean-architecture/etc/cleanArchitectureUMLDiagram.png b/clean-architecture/etc/cleanArchitectureUMLDiagram.png new file mode 100644 index 000000000000..f107d34a14d8 Binary files /dev/null and b/clean-architecture/etc/cleanArchitectureUMLDiagram.png differ diff --git a/clean-architecture/pom.xml b/clean-architecture/pom.xml new file mode 100644 index 000000000000..8222c6d9d80d --- /dev/null +++ b/clean-architecture/pom.xml @@ -0,0 +1,70 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + clean-architecture + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.cleanarchitecture.App + + + + + + + + + diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java new file mode 100644 index 000000000000..4b56dcbbfd0f --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/App.java @@ -0,0 +1,71 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import lombok.extern.slf4j.Slf4j; + +/** + * Clean Architecture ensures separation of concerns by organizing code, into layers and making it + * scalable and maintainable. + * + *

    In the example there are Entities (Core Models) – Product, Cart, Order handle business logic. + * Use Cases (Application Logic) – ShoppingCartService manages operations like adding items and + * checkout. Interfaces & Adapters – Repositories (CartRepository, OrderRepository) abstract data + * handling, while controllers (CartController, OrderController) manage interactions. + */ +@Slf4j +public final class App { + + private App() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(final String[] args) { + ProductRepository productRepository = new InMemoryProductRepository(); + CartRepository cartRepository = new InMemoryCartRepository(); + OrderRepository orderRepository = new InMemoryOrderRepository(); + + ShoppingCartService shoppingCartUseCase = + new ShoppingCartService(productRepository, cartRepository, orderRepository); + + CartController cartController = new CartController(shoppingCartUseCase); + OrderController orderController = new OrderController(shoppingCartUseCase); + + String userId = "user123"; + cartController.addItemToCart(userId, "1", 1); + cartController.addItemToCart(userId, "2", 2); + + Order order = orderController.checkout(userId); + LOGGER.info("Total: ${}", cartController.calculateTotal(userId)); + + LOGGER.info( + "Order placed! Order ID: {}, Total: ${}", order.getOrderId(), order.getTotalPrice()); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java new file mode 100644 index 000000000000..c4e65df94845 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Cart.java @@ -0,0 +1,64 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import lombok.Getter; + +/** + * Represents a shopping cart containing a product and its quantity. This class calculates the total + * price of the product based on its price and quantity. + */ +@Getter +public class Cart { + /** The product in the cart. It holds the product details such as name, price, and description. */ + private final Product product; + + /** + * The quantity of the product in the cart. It represents how many units of the product are added + * to the cart. + */ + private final int quantity; + + /** + * Constructs a new Cart instance with a specified product and quantity. + * + * @param prod the product to be added to the cart. + * @param qty the quantity of the product in the cart. + */ + public Cart(final Product prod, final int qty) { + this.product = prod; + this.quantity = qty; + } + + /** + * Calculates the total price of the products in the cart. The total price is the product's price + * multiplied by the quantity. + * + * @return the total price of the products in the cart. + */ + public double getTotalPrice() { + return product.getPrice() * quantity; + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java new file mode 100644 index 000000000000..da93cc2a6d93 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartController.java @@ -0,0 +1,77 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +/** + * Controller class for handling shopping cart operations. + * + *

    This class provides methods to add, remove, and calculate the total price of items in a user's + * shopping cart. + */ +public class CartController { + + /** Service layer responsible for cart operations. */ + private final ShoppingCartService shoppingCartUseCase; + + /** + * Constructs a CartController with the specified shopping cart service. + * + * @param shoppingCart The shopping cart service to handle cart operations. + */ + public CartController(final ShoppingCartService shoppingCart) { + this.shoppingCartUseCase = shoppingCart; + } + + /** + * Adds an item to the user's cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be added. + * @param quantity The quantity of the product. + */ + public void addItemToCart(final String userId, final String productId, final int quantity) { + shoppingCartUseCase.addItemToCart(userId, productId, quantity); + } + + /** + * Removes an item from the user's cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be removed. + */ + public void removeItemFromCart(final String userId, final String productId) { + shoppingCartUseCase.removeItemFromCart(userId, productId); + } + + /** + * Calculates the total cost of items in the user's cart. + * + * @param userId The ID of the user. + * @return The total price of all items in the cart. + */ + public double calculateTotal(final String userId) { + return shoppingCartUseCase.calculateTotal(userId); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java new file mode 100644 index 000000000000..844bc48345b4 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/CartRepository.java @@ -0,0 +1,70 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.List; + +/** CartRepository. */ +public interface CartRepository { + /** + * Adds an item to the user's cart. + * + * @param userId The ID of the user. + * @param product The product to be added. + * @param quantity The quantity of the product. + */ + void addItemToCart(String userId, Product product, int quantity); + + /** + * Removes an item from the user's cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be removed. + */ + void removeItemFromCart(String userId, String productId); + + /** + * Retrieves the list of items in the user's cart. + * + * @param userId The ID of the user. + * @return A list of items in the cart. + */ + List getItemsInCart(String userId); + + /** + * Calculates the total price of the items in the user's cart. + * + * @param userId The ID of the user. + * @return The total price of all items in the cart. + */ + double calculateTotal(String userId); + + /** + * Clears all items from the user's cart. + * + * @param userId The ID of the user. + */ + void clearCart(String userId); +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java new file mode 100644 index 000000000000..2965cddd57f3 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryCartRepository.java @@ -0,0 +1,102 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@link CartRepository} that stores cart items in memory. + * + *

    This class maintains a map of user carts where each user has a list of cart items. + */ +public class InMemoryCartRepository implements CartRepository { + /** A map storing user carts with their respective cart items. */ + private final Map> userCarts = new HashMap<>(); + + /** + * Adds an item to the user's cart. + * + * @param userId The ID of the user. + * @param product The product to be added. + * @param quantity The quantity of the product. + */ + @Override + public void addItemToCart(final String userId, final Product product, final int quantity) { + List cart = userCarts.getOrDefault(userId, new ArrayList<>()); + cart.add(new Cart(product, quantity)); + userCarts.put(userId, cart); + } + + /** + * Removes an item from the user's cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be removed. + */ + @Override + public void removeItemFromCart(final String userId, final String productId) { + List cart = userCarts.get(userId); + if (cart != null) { + cart.removeIf(item -> item.getProduct().getId().equals(productId)); + } + } + + /** + * Retrieves all items in the user's cart. + * + * @param userId The ID of the user. + * @return A list of {@link Cart} items in the user's cart. + */ + @Override + public List getItemsInCart(final String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()); + } + + /** + * Calculates the total price of items in the user's cart. + * + * @param userId The ID of the user. + * @return The total price of the cart. + */ + @Override + public double calculateTotal(final String userId) { + return userCarts.getOrDefault(userId, new ArrayList<>()).stream() + .mapToDouble(Cart::getTotalPrice) + .sum(); + } + + /** + * Clears all items from the user's cart. + * + * @param userId The ID of the user. + */ + @Override + public void clearCart(final String userId) { + userCarts.remove(userId); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java new file mode 100644 index 000000000000..b8a17cd6045a --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryOrderRepository.java @@ -0,0 +1,49 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.ArrayList; +import java.util.List; + +/** + * An in-memory implementation of the {@link OrderRepository}. + * + *

    This class stores orders in a list, allowing orders to be saved but not persisted beyond the + * application's runtime. + */ +public class InMemoryOrderRepository implements OrderRepository { + /** A list to store orders in memory. */ + private final List orders = new ArrayList<>(); + + /** + * Saves an order to the in-memory repository. + * + * @param order The order to be saved. + */ + @Override + public void saveOrder(final Order order) { + orders.add(order); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java new file mode 100644 index 000000000000..c91677feeff5 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/InMemoryProductRepository.java @@ -0,0 +1,71 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.HashMap; +import java.util.Map; + +/** + * In-memory implementation of the {@link ProductRepository} interface. + * + *

    This repository stores products in memory allowing retrieval by product ID. + */ +public class InMemoryProductRepository implements ProductRepository { + /** A map to store products by their unique product ID. */ + private final Map products = new HashMap<>(); + + /** + * The price of the Laptop in USD. + * + *

    Used in the in-memory product repository to define the cost of a Laptop. + */ + private static final double LAPTOP_PRICE = 1000.0; + + /** + * The price of the Smartphone in USD. + * + *

    Used in the in-memory product repository to define the cost of a Smartphone. + */ + private static final double SMARTPHONE_PRICE = 500.0; + + /** + * Constructs an {@code InMemoryProductRepository} and initializes it with some example products. + */ + public InMemoryProductRepository() { + products.put("1", new Product("1", "Laptop", LAPTOP_PRICE)); + products.put("2", new Product("2", "Smartphone", SMARTPHONE_PRICE)); + } + + /** + * Retrieves a product by its unique ID. + * + * @param productId The ID of the product to retrieve. + * @return The {@link Product} corresponding to the given ID {@code null} if not found. + */ + @Override + public Product getProductById(final String productId) { + return products.get(productId); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java new file mode 100644 index 000000000000..70bf058dc2eb --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Order.java @@ -0,0 +1,58 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.List; +import lombok.Getter; + +/** + * Represents an order placed by a user containing the ordered items and total price. + * + *

    An order includes a unique order ID, a list of cart items and the total price of the order. + */ +@Getter +public class Order { + /** The unique identifier for this order. */ + private final String orderId; + + /** The list of items included in this order. */ + private final List items; + + /** The list of items included in this order. */ + private final double totalPrice; + + /** + * Constructs an {@code Order} with the given order ID and list of cart items. The total price is + * based on the individual item prices in the cart. + * + * @param id The unique identifier for the order. + * @param item The list of cart items included in the order. + */ + public Order(final String id, final List item) { + this.orderId = id; + this.items = item; + this.totalPrice = items.stream().mapToDouble(Cart::getTotalPrice).sum(); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java new file mode 100644 index 000000000000..d61dad322750 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderController.java @@ -0,0 +1,54 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +/** + * Controller for handling order-related operations. + * + *

    This class provides an endpoint for users to checkout their cart and place an order. + */ +public class OrderController { + /** Service for managing shopping cart operations. */ + private final ShoppingCartService shoppingCartUseCase; + + /** + * Constructs an {@code OrderController} with the given shopping cart service. + * + * @param shoppingCartUse The shopping cart service used to process orders. + */ + public OrderController(final ShoppingCartService shoppingCartUse) { + this.shoppingCartUseCase = shoppingCartUse; + } + + /** + * Processes the checkout for a given user and creates an order. + * + * @param userId The ID of the user checking out. + * @return The created {@link Order} after checkout. + */ + public Order checkout(final String userId) { + return shoppingCartUseCase.checkout(userId); + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java new file mode 100644 index 000000000000..4c7276fcb53f --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/OrderRepository.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +/** + * Repository interface for managing order persistence. + * + *

    This interface defines the contract for storing orders in the system. + */ +public interface OrderRepository { + /** + * Saves an order to the repository. + * + * @param order The order to be saved. + */ + void saveOrder(Order order); +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java new file mode 100644 index 000000000000..100613872865 --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/Product.java @@ -0,0 +1,53 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import lombok.Getter; + +/** Represents a product in the system. */ +@Getter +public class Product { + /** The unique identifier for the product. */ + private final String id; + + /** The name of the product. */ + private final String name; + + /** The price of the product. */ + private final double price; + + /** + * Constructs a new Product with the given details. + * + * @param pdtId The unique identifier of the product. + * @param firstName The name of the product. + * @param p The price of the product. + */ + public Product(final String pdtId, final String firstName, final double p) { + this.id = pdtId; + this.name = firstName; + this.price = p; + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java new file mode 100644 index 000000000000..713b62e799bc --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ProductRepository.java @@ -0,0 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +/** Repository interface for handling product-related operations. */ +public interface ProductRepository { + /** + * Retrieves a product by its unique identifier. + * + * @param productId The unique ID of the product. + * @return The product corresponding to the given ID. + */ + Product getProductById(String productId); +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java new file mode 100644 index 000000000000..cd74aa3145cf --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/ShoppingCartService.java @@ -0,0 +1,112 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import java.util.List; + +/** + * Service class for managing shopping cart operations. + * + *

    This class provides functionalities to add and remove items from the cart, calculate the total + * price, and handle checkout operations. + */ +public class ShoppingCartService { + /** Repository for managing product data. */ + private final ProductRepository productRepository; + + /** Repository for managing cart data. */ + private final CartRepository cartRepository; + + /** Repository for managing order data. */ + private final OrderRepository orderRepository; + + /** + * Constructs a ShoppingCartService with the required repositories. + * + * @param pdtRepository The repository to fetch product details. + * @param repository The repository to manage cart operations. + * @param ordRepository The repository to handle order persistence. + */ + public ShoppingCartService( + final ProductRepository pdtRepository, + final CartRepository repository, + final OrderRepository ordRepository) { + this.productRepository = pdtRepository; + this.cartRepository = repository; + this.orderRepository = ordRepository; + } + + /** + * Adds an item to the user's shopping cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be added. + * @param quantity The quantity of the product. + */ + public void addItemToCart(final String userId, final String productId, final int quantity) { + Product product = productRepository.getProductById(productId); + if (product != null) { + cartRepository.addItemToCart(userId, product, quantity); + } + } + + /** + * Removes an item from the user's shopping cart. + * + * @param userId The ID of the user. + * @param productId The ID of the product to be removed. + */ + public void removeItemFromCart(final String userId, final String productId) { + cartRepository.removeItemFromCart(userId, productId); + } + + /** + * Calculates the total cost of items in the user's shopping cart. + * + * @param userId The ID of the user. + * @return The total price of all items in the cart. + */ + public double calculateTotal(final String userId) { + return cartRepository.calculateTotal(userId); + } + + /** + * Checks out the user's cart and creates an order. + * + *

    This method retrieves the cart items, generates an order ID, creates a new order, saves it, + * and clears the cart. + * + * @param userId The ID of the user. + * @return The created order containing purchased items. + */ + public Order checkout(final String userId) { + List items = cartRepository.getItemsInCart(userId); + String orderId = "ORDER-" + System.currentTimeMillis(); + Order order = new Order(orderId, items); + orderRepository.saveOrder(order); + cartRepository.clearCart(userId); + return order; + } +} diff --git a/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java new file mode 100644 index 000000000000..7b8142f436ae --- /dev/null +++ b/clean-architecture/src/main/java/com/iluwatar/cleanarchitecture/package-info.java @@ -0,0 +1,31 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/** + * Provides classes and interfaces for the clean architecture pattern implementation. + * + *

    This package includes classes for managing products, carts, orders, repositories, and services + * for a shopping cart system, following the clean architecture principles. + */ +package com.iluwatar.cleanarchitecture; diff --git a/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java new file mode 100644 index 000000000000..86265d2886b7 --- /dev/null +++ b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/AppTest.java @@ -0,0 +1,43 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.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. + */ + @Test + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java new file mode 100644 index 000000000000..c015c54c139c --- /dev/null +++ b/clean-architecture/src/test/java/com/iluwatar/cleanarchitecture/CartControllerTest.java @@ -0,0 +1,65 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.cleanarchitecture; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CartControllerTest { + + private CartController cartController; + + @BeforeEach + void setUp() { + ProductRepository productRepository = new InMemoryProductRepository(); + CartRepository cartRepository = new InMemoryCartRepository(); + OrderRepository orderRepository = new InMemoryOrderRepository(); + ShoppingCartService shoppingCartUseCase = + new ShoppingCartService(productRepository, cartRepository, orderRepository); + cartController = new CartController(shoppingCartUseCase); + } + + @Test + void testRemoveItemFromCart() { + cartController.addItemToCart("user123", "1", 1); + cartController.addItemToCart("user123", "2", 2); + + assertEquals(2000.0, cartController.calculateTotal("user123")); + + cartController.removeItemFromCart("user123", "1"); + + assertEquals(1000.0, cartController.calculateTotal("user123")); + } + + @Test + void testRemoveNonExistentItem() { + cartController.addItemToCart("user123", "2", 2); + cartController.removeItemFromCart("user123", "999"); + + assertEquals(1000.0, cartController.calculateTotal("user123")); + } +} diff --git a/client-session/README.md b/client-session/README.md index c49e53c7174b..e693ef7db977 100644 --- a/client-session/README.md +++ b/client-session/README.md @@ -33,6 +33,10 @@ Wikipedia says > The client-server model on Wikipedia describes a system where client devices request services and resources from centralized servers. This model is crucial in web applications where client sessions are used to manage user-specific data across multiple requests. For example, when a bank customer accesses online banking services, their login credentials and session state are managed by the web server to maintain continuity of their interactions. +Sequence diagram + +![Client Session sequence diagram](./etc/client-session-sequence-diagram.png) + ## Programmatic Example of Client Session Pattern in Java The Client Session design pattern is a behavioral design pattern that maintains a user's state and data across multiple requests within a web application, ensuring a continuous and personalized user experience. This pattern is commonly used in web applications where user-specific data needs to be managed across multiple requests. diff --git a/client-session/etc/client-session-sequence-diagram.png b/client-session/etc/client-session-sequence-diagram.png new file mode 100644 index 000000000000..7d1282a9bbd9 Binary files /dev/null and b/client-session/etc/client-session-sequence-diagram.png differ diff --git a/client-session/pom.xml b/client-session/pom.xml index b7ff08637091..1b2ea4564ff9 100644 --- a/client-session/pom.xml +++ b/client-session/pom.xml @@ -34,6 +34,14 @@ client-session + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/client-session/src/main/java/com/iluwatar/client/session/App.java b/client-session/src/main/java/com/iluwatar/client/session/App.java index 282d8a7f3b28..8f744353ed1b 100644 --- a/client-session/src/main/java/com/iluwatar/client/session/App.java +++ b/client-session/src/main/java/com/iluwatar/client/session/App.java @@ -29,12 +29,11 @@ * The Client-Session pattern allows the session data to be stored on the client side and send this * data to the server with each request. * - *

    In this example, The {@link Server} class represents the server that would process the + *

    In this example, The {@link Server} class represents the server that would process the * incoming {@link Request} and also assign {@link Session} to a client. Here one instance of Server * is created. The we create two sessions for two different clients. These sessions are then passed * on to the server in the request along with the data. The server is then able to interpret the * client based on the session associated with it. - *

    */ public class App { diff --git a/client-session/src/main/java/com/iluwatar/client/session/Request.java b/client-session/src/main/java/com/iluwatar/client/session/Request.java index 008011f180f7..e47ed2775ac4 100644 --- a/client-session/src/main/java/com/iluwatar/client/session/Request.java +++ b/client-session/src/main/java/com/iluwatar/client/session/Request.java @@ -28,9 +28,7 @@ import lombok.AllArgsConstructor; import lombok.Data; -/** - * The Request class which contains the Session details and data. - */ +/** The Request class which contains the Session details and data. */ @Data @AllArgsConstructor public class Request { @@ -38,5 +36,4 @@ public class Request { private String data; private Session session; - } diff --git a/client-session/src/main/java/com/iluwatar/client/session/Server.java b/client-session/src/main/java/com/iluwatar/client/session/Server.java index 6d9dc3dbc80a..13a43a2dda81 100644 --- a/client-session/src/main/java/com/iluwatar/client/session/Server.java +++ b/client-session/src/main/java/com/iluwatar/client/session/Server.java @@ -31,7 +31,8 @@ import lombok.extern.slf4j.Slf4j; /** - * The Server class. The client communicates with the server and request processing and getting a new session. + * The Server class. The client communicates with the server and request processing and getting a + * new session. */ @Slf4j @Data @@ -41,12 +42,10 @@ public class Server { private int port; - /** * Creates a new session. * * @param name name of the client - * * @return Session Object */ public Session getSession(String name) { @@ -59,7 +58,10 @@ public Session getSession(String name) { * @param request Request object with data and Session */ public void process(Request request) { - LOGGER.info("Processing Request with client: " + request.getSession().getClientName() + " data: " + request.getData()); + LOGGER.info( + "Processing Request with client: " + + request.getSession().getClientName() + + " data: " + + request.getData()); } - } diff --git a/client-session/src/main/java/com/iluwatar/client/session/Session.java b/client-session/src/main/java/com/iluwatar/client/session/Session.java index bb9f7246cb4e..a7639485b83c 100644 --- a/client-session/src/main/java/com/iluwatar/client/session/Session.java +++ b/client-session/src/main/java/com/iluwatar/client/session/Session.java @@ -29,20 +29,16 @@ import lombok.Data; /** - * The Session class. Each client get assigned a Session which is then used for further communications. + * The Session class. Each client get assigned a Session which is then used for further + * communications. */ @Data @AllArgsConstructor public class Session { - /** - * Session id. - */ + /** Session id. */ private String id; - /** - * Client name. - */ + /** Client name. */ private String clientName; - } diff --git a/client-session/src/test/java/com/iluwatar/client/session/AppTest.java b/client-session/src/test/java/com/iluwatar/client/session/AppTest.java index 0e33f74f460b..63951ae48ae3 100644 --- a/client-session/src/test/java/com/iluwatar/client/session/AppTest.java +++ b/client-session/src/test/java/com/iluwatar/client/session/AppTest.java @@ -33,6 +33,6 @@ class AppTest { @Test void appStartsWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/client-session/src/test/java/com/iluwatar/client/session/ServerTest.java b/client-session/src/test/java/com/iluwatar/client/session/ServerTest.java index 72f960332a2f..0037ca6339fb 100644 --- a/client-session/src/test/java/com/iluwatar/client/session/ServerTest.java +++ b/client-session/src/test/java/com/iluwatar/client/session/ServerTest.java @@ -24,9 +24,10 @@ */ package com.iluwatar.client.session; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + class ServerTest { @Test diff --git a/collecting-parameter/README.md b/collecting-parameter/README.md index 55609aebdcb9..89c37e79bfb8 100644 --- a/collecting-parameter/README.md +++ b/collecting-parameter/README.md @@ -36,6 +36,10 @@ Wikipedia says > In the Collecting Parameter idiom a collection (list, map, etc.) is passed repeatedly as a parameter to a method which adds items to the collection. +Flowchart + +![Collecting Parameter flowchart](./etc/collecting-parameter-flowchart.png) + ## Programmatic Example of Collecting Parameter Pattern in Java Within a large corporate building, there exists a global printer queue that is a collection of all the printing jobs that are currently pending. Various floors contain different models of printers, each having a different printing policy. We must construct a program that can continually add appropriate printing jobs to a collection, which is called the collecting parameter. diff --git a/collecting-parameter/etc/collecting-parameter-flowchart.png b/collecting-parameter/etc/collecting-parameter-flowchart.png new file mode 100644 index 000000000000..dcab61c2eb5d Binary files /dev/null and b/collecting-parameter/etc/collecting-parameter-flowchart.png differ diff --git a/collecting-parameter/pom.xml b/collecting-parameter/pom.xml index 8f7fdc2b52db..8c5c015468a4 100644 --- a/collecting-parameter/pom.xml +++ b/collecting-parameter/pom.xml @@ -39,11 +39,6 @@ junit-jupiter-engine test - - junit - junit - test - diff --git a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/App.java b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/App.java index 93ead620da95..d505970fb84f 100644 --- a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/App.java +++ b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/App.java @@ -28,23 +28,23 @@ import java.util.Queue; /** - * The Collecting Parameter Design Pattern aims to return a result that is the collaborative result of several - * methods. This design pattern uses a 'collecting parameter' that is passed to several functions, accumulating results - * as it travels from method-to-method. This is different to the Composed Method design pattern, where a single - * collection is modified via several methods. + * The Collecting Parameter Design Pattern aims to return a result that is the collaborative result + * of several methods. This design pattern uses a 'collecting parameter' that is passed to several + * functions, accumulating results as it travels from method-to-method. This is different to the + * Composed Method design pattern, where a single collection is modified via several methods. * - *

    This example is inspired by Kent Beck's example in his book, 'Smalltalk Best Practice Patterns'. The context for this - * situation is that there is a single printer queue {@link PrinterQueue} that holds numerous print jobs - * {@link PrinterItem} that must be distributed to various print centers. - * Each print center has its own requirements and printing limitations. In this example, the following requirements are: - * If an A4 document is coloured, it must also be single-sided. All other non-coloured A4 documents are accepted. - * All A3 documents must be non-coloured and single sided. All A2 documents must be a single page, single sided, and + *

    This example is inspired by Kent Beck's example in his book, 'Smalltalk Best Practice + * Patterns'. The context for this situation is that there is a single printer queue {@link + * PrinterQueue} that holds numerous print jobs {@link PrinterItem} that must be distributed to + * various print centers. Each print center has its own requirements and printing limitations. In + * this example, the following requirements are: If an A4 document is coloured, it must also be + * single-sided. All other non-coloured A4 documents are accepted. All A3 documents must be + * non-coloured and single sided. All A2 documents must be a single page, single sided, and * non-coloured. * - *

    A collecting parameter (the result variable) is used to filter the global printer queue so that it meets the - * requirements for this centre, - **/ - + *

    A collecting parameter (the result variable) is used to filter the global printer queue so + * that it meets the requirements for this centre, + */ public class App { static PrinterQueue printerQueue = PrinterQueue.getInstance(); @@ -75,16 +75,16 @@ public static void main(String[] args) { } /** - * Adds A4 document jobs to the collecting parameter according to some policy that can be whatever the client - * (the print center) wants. + * Adds A4 document jobs to the collecting parameter according to some policy that can be whatever + * the client (the print center) wants. * * @param printerItemsCollection the collecting parameter */ public static void addValidA4Papers(Queue printerItemsCollection) { /* - Iterate through the printer queue, and add A4 papers according to the correct policy to the collecting parameter, - which is 'printerItemsCollection' in this case. - */ + Iterate through the printer queue, and add A4 papers according to the correct policy to the collecting parameter, + which is 'printerItemsCollection' in this case. + */ for (PrinterItem nextItem : printerQueue.getPrinterQueue()) { if (nextItem.paperSize.equals(PaperSizes.A4)) { var isColouredAndSingleSided = nextItem.isColour && !nextItem.isDoubleSided; @@ -96,9 +96,9 @@ public static void addValidA4Papers(Queue printerItemsCollection) { } /** - * Adds A3 document jobs to the collecting parameter according to some policy that can be whatever the client - * (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate - * the wants of the client. + * Adds A3 document jobs to the collecting parameter according to some policy that can be whatever + * the client (the print center) wants. The code is similar to the 'addA4Papers' method. The code + * can be changed to accommodate the wants of the client. * * @param printerItemsCollection the collecting parameter */ @@ -106,7 +106,8 @@ public static void addValidA3Papers(Queue printerItemsCollection) { for (PrinterItem nextItem : printerQueue.getPrinterQueue()) { if (nextItem.paperSize.equals(PaperSizes.A3)) { - // Encoding the policy into a Boolean: the A3 paper cannot be coloured and double-sided at the same time + // Encoding the policy into a Boolean: the A3 paper cannot be coloured and double-sided at + // the same time var isNotColouredAndSingleSided = !nextItem.isColour && !nextItem.isDoubleSided; if (isNotColouredAndSingleSided) { printerItemsCollection.add(nextItem); @@ -116,9 +117,9 @@ public static void addValidA3Papers(Queue printerItemsCollection) { } /** - * Adds A2 document jobs to the collecting parameter according to some policy that can be whatever the client - * (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate - * the wants of the client. + * Adds A2 document jobs to the collecting parameter according to some policy that can be whatever + * the client (the print center) wants. The code is similar to the 'addA4Papers' method. The code + * can be changed to accommodate the wants of the client. * * @param printerItemsCollection the collecting parameter */ @@ -126,9 +127,10 @@ public static void addValidA2Papers(Queue printerItemsCollection) { for (PrinterItem nextItem : printerQueue.getPrinterQueue()) { if (nextItem.paperSize.equals(PaperSizes.A2)) { - // Encoding the policy into a Boolean: the A2 paper must be single page, single-sided, and non-coloured. - var isNotColouredSingleSidedAndOnePage = nextItem.pageCount == 1 && !nextItem.isDoubleSided - && !nextItem.isColour; + // Encoding the policy into a Boolean: the A2 paper must be single page, single-sided, and + // non-coloured. + var isNotColouredSingleSidedAndOnePage = + nextItem.pageCount == 1 && !nextItem.isDoubleSided && !nextItem.isColour; if (isNotColouredSingleSidedAndOnePage) { printerItemsCollection.add(nextItem); } diff --git a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterItem.java b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterItem.java index 3f0a24854f02..5669f744dd84 100644 --- a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterItem.java +++ b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterItem.java @@ -26,18 +26,14 @@ import java.util.Objects; -/** - * This class represents a Print Item, that should be added to the queue. - **/ +/** This class represents a Print Item, that should be added to the queue. */ public class PrinterItem { PaperSizes paperSize; int pageCount; boolean isDoubleSided; boolean isColour; - /** - * The {@link PrinterItem} constructor. - **/ + /** The {@link PrinterItem} constructor. */ public PrinterItem(PaperSizes paperSize, int pageCount, boolean isDoubleSided, boolean isColour) { if (!Objects.isNull(paperSize)) { this.paperSize = paperSize; @@ -53,6 +49,5 @@ public PrinterItem(PaperSizes paperSize, int pageCount, boolean isDoubleSided, b this.isColour = isColour; this.isDoubleSided = isDoubleSided; - } -} \ No newline at end of file +} diff --git a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterQueue.java b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterQueue.java index 882fc5277f71..8cdfc84107a3 100644 --- a/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterQueue.java +++ b/collecting-parameter/src/main/java/com/iluwatar/collectingparameter/PrinterQueue.java @@ -29,15 +29,17 @@ import java.util.Queue; /** - * This class represents a singleton Printer Queue. It contains a queue that can be filled up with {@link PrinterItem}. - **/ + * This class represents a singleton Printer Queue. It contains a queue that can be filled up with + * {@link PrinterItem}. + */ public class PrinterQueue { static PrinterQueue currentInstance = null; private final Queue printerItemQueue; /** - * This class is a singleton. The getInstance method will ensure that only one instance exists at a time. + * This class is a singleton. The getInstance method will ensure that only one instance exists at + * a time. */ public static PrinterQueue getInstance() { if (Objects.isNull(currentInstance)) { @@ -46,16 +48,12 @@ public static PrinterQueue getInstance() { return currentInstance; } - /** - * Empty the printer queue. - */ + /** Empty the printer queue. */ public void emptyQueue() { currentInstance.getPrinterQueue().clear(); } - /** - * Private constructor prevents instantiation, unless using the getInstance() method. - */ + /** Private constructor prevents instantiation, unless using the getInstance() method. */ private PrinterQueue() { printerItemQueue = new LinkedList<>(); } @@ -72,5 +70,4 @@ public Queue getPrinterQueue() { public void addPrinterItem(PrinterItem printerItem) { currentInstance.getPrinterQueue().add(printerItem); } - } diff --git a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/AppTest.java b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/AppTest.java index 57f9037778ae..8597c4d6f2cb 100644 --- a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/AppTest.java +++ b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/AppTest.java @@ -24,16 +24,14 @@ */ package com.iluwatar.collectingparameter; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + class AppTest { - /** - * Checks whether {@link App} executes without throwing exception - */ + /** Checks whether {@link App} executes without throwing exception */ @Test void executesWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/CollectingParameterTest.java b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/CollectingParameterTest.java index 9818dbb1809e..c53c5ac2c0ed 100644 --- a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/CollectingParameterTest.java +++ b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/CollectingParameterTest.java @@ -24,11 +24,11 @@ */ package com.iluwatar.collectingparameter; +import java.util.LinkedList; +import java.util.Queue; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import java.util.LinkedList; -import java.util.Queue; class CollectingParameterTest { diff --git a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/PrinterQueueTest.java b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/PrinterQueueTest.java index fc633108be76..8c03eea90c95 100644 --- a/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/PrinterQueueTest.java +++ b/collecting-parameter/src/test/java/com/iluwatar/collectingparameter/PrinterQueueTest.java @@ -24,12 +24,12 @@ */ package com.iluwatar.collectingparameter; +import static org.junit.jupiter.api.Assertions.*; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import static org.junit.jupiter.api.Assertions.*; - class PrinterQueueTest { @Test @@ -43,13 +43,14 @@ void singletonTest() { @Test() @Timeout(1000) void negativePageCount() throws IllegalArgumentException { - Assertions.assertThrows(IllegalArgumentException.class, () -> new PrinterItem(PaperSizes.A4, -1, true, true)); + Assertions.assertThrows( + IllegalArgumentException.class, () -> new PrinterItem(PaperSizes.A4, -1, true, true)); } @Test() @Timeout(1000) void nullPageSize() throws IllegalArgumentException { - Assertions.assertThrows(IllegalArgumentException.class, () -> new PrinterItem(null, 1, true, true)); + Assertions.assertThrows( + IllegalArgumentException.class, () -> new PrinterItem(null, 1, true, true)); } - -} \ No newline at end of file +} diff --git a/collection-pipeline/README.md b/collection-pipeline/README.md index 8f8764be2b28..33d207922828 100644 --- a/collection-pipeline/README.md +++ b/collection-pipeline/README.md @@ -29,6 +29,10 @@ Wikipedia says > In software engineering, a pipeline consists of a chain of processing elements (processes, threads, coroutines, functions, etc.), arranged so that the output of each element is the input of the next; the name is by analogy to a physical pipeline. Usually some amount of buffering is provided between consecutive elements. The information that flows in these pipelines is often a stream of records, bytes, or bits, and the elements of a pipeline may be called filters; this is also called the pipe(s) and filters design pattern. Connecting elements into a pipeline is analogous to function composition. +Flowchart + +![Collection Pipeline flowchart](./etc/collection-pipeline-flowchart.png) + ## Programmatic Example of Collection Pipeline Pattern in Java The Collection Pipeline is a programming pattern where you organize some computation as a sequence of operations which compose by taking a collection as output of one operation and feeding it into the next. diff --git a/collection-pipeline/etc/collection-pipeline-flowchart.png b/collection-pipeline/etc/collection-pipeline-flowchart.png new file mode 100644 index 000000000000..48ee51002129 Binary files /dev/null and b/collection-pipeline/etc/collection-pipeline-flowchart.png differ diff --git a/collection-pipeline/pom.xml b/collection-pipeline/pom.xml index 63de6d75f523..b3c36cf06ef6 100644 --- a/collection-pipeline/pom.xml +++ b/collection-pipeline/pom.xml @@ -34,6 +34,14 @@ collection-pipeline + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java index 2cfeb963ba49..7b4537800437 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Car.java @@ -25,8 +25,5 @@ package com.iluwatar.collectionpipeline; -/** - * A Car class that has the properties of make, model, year and category. - */ -public record Car(String make, String model, int year, Category category) { -} +/** A Car class that has the properties of make, model, year and category. */ +public record Car(String make, String model, int year, Category category) {} diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/CarFactory.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/CarFactory.java index b96af01dbe53..2bfaa676060e 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/CarFactory.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/CarFactory.java @@ -26,12 +26,9 @@ import java.util.List; -/** - * A factory class to create a collection of {@link Car} instances. - */ +/** A factory class to create a collection of {@link Car} instances. */ public class CarFactory { - private CarFactory() { - } + private CarFactory() {} /** * Factory method to create a {@link List} of {@link Car} instances. @@ -39,11 +36,12 @@ private CarFactory() { * @return {@link List} of {@link Car} */ public static List createCars() { - return List.of(new Car("Jeep", "Wrangler", 2011, Category.JEEP), + return List.of( + new Car("Jeep", "Wrangler", 2011, Category.JEEP), new Car("Jeep", "Comanche", 1990, Category.JEEP), new Car("Dodge", "Avenger", 2010, Category.SEDAN), new Car("Buick", "Cascada", 2016, Category.CONVERTIBLE), new Car("Ford", "Focus", 2012, Category.SEDAN), new Car("Chevrolet", "Geo Metro", 1992, Category.CONVERTIBLE)); } -} \ No newline at end of file +} diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Category.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Category.java index c7b3dfcdccd3..9de7e4da3b50 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Category.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Category.java @@ -24,9 +24,7 @@ */ package com.iluwatar.collectionpipeline; -/** - * Enum for the category of car. - */ +/** Enum for the category of car. */ public enum Category { JEEP, SEDAN, diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/FunctionalProgramming.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/FunctionalProgramming.java index 20bf77d29a2f..dfe2d9ea5697 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/FunctionalProgramming.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/FunctionalProgramming.java @@ -33,20 +33,19 @@ /** * Iterating and sorting with a collection pipeline * - *

    In functional programming, it's common to sequence complex operations through - * a series of smaller modular functions or operations. The series is called a composition of - * functions, or a function composition. When a collection of data flows through a function - * composition, it becomes a collection pipeline. Function Composition and Collection Pipeline are - * two design patterns frequently used in functional-style programming. + *

    In functional programming, it's common to sequence complex operations through a series of + * smaller modular functions or operations. The series is called a composition of functions, or a + * function composition. When a collection of data flows through a function composition, it becomes + * a collection pipeline. Function Composition and Collection Pipeline are two design patterns + * frequently used in functional-style programming. * - *

    Instead of passing a lambda expression to the map method, we passed the - * method reference Car::getModel. Likewise, instead of passing the lambda expression car -> - * car.getYear() to the comparing method, we passed the method reference Car::getYear. Method - * references are short, concise, and expressive. It is best to use them wherever possible. + *

    Instead of passing a lambda expression to the map method, we passed the method reference + * Car::getModel. Likewise, instead of passing the lambda expression car -> car.getYear() to the + * comparing method, we passed the method reference Car::getYear. Method references are short, + * concise, and expressive. It is best to use them wherever possible. */ public class FunctionalProgramming { - private FunctionalProgramming() { - } + private FunctionalProgramming() {} /** * Method to get models using for collection pipeline. @@ -55,8 +54,11 @@ private FunctionalProgramming() { * @return {@link List} of {@link String} representing models built after year 2000 */ public static List getModelsAfter2000(List cars) { - return cars.stream().filter(car -> car.year() > 2000).sorted(Comparator.comparing(Car::year)) - .map(Car::model).toList(); + return cars.stream() + .filter(car -> car.year() > 2000) + .sorted(Comparator.comparing(Car::year)) + .map(Car::model) + .toList(); } /** @@ -76,8 +78,11 @@ public static Map> getGroupingOfCarsByCategory(List car * @return {@link List} of {@link Car} to belonging to the group */ public static List getSedanCarsOwnedSortedByDate(List persons) { - return persons.stream().map(Person::cars).flatMap(List::stream) + return persons.stream() + .map(Person::cars) + .flatMap(List::stream) .filter(car -> Category.SEDAN.equals(car.category())) - .sorted(Comparator.comparing(Car::year)).toList(); + .sorted(Comparator.comparing(Car::year)) + .toList(); } } diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/ImperativeProgramming.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/ImperativeProgramming.java index 2a85334c6643..24c0b965e4da 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/ImperativeProgramming.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/ImperativeProgramming.java @@ -35,21 +35,20 @@ * Imperative-style programming to iterate over the list and get the names of cars made later than * the year 2000. We then sort the models in ascending order by year. * - *

    As you can see, there's a lot of looping in this code. First, the - * getModelsAfter2000UsingFor method takes a list of cars as its parameter. It extracts or filters - * out cars made after the year 2000, putting them into a new list named carsSortedByYear. Next, it - * sorts that list in ascending order by year-of-make. Finally, it loops through the list - * carsSortedByYear to get the model names and returns them in a list. + *

    As you can see, there's a lot of looping in this code. First, the getModelsAfter2000UsingFor + * method takes a list of cars as its parameter. It extracts or filters out cars made after the year + * 2000, putting them into a new list named carsSortedByYear. Next, it sorts that list in ascending + * order by year-of-make. Finally, it loops through the list carsSortedByYear to get the model names + * and returns them in a list. * - *

    This short example demonstrates what I call the effect of statements. While - * functions and methods in general can be used as expressions, the {@link Collections} sort method - * doesn't return a result. Because it is used as a statement, it mutates the list given as - * argument. Both of the for loops also mutate lists as they iterate. Being statements, that's just - * how these elements work. As a result, the code contains unnecessary garbage variables + *

    This short example demonstrates what I call the effect of statements. While functions and + * methods in general can be used as expressions, the {@link Collections} sort method doesn't return + * a result. Because it is used as a statement, it mutates the list given as argument. Both of the + * for loops also mutate lists as they iterate. Being statements, that's just how these elements + * work. As a result, the code contains unnecessary garbage variables */ public class ImperativeProgramming { - private ImperativeProgramming() { - } + private ImperativeProgramming() {} /** * Method to return the car models built after year 2000 using for loops. @@ -66,12 +65,14 @@ public static List getModelsAfter2000(List cars) { } } - Collections.sort(carsSortedByYear, new Comparator() { - @Override - public int compare(Car car1, Car car2) { - return car1.year() - car2.year(); - } - }); + Collections.sort( + carsSortedByYear, + new Comparator() { + @Override + public int compare(Car car1, Car car2) { + return car1.year() - car2.year(); + } + }); List models = new ArrayList<>(); for (Car car : carsSortedByYear) { @@ -121,12 +122,13 @@ public static List getSedanCarsOwnedSortedByDate(List persons) { } } - sedanCars.sort(new Comparator() { - @Override - public int compare(Car o1, Car o2) { - return o1.year() - o2.year(); - } - }); + sedanCars.sort( + new Comparator() { + @Override + public int compare(Car o1, Car o2) { + return o1.year() - o2.year(); + } + }); return sedanCars; } diff --git a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java index 992596125423..a84dc7f72a31 100644 --- a/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java +++ b/collection-pipeline/src/main/java/com/iluwatar/collectionpipeline/Person.java @@ -26,7 +26,5 @@ import java.util.List; -/** - * A Person class that has the list of cars that the person owns and use. - */ +/** A Person class that has the list of cars that the person owns and use. */ public record Person(List cars) {} diff --git a/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java b/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java index 959d0c6b5b83..9a990b09ec15 100644 --- a/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java +++ b/collection-pipeline/src/test/java/com/iluwatar/collectionpipeline/AppTest.java @@ -31,9 +31,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; -/** - * Tests that Collection Pipeline methods work as expected. - */ +/** Tests that Collection Pipeline methods work as expected. */ @Slf4j class AppTest { @@ -53,19 +51,20 @@ void testGetModelsAfter2000UsingPipeline() { @Test void testGetGroupingOfCarsByCategory() { - var modelsExpected = Map.of( - Category.CONVERTIBLE, List.of( - new Car("Buick", "Cascada", 2016, Category.CONVERTIBLE), - new Car("Chevrolet", "Geo Metro", 1992, Category.CONVERTIBLE) - ), - Category.SEDAN, List.of( - new Car("Dodge", "Avenger", 2010, Category.SEDAN), - new Car("Ford", "Focus", 2012, Category.SEDAN) - ), - Category.JEEP, List.of( - new Car("Jeep", "Wrangler", 2011, Category.JEEP), - new Car("Jeep", "Comanche", 1990, Category.JEEP)) - ); + var modelsExpected = + Map.of( + Category.CONVERTIBLE, + List.of( + new Car("Buick", "Cascada", 2016, Category.CONVERTIBLE), + new Car("Chevrolet", "Geo Metro", 1992, Category.CONVERTIBLE)), + Category.SEDAN, + List.of( + new Car("Dodge", "Avenger", 2010, Category.SEDAN), + new Car("Ford", "Focus", 2012, Category.SEDAN)), + Category.JEEP, + List.of( + new Car("Jeep", "Wrangler", 2011, Category.JEEP), + new Car("Jeep", "Comanche", 1990, Category.JEEP))); var modelsFunctional = FunctionalProgramming.getGroupingOfCarsByCategory(cars); var modelsImperative = ImperativeProgramming.getGroupingOfCarsByCategory(cars); LOGGER.info("Category " + modelsFunctional); @@ -76,10 +75,10 @@ void testGetGroupingOfCarsByCategory() { @Test void testGetSedanCarsOwnedSortedByDate() { var john = new Person(cars); - var modelsExpected = List.of( - new Car("Dodge", "Avenger", 2010, Category.SEDAN), - new Car("Ford", "Focus", 2012, Category.SEDAN) - ); + var modelsExpected = + List.of( + new Car("Dodge", "Avenger", 2010, Category.SEDAN), + new Car("Ford", "Focus", 2012, Category.SEDAN)); var modelsFunctional = FunctionalProgramming.getSedanCarsOwnedSortedByDate(List.of(john)); var modelsImperative = ImperativeProgramming.getSedanCarsOwnedSortedByDate(List.of(john)); assertEquals(modelsExpected, modelsFunctional); diff --git a/combinator/README.md b/combinator/README.md index 0ebe7deea67a..93aa7792fb58 100644 --- a/combinator/README.md +++ b/combinator/README.md @@ -35,6 +35,10 @@ Wikipedia says > A combinator is a higher-order function that uses only function application and earlier defined combinators to define a result from its arguments. +Flowchart + +![Combinator flowchart](./etc/combinator-flowchart.png) + ## Programmatic Example of Combinator Pattern in Java In software design, combinatory logic is pivotal for creating reusable and modular code components. By leveraging higher-order functions, the Combinator pattern promotes code reuse and maintainability in Java applications. diff --git a/combinator/etc/combinator-flowchart.png b/combinator/etc/combinator-flowchart.png new file mode 100644 index 000000000000..9cc8b341395d Binary files /dev/null and b/combinator/etc/combinator-flowchart.png differ diff --git a/combinator/pom.xml b/combinator/pom.xml index 0e2f41962a5b..d366b383a187 100644 --- a/combinator/pom.xml +++ b/combinator/pom.xml @@ -34,10 +34,37 @@ combinator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.combinator.CombinatorApp + + + + + + + + diff --git a/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java b/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java index 2ca6e16d774e..bf1ec4a017de 100644 --- a/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java +++ b/combinator/src/main/java/com/iluwatar/combinator/CombinatorApp.java @@ -26,25 +26,20 @@ import lombok.extern.slf4j.Slf4j; - /** - * The functional pattern representing a style of organizing libraries - * centered around the idea of combining functions. - * Putting it simply, there is some type T, some functions - * for constructing "primitive" values of type T, - * and some "combinators" which can combine values of type T - * in various ways to build up more complex values of type T. - * The class {@link Finder} defines a simple function {@link Finder#find(String)} - * and connected functions - * {@link Finder#or(Finder)}, - * {@link Finder#not(Finder)}, - * {@link Finder#and(Finder)} - * Using them the became possible to get more complex functions {@link Finders} + * The functional pattern representing a style of organizing libraries centered around the idea of + * combining functions. Putting it simply, there is some type T, some functions for constructing + * "primitive" values of type T, and some "combinators" which can combine values of type T in + * various ways to build up more complex values of type T. The class {@link Finder} defines a simple + * function {@link Finder#find(String)} and connected functions {@link Finder#or(Finder)}, {@link + * Finder#not(Finder)}, {@link Finder#and(Finder)} Using them the became possible to get more + * complex functions {@link Finders} */ @Slf4j public class CombinatorApp { - private static final String TEXT = """ + private static final String TEXT = + """ It was many and many a year ago, In a kingdom by the sea, That a maiden there lived whom you may know @@ -60,15 +55,16 @@ public class CombinatorApp { /** * main. + * * @param args args */ public static void main(String[] args) { - var queriesOr = new String[]{"many", "Annabel"}; + var queriesOr = new String[] {"many", "Annabel"}; var finder = Finders.expandedFinder(queriesOr); var res = finder.find(text()); LOGGER.info("the result of expanded(or) query[{}] is {}", queriesOr, res); - var queriesAnd = new String[]{"Annabel", "my"}; + var queriesAnd = new String[] {"Annabel", "my"}; finder = Finders.specializedFinder(queriesAnd); res = finder.find(text()); LOGGER.info("the result of specialized(and) query[{}] is {}", queriesAnd, res); @@ -79,12 +75,10 @@ public static void main(String[] args) { res = Finders.filteredFinder(" was ", "many", "child").find(text()); LOGGER.info("the result of filtered query is {}", res); - } private static String text() { return TEXT; } - } diff --git a/combinator/src/main/java/com/iluwatar/combinator/Finder.java b/combinator/src/main/java/com/iluwatar/combinator/Finder.java index 1189367dedb1..cc2d5ce84918 100644 --- a/combinator/src/main/java/com/iluwatar/combinator/Finder.java +++ b/combinator/src/main/java/com/iluwatar/combinator/Finder.java @@ -28,13 +28,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -/** - * Functional interface to find lines in text. - */ +/** Functional interface to find lines in text. */ public interface Finder { /** * The function to find lines in text. + * * @param text full tet * @return result of searching */ @@ -42,17 +41,20 @@ public interface Finder { /** * Simple implementation of function {@link #find(String)}. + * * @param word for searching * @return this */ static Finder contains(String word) { - return txt -> Stream.of(txt.split("\n")) - .filter(line -> line.toLowerCase().contains(word.toLowerCase())) - .collect(Collectors.toList()); + return txt -> + Stream.of(txt.split("\n")) + .filter(line -> line.toLowerCase().contains(word.toLowerCase())) + .collect(Collectors.toList()); } /** * combinator not. + * * @param notFinder finder to combine * @return new finder including previous finders */ @@ -66,6 +68,7 @@ default Finder not(Finder notFinder) { /** * combinator or. + * * @param orFinder finder to combine * @return new finder including previous finders */ @@ -79,16 +82,14 @@ default Finder or(Finder orFinder) { /** * combinator and. + * * @param andFinder finder to combine * @return new finder including previous finders */ default Finder and(Finder andFinder) { - return - txt -> this - .find(txt) - .stream() + return txt -> + this.find(txt).stream() .flatMap(line -> andFinder.find(line).stream()) .collect(Collectors.toList()); } - } diff --git a/combinator/src/main/java/com/iluwatar/combinator/Finders.java b/combinator/src/main/java/com/iluwatar/combinator/Finders.java index 1920c85d4260..a6f8dd7cac79 100644 --- a/combinator/src/main/java/com/iluwatar/combinator/Finders.java +++ b/combinator/src/main/java/com/iluwatar/combinator/Finders.java @@ -28,30 +28,25 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -/** - * Complex finders consisting of simple finder. - */ +/** Complex finders consisting of simple finder. */ public class Finders { - private Finders() { - } - + private Finders() {} /** * Finder to find a complex query. + * * @param query to find * @param orQuery alternative to find * @param notQuery exclude from search * @return new finder */ public static Finder advancedFinder(String query, String orQuery, String notQuery) { - return - Finder.contains(query) - .or(Finder.contains(orQuery)) - .not(Finder.contains(notQuery)); + return Finder.contains(query).or(Finder.contains(orQuery)).not(Finder.contains(notQuery)); } /** * Filtered finder looking a query with excluded queries as well. + * * @param query to find * @param excludeQueries to exclude * @return new finder @@ -63,11 +58,11 @@ public static Finder filteredFinder(String query, String... excludeQueries) { finder = finder.not(Finder.contains(q)); } return finder; - } /** * Specialized query. Every next query is looked in previous result. + * * @param queries array with queries * @return new finder */ @@ -82,6 +77,7 @@ public static Finder specializedFinder(String... queries) { /** * Expanded query. Looking for alternatives. + * * @param queries array with queries. * @return new finder */ diff --git a/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java b/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java index 02f8581df62a..4a7a6fff7a4e 100644 --- a/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java +++ b/combinator/src/test/java/com/iluwatar/combinator/CombinatorAppTest.java @@ -32,12 +32,12 @@ class CombinatorAppTest { /** * Issue: Add at least one assertion to this test case. - *

    - * Solution: Inserted assertion to check whether the execution of the main method in {@link CombinatorApp#main(String[])} - * throws an exception. + * + *

    Solution: Inserted assertion to check whether the execution of the main method in {@link + * CombinatorApp#main(String[])} throws an exception. */ @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> CombinatorApp.main(new String[]{})); + assertDoesNotThrow(() -> CombinatorApp.main(new String[] {})); } } diff --git a/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java b/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java index aa84b9350d5b..f8a23927c33e 100644 --- a/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java +++ b/combinator/src/test/java/com/iluwatar/combinator/FindersTest.java @@ -65,7 +65,6 @@ void expandedFinderTest() { assertEquals("In this kingdom by the sea;", res.get(2)); } - private String text() { return """ It was many and many a year ago, @@ -81,5 +80,4 @@ private String text() { With a love that the winged seraphs of heaven Coveted her and me."""; } - } diff --git a/command-query-responsibility-segregation/README.md b/command-query-responsibility-segregation/README.md index 4c9749d97c06..bc1de83fc681 100644 --- a/command-query-responsibility-segregation/README.md +++ b/command-query-responsibility-segregation/README.md @@ -32,6 +32,10 @@ Microsoft's documentation says > CQRS separates reads and writes into different models, using commands to update data, and queries to read data. +Architecture diagram + +![CQRS Architecture Diagram](./etc/cqrs-architecture-diagram.png) + ## Programmatic Example of CQRS Pattern in Java One way to implement the Command Query Responsibility Segregation (CQRS) pattern is to separate the read and write operations into different services. diff --git a/command-query-responsibility-segregation/etc/cqrs-architecture-diagram.png b/command-query-responsibility-segregation/etc/cqrs-architecture-diagram.png new file mode 100644 index 000000000000..7391582c245c Binary files /dev/null and b/command-query-responsibility-segregation/etc/cqrs-architecture-diagram.png differ diff --git a/command-query-responsibility-segregation/pom.xml b/command-query-responsibility-segregation/pom.xml index bc2659cdcd51..c3c277dd0b80 100644 --- a/command-query-responsibility-segregation/pom.xml +++ b/command-query-responsibility-segregation/pom.xml @@ -34,6 +34,14 @@ command-query-responsibility-segregation + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -46,14 +54,17 @@ org.hibernate hibernate-core + 5.6.15.Final org.glassfish.jaxb jaxb-runtime + 2.3.3 javax.xml.bind jaxb-api + 2.3.1 diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/app/App.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/app/App.java index 52b8ead45a74..62317638955c 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/app/App.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/app/App.java @@ -62,8 +62,8 @@ public static void main(String[] args) { commands.bookAddedToAuthor("Effective Java", 40.54, AppConstants.J_BLOCH); commands.bookAddedToAuthor("Java Puzzlers", 39.99, AppConstants.J_BLOCH); commands.bookAddedToAuthor("Java Concurrency in Practice", 29.40, AppConstants.J_BLOCH); - commands.bookAddedToAuthor("Patterns of Enterprise" - + " Application Architecture", 54.01, AppConstants.M_FOWLER); + commands.bookAddedToAuthor( + "Patterns of Enterprise" + " Application Architecture", 54.01, AppConstants.M_FOWLER); commands.bookAddedToAuthor("Domain Specific Languages", 48.89, AppConstants.M_FOWLER); commands.authorNameUpdated(AppConstants.E_EVANS, "Eric J. Evans"); @@ -86,5 +86,4 @@ public static void main(String[] args) { HibernateUtil.getSessionFactory().close(); } - } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java index 114280afb65f..9cf8c52cbb09 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.cqrs.commandes; -/** - * This interface represents the commands of the CQRS pattern. - */ +/** This interface represents the commands of the CQRS pattern. */ public interface CommandService { void authorCreated(String username, String name, String email); @@ -42,5 +40,4 @@ public interface CommandService { void bookTitleUpdated(String oldTitle, String newTitle); void bookPriceUpdated(String title, double price); - } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java index 52b32dc45f85..b4a368c98319 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/commandes/CommandServiceImpl.java @@ -140,5 +140,4 @@ public void bookPriceUpdated(String title, double price) { session.getTransaction().commit(); } } - } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java index 5753f8c2bd3a..71d266f43c35 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/constants/AppConstants.java @@ -24,14 +24,11 @@ */ package com.iluwatar.cqrs.constants; -/** - * Class to define the constants. - */ +/** Class to define the constants. */ public class AppConstants { public static final String E_EVANS = "eEvans"; public static final String J_BLOCH = "jBloch"; public static final String M_FOWLER = "mFowler"; public static final String USER_NAME = "username"; - } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Author.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Author.java index ff9192717e8e..03155d67a30b 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Author.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Author.java @@ -32,9 +32,7 @@ import lombok.Setter; import lombok.ToString; -/** - * This is an Author entity. It is used by Hibernate for persistence. - */ +/** This is an Author entity. It is used by Hibernate for persistence. */ @ToString @Getter @Setter @@ -43,6 +41,7 @@ public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; + private String username; private String name; private String email; @@ -51,8 +50,8 @@ public class Author { * Constructor. * * @param username username of the author - * @param name name of the author - * @param email email of the author + * @param name name of the author + * @param email email of the author */ public Author(String username, String name, String email) { this.username = username; @@ -60,7 +59,5 @@ public Author(String username, String name, String email) { this.email = email; } - protected Author() { - } - + protected Author() {} } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Book.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Book.java index 171c91d4da83..2e2c356528e9 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Book.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/domain/model/Book.java @@ -45,16 +45,16 @@ public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; + private String title; private double price; - @ManyToOne - private Author author; + @ManyToOne private Author author; /** * Constructor. * - * @param title title of the book - * @param price price of the book + * @param title title of the book + * @param price price of the book * @param author author of the book */ public Book(String title, double price, Author author) { @@ -63,7 +63,5 @@ public Book(String title, double price, Author author) { this.author = author; } - protected Book() { - } - + protected Book() {} } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Author.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Author.java index 35a565cfa332..58074e6dafe2 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Author.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Author.java @@ -30,9 +30,7 @@ import lombok.NoArgsConstructor; import lombok.ToString; -/** - * This is a DTO (Data Transfer Object) author, contains only useful information to be returned. - */ +/** This is a DTO (Data Transfer Object) author, contains only useful information to be returned. */ @ToString @EqualsAndHashCode @Getter @@ -43,5 +41,4 @@ public class Author { private String name; private String email; private String username; - } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Book.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Book.java index a938c65d39a9..72ce5b8c249b 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Book.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/dto/Book.java @@ -30,9 +30,7 @@ import lombok.NoArgsConstructor; import lombok.ToString; -/** - * This is a DTO (Data Transfer Object) book, contains only useful information to be returned. - */ +/** This is a DTO (Data Transfer Object) book, contains only useful information to be returned. */ @ToString @EqualsAndHashCode @Getter @@ -42,5 +40,4 @@ public class Book { private String title; private double price; - } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryService.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryService.java index c226c6f213a6..b37c1dad05a9 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryService.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryService.java @@ -29,9 +29,7 @@ import java.math.BigInteger; import java.util.List; -/** - * This interface represents the query methods of the CQRS pattern. - */ +/** This interface represents the query methods of the CQRS pattern. */ public interface QueryService { Author getAuthorByUsername(String username); @@ -43,5 +41,4 @@ public interface QueryService { BigInteger getAuthorBooksCount(String username); BigInteger getAuthorsCount(); - } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java index ed6db0b500cb..60847d1c6db6 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/queries/QueryServiceImpl.java @@ -45,9 +45,10 @@ public class QueryServiceImpl implements QueryService { public Author getAuthorByUsername(String username) { Author authorDto; try (var session = sessionFactory.openSession()) { - Query sqlQuery = session.createQuery( + Query sqlQuery = + session.createQuery( "select new com.iluwatar.cqrs.dto.Author(a.name, a.email, a.username)" - + " from com.iluwatar.cqrs.domain.model.Author a where a.username=:username"); + + " from com.iluwatar.cqrs.domain.model.Author a where a.username=:username"); sqlQuery.setParameter(AppConstants.USER_NAME, username); authorDto = sqlQuery.uniqueResult(); } @@ -58,9 +59,10 @@ public Author getAuthorByUsername(String username) { public Book getBook(String title) { Book bookDto; try (var session = sessionFactory.openSession()) { - Query sqlQuery = session.createQuery( + Query sqlQuery = + session.createQuery( "select new com.iluwatar.cqrs.dto.Book(b.title, b.price)" - + " from com.iluwatar.cqrs.domain.model.Book b where b.title=:title"); + + " from com.iluwatar.cqrs.domain.model.Book b where b.title=:title"); sqlQuery.setParameter("title", title); bookDto = sqlQuery.uniqueResult(); } @@ -71,10 +73,11 @@ public Book getBook(String title) { public List getAuthorBooks(String username) { List bookDtos; try (var session = sessionFactory.openSession()) { - Query sqlQuery = session.createQuery( + Query sqlQuery = + session.createQuery( "select new com.iluwatar.cqrs.dto.Book(b.title, b.price)" - + " from com.iluwatar.cqrs.domain.model.Author a, com.iluwatar.cqrs.domain.model.Book b " - + "where b.author.id = a.id and a.username=:username"); + + " from com.iluwatar.cqrs.domain.model.Author a, com.iluwatar.cqrs.domain.model.Book b " + + "where b.author.id = a.id and a.username=:username"); sqlQuery.setParameter(AppConstants.USER_NAME, username); bookDtos = sqlQuery.list(); } @@ -85,9 +88,11 @@ public List getAuthorBooks(String username) { public BigInteger getAuthorBooksCount(String username) { BigInteger bookcount; try (var session = sessionFactory.openSession()) { - var sqlQuery = session.createNativeQuery( - "SELECT count(b.title)" + " FROM Book b, Author a" - + " where b.author_id = a.id and a.username=:username"); + var sqlQuery = + session.createNativeQuery( + "SELECT count(b.title)" + + " FROM Book b, Author a" + + " where b.author_id = a.id and a.username=:username"); sqlQuery.setParameter(AppConstants.USER_NAME, username); bookcount = (BigInteger) sqlQuery.uniqueResult(); } @@ -103,5 +108,4 @@ public BigInteger getAuthorsCount() { } return authorcount; } - } diff --git a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java index b7f6e76f3faa..0b777fc59b1f 100644 --- a/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java +++ b/command-query-responsibility-segregation/src/main/java/com/iluwatar/cqrs/util/HibernateUtil.java @@ -54,5 +54,4 @@ private static SessionFactory buildSessionFactory() { public static SessionFactory getSessionFactory() { return SESSIONFACTORY; } - } diff --git a/command-query-responsibility-segregation/src/test/java/com/iluwatar/cqrs/IntegrationTest.java b/command-query-responsibility-segregation/src/test/java/com/iluwatar/cqrs/IntegrationTest.java index d7df072aa05f..a4fb9ed3bb7a 100644 --- a/command-query-responsibility-segregation/src/test/java/com/iluwatar/cqrs/IntegrationTest.java +++ b/command-query-responsibility-segregation/src/test/java/com/iluwatar/cqrs/IntegrationTest.java @@ -36,9 +36,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -/** - * Integration test of IQueryService and ICommandService with h2 data - */ +/** Integration test of IQueryService and ICommandService with h2 data */ class IntegrationTest { private static QueryService queryService; @@ -64,7 +62,6 @@ static void initializeAndPopulateDatabase() { commandService.bookAddedToAuthor("title2", 20, "username1"); commandService.bookPriceUpdated("title2", 30); commandService.bookTitleUpdated("title2", "new_title2"); - } @Test @@ -80,7 +77,6 @@ void testGetUpdatedAuthorByUsername() { var author = queryService.getAuthorByUsername("new_username2"); var expectedAuthor = new Author("new_name2", "new_email2", "new_username2"); assertEquals(expectedAuthor, author); - } @Test @@ -109,5 +105,4 @@ void testGetAuthorsCount() { var authorCount = queryService.getAuthorsCount(); assertEquals(new BigInteger("2"), authorCount); } - } diff --git a/command/README.md b/command/README.md index 8877f722e4b9..761267c04019 100644 --- a/command/README.md +++ b/command/README.md @@ -34,6 +34,10 @@ Wikipedia says > In object-oriented programming, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. +Sequence diagram + +![Command sequence diagram](./etc/command-sequence-diagram.png) + ## Programmatic Example of Command Pattern in Java In the Command pattern, objects are used to encapsulate all information needed to perform an action or trigger an event at a later time. This pattern is particularly useful for implementing undo functionality in applications. diff --git a/command/etc/command-sequence-diagram.png b/command/etc/command-sequence-diagram.png new file mode 100644 index 000000000000..845d0a2fde0c Binary files /dev/null and b/command/etc/command-sequence-diagram.png differ diff --git a/command/pom.xml b/command/pom.xml index d6532a8db700..83fb68cbf8e3 100644 --- a/command/pom.xml +++ b/command/pom.xml @@ -34,6 +34,14 @@ command + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/command/src/main/java/com/iluwatar/command/App.java b/command/src/main/java/com/iluwatar/command/App.java index 18dd6eee3596..a8e0edcdf45c 100644 --- a/command/src/main/java/com/iluwatar/command/App.java +++ b/command/src/main/java/com/iluwatar/command/App.java @@ -32,9 +32,9 @@ *

    Four terms always associated with the command pattern are command, receiver, invoker and * client. A command object (spell) knows about the receiver (target) and invokes a method of the * receiver. An invoker object (wizard) receives a reference to the command to be executed and - * optionally does bookkeeping about the command execution. The invoker does not know anything - * about how the command is executed. The client decides which commands to execute at which - * points. To execute a command, it passes a reference of the function to the invoker object. + * optionally does bookkeeping about the command execution. The invoker does not know anything about + * how the command is executed. The client decides which commands to execute at which points. To + * execute a command, it passes a reference of the function to the invoker object. * *

    In other words, in this example the wizard casts spells on the goblin. The wizard keeps track * of the previous spells cast, so it is easy to undo them. In addition, the wizard keeps track of diff --git a/command/src/main/java/com/iluwatar/command/Goblin.java b/command/src/main/java/com/iluwatar/command/Goblin.java index ed9ee49b85ea..911983c7b71d 100644 --- a/command/src/main/java/com/iluwatar/command/Goblin.java +++ b/command/src/main/java/com/iluwatar/command/Goblin.java @@ -24,9 +24,7 @@ */ package com.iluwatar.command; -/** - * Goblin is the target of the spells. - */ +/** Goblin is the target of the spells. */ public class Goblin extends Target { public Goblin() { diff --git a/command/src/main/java/com/iluwatar/command/Size.java b/command/src/main/java/com/iluwatar/command/Size.java index d16149c1d81e..203f190a89c4 100644 --- a/command/src/main/java/com/iluwatar/command/Size.java +++ b/command/src/main/java/com/iluwatar/command/Size.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Enumeration for target size. - */ +/** Enumeration for target size. */ @RequiredArgsConstructor public enum Size { - SMALL("small"), NORMAL("normal"); diff --git a/command/src/main/java/com/iluwatar/command/Target.java b/command/src/main/java/com/iluwatar/command/Target.java index 7afd847b2a90..5885e0c4a9a6 100644 --- a/command/src/main/java/com/iluwatar/command/Target.java +++ b/command/src/main/java/com/iluwatar/command/Target.java @@ -1,66 +1,58 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.command; - -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; - -/** - * Base class for spell targets. - */ -@Slf4j -@Getter -@Setter -public abstract class Target { - - private Size size; - - private Visibility visibility; - - /** - * Print status. - */ - public void printStatus() { - LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); - } - - /** - * Changes the size of the target. - */ - public void changeSize() { - var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; - setSize(oldSize); - } - - /** - * Changes the visibility of the target. - */ - public void changeVisibility() { - var visible = getVisibility() == Visibility.INVISIBLE - ? Visibility.VISIBLE : Visibility.INVISIBLE; - setVisibility(visible); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.command; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** Base class for spell targets. */ +@Slf4j +@Getter +@Setter +public abstract class Target { + + private Size size; + + private Visibility visibility; + + /** Print status. */ + public void printStatus() { + LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); + } + + /** Changes the size of the target. */ + public void changeSize() { + var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; + setSize(oldSize); + } + + /** Changes the visibility of the target. */ + public void changeVisibility() { + var visible = + getVisibility() == Visibility.INVISIBLE ? Visibility.VISIBLE : Visibility.INVISIBLE; + setVisibility(visible); + } +} diff --git a/command/src/main/java/com/iluwatar/command/Visibility.java b/command/src/main/java/com/iluwatar/command/Visibility.java index d1539482e0e8..a07e7876b609 100644 --- a/command/src/main/java/com/iluwatar/command/Visibility.java +++ b/command/src/main/java/com/iluwatar/command/Visibility.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Enumeration for target visibility. - */ +/** Enumeration for target visibility. */ @RequiredArgsConstructor public enum Visibility { - VISIBLE("visible"), INVISIBLE("invisible"); diff --git a/command/src/main/java/com/iluwatar/command/Wizard.java b/command/src/main/java/com/iluwatar/command/Wizard.java index 93d23cd68eff..797c41ec04ad 100644 --- a/command/src/main/java/com/iluwatar/command/Wizard.java +++ b/command/src/main/java/com/iluwatar/command/Wizard.java @@ -28,26 +28,20 @@ import java.util.LinkedList; import lombok.extern.slf4j.Slf4j; -/** - * Wizard is the invoker of the commands. - */ +/** Wizard is the invoker of the commands. */ @Slf4j public class Wizard { private final Deque undoStack = new LinkedList<>(); private final Deque redoStack = new LinkedList<>(); - /** - * Cast spell. - */ + /** Cast spell. */ public void castSpell(Runnable runnable) { runnable.run(); undoStack.offerLast(runnable); } - /** - * Undo last spell. - */ + /** Undo last spell. */ public void undoLastSpell() { if (!undoStack.isEmpty()) { var previousSpell = undoStack.pollLast(); @@ -56,9 +50,7 @@ public void undoLastSpell() { } } - /** - * Redo last spell. - */ + /** Redo last spell. */ public void redoLastSpell() { if (!redoStack.isEmpty()) { var previousSpell = redoStack.pollLast(); diff --git a/command/src/test/java/com/iluwatar/command/AppTest.java b/command/src/test/java/com/iluwatar/command/AppTest.java index 830fee8fb4dc..874bc3609d91 100644 --- a/command/src/test/java/com/iluwatar/command/AppTest.java +++ b/command/src/test/java/com/iluwatar/command/AppTest.java @@ -24,23 +24,21 @@ */ package com.iluwatar.command; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Command example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Command 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#main(String[])} - * throws an exception. + *

    Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/command/src/test/java/com/iluwatar/command/CommandTest.java b/command/src/test/java/com/iluwatar/command/CommandTest.java index 766cede95b43..5e41c884697b 100644 --- a/command/src/test/java/com/iluwatar/command/CommandTest.java +++ b/command/src/test/java/com/iluwatar/command/CommandTest.java @@ -80,17 +80,18 @@ void testCommand() { * This method asserts that the passed goblin object has the name as expectedName, size as * expectedSize and visibility as expectedVisibility. * - * @param goblin a goblin object whose state is to be verified against other - * parameters - * @param expectedName expectedName of the goblin - * @param expectedSize expected size of the goblin + * @param goblin a goblin object whose state is to be verified against other parameters + * @param expectedName expectedName of the goblin + * @param expectedSize expected size of the goblin * @param expectedVisibility expected visibility of the goblin */ - private void verifyGoblin(Goblin goblin, String expectedName, Size expectedSize, - Visibility expectedVisibility) { + private void verifyGoblin( + Goblin goblin, String expectedName, Size expectedSize, Visibility expectedVisibility) { assertEquals(expectedName, goblin.toString(), "Goblin's name must be same as expectedName"); assertEquals(expectedSize, goblin.getSize(), "Goblin's size must be same as expectedSize"); - assertEquals(expectedVisibility, goblin.getVisibility(), + assertEquals( + expectedVisibility, + goblin.getVisibility(), "Goblin's visibility must be same as expectedVisibility"); } } diff --git a/commander/README.md b/commander/README.md index 902c6af1bcad..cb5215296bcf 100644 --- a/commander/README.md +++ b/commander/README.md @@ -33,6 +33,10 @@ In plain words > The Commander pattern turns a request into a stand-alone object, allowing for the parameterization of commands, queueing of actions, and the implementation of undo operations. +Sequence diagram + +![Commander sequence diagram](./etc/commander-sequence-diagram.png) + ## Programmatic Example of Commander Pattern in Java Managing transactions across different services in a distributed system, such as an e-commerce platform with separate `Payment` and `Shipping` microservices, requires careful coordination. Using the Commander pattern in Java for transaction coordination helps ensure data consistency and reliability, even when services experience partial failures. diff --git a/commander/etc/commander-sequence-diagram.png b/commander/etc/commander-sequence-diagram.png new file mode 100644 index 000000000000..00d6eee0779c Binary files /dev/null and b/commander/etc/commander-sequence-diagram.png differ diff --git a/commander/pom.xml b/commander/pom.xml index edaeef3e8117..1dbe2dc89de5 100644 --- a/commander/pom.xml +++ b/commander/pom.xml @@ -33,6 +33,10 @@ 1.26.0-SNAPSHOT commander + + 2.0.17 + 1.5.6 + org.junit.jupiter @@ -50,7 +54,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + 3.14.0 allCases diff --git a/commander/src/main/java/com/iluwatar/commander/AppAllCases.java b/commander/src/main/java/com/iluwatar/commander/AppAllCases.java index e9a9bffc76e4..51fa1fed4f34 100644 --- a/commander/src/main/java/com/iluwatar/commander/AppAllCases.java +++ b/commander/src/main/java/com/iluwatar/commander/AppAllCases.java @@ -38,71 +38,86 @@ import com.iluwatar.commander.shippingservice.ShippingService; /** - * The {@code AppAllCases} class tests various scenarios for the microservices involved - * in the order placement process. This class consolidates previously separated cases - * into a single class to manage different success and failure scenarios for each service. + * The {@code AppAllCases} class tests various scenarios for the microservices involved in the order + * placement process. This class consolidates previously separated cases into a single class to + * manage different success and failure scenarios for each service. + * + *

    The application consists of abstract classes {@link Database} and {@link Service} which are + * extended by all the databases and services. Each service has a corresponding database to be + * updated and receives requests from an external user through the {@link Commander} class. There + * are 5 microservices: * - *

    The application consists of abstract classes {@link Database} and {@link Service} - * which are extended by all the databases and services. Each service has a corresponding - * database to be updated and receives requests from an external user through the - * {@link Commander} class. There are 5 microservices: *

      - *
    • {@link ShippingService}
    • - *
    • {@link PaymentService}
    • - *
    • {@link MessagingService}
    • - *
    • {@link EmployeeHandle}
    • - *
    • {@link QueueDatabase}
    • + *
    • {@link ShippingService} + *
    • {@link PaymentService} + *
    • {@link MessagingService} + *
    • {@link EmployeeHandle} + *
    • {@link QueueDatabase} *
    * - *

    Retries are managed using the {@link Retry} class, ensuring idempotence by performing - * checks before making requests to services and updating the {@link Order} class fields - * upon request success or definitive failure. + *

    Retries are managed using the {@link Retry} class, ensuring idempotence by performing checks + * before making requests to services and updating the {@link Order} class fields upon request + * success or definitive failure. * *

    This class tests the following scenarios: + * *

      - *
    • Employee database availability and unavailability
    • - *
    • Payment service success and failures
    • - *
    • Messaging service database availability and unavailability
    • - *
    • Queue database availability and unavailability
    • - *
    • Shipping service success and failures
    • + *
    • Employee database availability and unavailability + *
    • Payment service success and failures + *
    • Messaging service database availability and unavailability + *
    • Queue database availability and unavailability + *
    • Shipping service success and failures *
    * - *

    Each scenario is encapsulated in a corresponding method that sets up the service - * conditions and tests the order placement process. + *

    Each scenario is encapsulated in a corresponding method that sets up the service conditions + * and tests the order placement process. * - *

    The main method executes all success and failure cases to verify the application's - * behavior under different conditions. + *

    The main method executes all success and failure cases to verify the application's behavior + * under different conditions. * *

    Usage: - *

    - * {@code
    + *
    + * 
    {@code
      * public static void main(String[] args) {
      *     AppAllCases app = new AppAllCases();
      *     app.testAllScenarios();
      * }
    - * }
    - * 
    + * }
    */ - public class AppAllCases { private static final RetryParams retryParams = RetryParams.DEFAULT; private static final TimeLimits timeLimits = TimeLimits.DEFAULT; // Employee Database Fail Case void employeeDatabaseUnavailableCase() { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var ss = new ShippingService(new ShippingDatabase()); var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var qdb = new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); var user = new User("Jim", "ABCD"); var order = new Order(user, "book", 10f); @@ -114,8 +129,11 @@ void employeeDbSuccessCase() { var ps = new PaymentService(new PaymentDatabase()); var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var qdb = new QueueDatabase(); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); var user = new User("Jim", "ABCD"); @@ -127,10 +145,15 @@ void employeeDbSuccessCase() { void messagingDatabaseUnavailableCasePaymentSuccess() { var ps = new PaymentService(new PaymentDatabase()); var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ms = + new MessagingService( + new MessagingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var eh = new EmployeeHandle(new EmployeeDatabase()); var qdb = new QueueDatabase(); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); @@ -140,19 +163,34 @@ void messagingDatabaseUnavailableCasePaymentSuccess() { } void messagingDatabaseUnavailableCasePaymentError() { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var ms = + new MessagingService( + new MessagingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var eh = new EmployeeHandle(new EmployeeDatabase()); var qdb = new QueueDatabase(); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); @@ -161,21 +199,35 @@ void messagingDatabaseUnavailableCasePaymentError() { c.placeOrder(order); } - void messagingDatabaseUnavailableCasePaymentFailure() { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ms = + new MessagingService( + new MessagingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); var user = new User("Jim", "ABCD"); var order = new Order(user, "book", 10f); @@ -184,8 +236,11 @@ void messagingDatabaseUnavailableCasePaymentFailure() { // Messaging Database Success Case void messagingSuccessCase() { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var ss = new ShippingService(new ShippingDatabase()); var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); var eh = new EmployeeHandle(new EmployeeDatabase()); @@ -198,8 +253,11 @@ void messagingSuccessCase() { // Payment Database Fail Cases void paymentNotPossibleCase() { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new PaymentDetailsErrorException()); + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new PaymentDetailsErrorException()); var ss = new ShippingService(new ShippingDatabase()); var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); var eh = new EmployeeHandle(new EmployeeDatabase()); @@ -211,10 +269,15 @@ void paymentNotPossibleCase() { } void paymentDatabaseUnavailableCase() { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var ss = new ShippingService(new ShippingDatabase()); var ms = new MessagingService(new MessagingDatabase()); var eh = new EmployeeHandle(new EmployeeDatabase()); @@ -227,8 +290,11 @@ void paymentDatabaseUnavailableCase() { // Payment Database Success Case void paymentSuccessCase() { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var ss = new ShippingService(new ShippingDatabase()); var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); var eh = new EmployeeHandle(new EmployeeDatabase()); @@ -241,16 +307,26 @@ void paymentSuccessCase() { // Queue Database Fail Cases void queuePaymentTaskDatabaseUnavailableCase() { - var ps = new PaymentService(new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ps = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var ss = new ShippingService(new ShippingDatabase()); var ms = new MessagingService(new MessagingDatabase()); var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); var user = new User("Jim", "ABCD"); var order = new Order(user, "book", 10f); @@ -260,14 +336,24 @@ void queuePaymentTaskDatabaseUnavailableCase() { void queueMessageTaskDatabaseUnavailableCase() { var ps = new PaymentService(new PaymentDatabase()); var ss = new ShippingService(new ShippingDatabase()); - var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var ms = + new MessagingService( + new MessagingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var eh = new EmployeeHandle(new EmployeeDatabase()); - var qdb = new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); var user = new User("Jim", "ABCD"); var order = new Order(user, "book", 10f); @@ -278,20 +364,35 @@ void queueEmployeeDbTaskDatabaseUnavailableCase() { var ps = new PaymentService(new PaymentDatabase()); var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var qdb = - new QueueDatabase(new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); var user = new User("Jim", "ABCD"); var order = new Order(user, "book", 10f); @@ -303,8 +404,11 @@ void queueSuccessCase() { var ps = new PaymentService(new PaymentDatabase()); var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); var ms = new MessagingService(new MessagingDatabase()); - var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var qdb = new QueueDatabase(); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); var user = new User("Jim", "ABCD"); @@ -327,10 +431,16 @@ void itemUnavailableCase() { void shippingDatabaseUnavailableCase() { var ps = new PaymentService(new PaymentDatabase()); - var ss = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); + var ss = + new ShippingService( + new ShippingDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var ms = new MessagingService(new MessagingDatabase()); var eh = new EmployeeHandle(new EmployeeDatabase()); var qdb = new QueueDatabase(); @@ -357,8 +467,11 @@ void shippingSuccessCase() { var ps = new PaymentService(new PaymentDatabase()); var ss = new ShippingService(new ShippingDatabase(), new ItemUnavailableException()); var ms = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - var eh = new EmployeeHandle(new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); + var eh = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); var qdb = new QueueDatabase(); var c = new Commander(eh, ps, ss, ms, qdb, retryParams, timeLimits); var user = new User("Jim", "ABCD"); @@ -368,6 +481,7 @@ void shippingSuccessCase() { /** * Program entry point. + * * @param args command line arguments */ public static void main(String[] args) { @@ -383,7 +497,7 @@ public static void main(String[] args) { app.messagingDatabaseUnavailableCasePaymentFailure(); app.messagingSuccessCase(); - //Payment Database cases + // Payment Database cases app.paymentNotPossibleCase(); app.paymentDatabaseUnavailableCase(); app.paymentSuccessCase(); @@ -400,4 +514,4 @@ public static void main(String[] args) { app.shippingItemNotPossibleCase(); app.shippingSuccessCase(); } -} \ No newline at end of file +} diff --git a/commander/src/main/java/com/iluwatar/commander/Commander.java b/commander/src/main/java/com/iluwatar/commander/Commander.java index 24648bce3266..b25e1c128c6d 100644 --- a/commander/src/main/java/com/iluwatar/commander/Commander.java +++ b/commander/src/main/java/com/iluwatar/commander/Commander.java @@ -42,36 +42,34 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** - *

    Commander pattern is used to handle all issues that can come up while making a - * distributed transaction. The idea is to have a commander, which coordinates the execution of all - * instructions and ensures proper completion using retries and taking care of idempotence. By - * queueing instructions while they haven't been done, we can ensure a state of 'eventual - * consistency'.

    - *

    In our example, we have an e-commerce application. When the user places an order, - * the shipping service is intimated first. If the service does not respond for some reason, the - * order is not placed. If response is received, the commander then calls for the payment service to - * be intimated. If this fails, the shipping still takes place (order converted to COD) and the item - * is queued. If the queue is also found to be unavailable, the payment is noted to be not done and + * Commander pattern is used to handle all issues that can come up while making a distributed + * transaction. The idea is to have a commander, which coordinates the execution of all instructions + * and ensures proper completion using retries and taking care of idempotence. By queueing + * instructions while they haven't been done, we can ensure a state of 'eventual consistency'. + * + *

    In our example, we have an e-commerce application. When the user places an order, the shipping + * service is intimated first. If the service does not respond for some reason, the order is not + * placed. If response is received, the commander then calls for the payment service to be + * intimated. If this fails, the shipping still takes place (order converted to COD) and the item is + * queued. If the queue is also found to be unavailable, the payment is noted to be not done and * this is added to an employee database. Three types of messages are sent to the user - one, if * payment succeeds; two, if payment fails definitively; and three, if payment fails in the first * attempt. If the message is not sent, this is also queued and is added to employee db. We also * have a time limit for each instruction to be completed, after which, the instruction is not * executed, thereby ensuring that resources are not held for too long. In the rare occasion in - * which everything fails, an individual would have to step in to figure out how to solve the - * issue.

    - *

    We have abstract classes {@link Database} and {@link Service} which are extended - * by all the databases and services. Each service has a database to be updated, and receives - * request from an outside user (the {@link Commander} class here). There are 5 microservices - - * {@link ShippingService}, {@link PaymentService}, {@link MessagingService}, {@link EmployeeHandle} - * and a {@link QueueDatabase}. We use retries to execute any instruction using {@link Retry} class, - * and idempotence is ensured by going through some checks before making requests to services and - * making change in {@link Order} class fields if request succeeds or definitively fails. There is - * a single class {@link AppAllCases} that looks at the different scenarios that may be encountered - * during the placing of an order, including both success and failure cases for each service.

    + * which everything fails, an individual would have to step in to figure out how to solve the issue. + * + *

    We have abstract classes {@link Database} and {@link Service} which are extended by all the + * databases and services. Each service has a database to be updated, and receives request from an + * outside user (the {@link Commander} class here). There are 5 microservices - {@link + * ShippingService}, {@link PaymentService}, {@link MessagingService}, {@link EmployeeHandle} and a + * {@link QueueDatabase}. We use retries to execute any instruction using {@link Retry} class, and + * idempotence is ensured by going through some checks before making requests to services and making + * change in {@link Order} class fields if request succeeds or definitively fails. There is a single + * class {@link AppAllCases} that looks at the different scenarios that may be encountered during + * the placing of an order, including both success and failure cases for each service. */ - public class Commander { private final QueueDatabase queue; @@ -79,7 +77,8 @@ public class Commander { private final PaymentService paymentService; private final ShippingService shippingService; private final MessagingService messagingService; - private int queueItems = 0; //keeping track here only so don't need access to queue db to get this + private int queueItems = + 0; // keeping track here only so don't need access to queue db to get this private final int numOfRetries; private final long retryDuration; private final long queueTime; @@ -90,19 +89,24 @@ public class Commander { private boolean finalSiteMsgShown; private static final Logger LOG = LoggerFactory.getLogger(Commander.class); - //we could also have another db where it stores all orders + // we could also have another db where it stores all orders private static final String ORDER_ID = "Order {}"; private static final String REQUEST_ID = " request Id: {}"; private static final String ERROR_CONNECTING_MSG_SVC = - ": Error in connecting to messaging service "; - private static final String TRY_CONNECTING_MSG_SVC = - ": Trying to connect to messaging service.."; + ": Error in connecting to messaging service "; + private static final String TRY_CONNECTING_MSG_SVC = ": Trying to connect to messaging service.."; private static final String DEFAULT_EXCEPTION_MESSAGE = "An exception occurred"; - Commander(EmployeeHandle empDb, PaymentService paymentService, ShippingService shippingService, - MessagingService messagingService, QueueDatabase qdb, RetryParams retryParams, TimeLimits timeLimits) { + Commander( + EmployeeHandle empDb, + PaymentService paymentService, + ShippingService shippingService, + MessagingService messagingService, + QueueDatabase qdb, + RetryParams retryParams, + TimeLimits timeLimits) { this.paymentService = paymentService; this.shippingService = shippingService; this.messagingService = messagingService; @@ -124,47 +128,68 @@ void placeOrder(Order order) { private void sendShippingRequest(Order order) { var list = shippingService.exceptionsList; - Retry.Operation op = l -> { - if (!l.isEmpty()) { - if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug(ORDER_ID + ": Error in connecting to shipping service, " - + "trying again..", order.id); - } else { - LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id); - } - throw l.remove(0); - } - String transactionId = shippingService.receiveRequest(order.item, order.user.address); - //could save this transaction id in a db too - LOG.info(ORDER_ID + ": Shipping placed successfully, transaction id: {}", - order.id, transactionId); - LOG.info("Order has been placed and will be shipped to you. Please wait while we make your" - + " payment... "); - sendPaymentRequest(order); - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - if (ShippingNotPossibleException.class.isAssignableFrom(err.getClass())) { - LOG.info("Shipping is currently not possible to your address. We are working on the problem" - + " and will get back to you asap."); - finalSiteMsgShown = true; - LOG.info(ORDER_ID + ": Shipping not possible to address, trying to add problem " - + "to employee db..", order.id); - employeeHandleIssue(o); - } else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) { - LOG.info("This item is currently unavailable. We will inform you as soon as the item " - + "becomes available again."); - finalSiteMsgShown = true; - LOG.info(ORDER_ID + ": Item {}" + " unavailable, trying to add " - + "problem to employee handle..", order.id, order.item); - employeeHandleIssue(o); - } else { - LOG.info("Sorry, there was a problem in creating your order. Please try later."); - LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id); - finalSiteMsgShown = true; - } - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + Retry.Operation op = + l -> { + if (!l.isEmpty()) { + if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { + LOG.debug( + ORDER_ID + ": Error in connecting to shipping service, " + "trying again..", + order.id); + } else { + LOG.debug(ORDER_ID + ": Error in creating shipping request..", order.id); + } + throw l.remove(0); + } + String transactionId = shippingService.receiveRequest(order.item, order.user.address); + // could save this transaction id in a db too + LOG.info( + ORDER_ID + ": Shipping placed successfully, transaction id: {}", + order.id, + transactionId); + LOG.info( + "Order has been placed and will be shipped to you. Please wait while we make your" + + " payment... "); + sendPaymentRequest(order); + }; + Retry.HandleErrorIssue handleError = + (o, err) -> { + if (ShippingNotPossibleException.class.isAssignableFrom(err.getClass())) { + LOG.info( + "Shipping is currently not possible to your address. We are working on the problem" + + " and will get back to you asap."); + finalSiteMsgShown = true; + LOG.info( + ORDER_ID + + ": Shipping not possible to address, trying to add problem " + + "to employee db..", + order.id); + employeeHandleIssue(o); + } else if (ItemUnavailableException.class.isAssignableFrom(err.getClass())) { + LOG.info( + "This item is currently unavailable. We will inform you as soon as the item " + + "becomes available again."); + finalSiteMsgShown = true; + LOG.info( + ORDER_ID + + ": Item {}" + + " unavailable, trying to add " + + "problem to employee handle..", + order.id, + order.item); + employeeHandleIssue(o); + } else { + LOG.info("Sorry, there was a problem in creating your order. Please try later."); + LOG.error(ORDER_ID + ": Shipping service unavailable, order not placed..", order.id); + finalSiteMsgShown = true; + } + }; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); r.perform(list, order); } @@ -174,23 +199,30 @@ private void sendPaymentRequest(Order order) { order.paid = PaymentStatus.NOT_DONE; sendPaymentFailureMessage(order); LOG.error(ORDER_ID + ": Payment time for order over, failed and returning..", order.id); - } //if succeeded or failed, would have been dequeued, no attempt to make payment + } // if succeeded or failed, would have been dequeued, no attempt to make payment return; } var list = paymentService.exceptionsList; - var t = new Thread(() -> { - Retry.Operation op = getRetryOperation(order); - - Retry.HandleErrorIssue handleError = getRetryHandleErrorIssue(order); - - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); - } - }); + var t = + new Thread( + () -> { + Retry.Operation op = getRetryOperation(order); + + Retry.HandleErrorIssue handleError = getRetryHandleErrorIssue(order); + + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } @@ -203,8 +235,8 @@ private Retry.HandleErrorIssue getRetryHandleErrorIssue(Order order) { if (o.messageSent.equals(MessageSent.NONE_SENT)) { handlePaymentError(order.id, o); } - if (o.paid.equals(PaymentStatus.TRYING) && System - .currentTimeMillis() - o.createdTime < paymentTime) { + if (o.paid.equals(PaymentStatus.TRYING) + && System.currentTimeMillis() - o.createdTime < paymentTime) { var qt = new QueueTask(o, TaskType.PAYMENT, -1); updateQueue(qt); } @@ -214,8 +246,9 @@ private Retry.HandleErrorIssue getRetryHandleErrorIssue(Order order) { private void handlePaymentError(String orderId, Order o) { if (!finalSiteMsgShown) { - LOG.info("There was an error in payment. We are on it, and will get back to you " - + "asap. Don't worry, your order has been placed and will be shipped."); + LOG.info( + "There was an error in payment. We are on it, and will get back to you " + + "asap. Don't worry, your order has been placed and will be shipped."); finalSiteMsgShown = true; } LOG.warn(ORDER_ID + ": Payment error, going to queue..", orderId); @@ -224,9 +257,10 @@ private void handlePaymentError(String orderId, Order o) { private void handlePaymentDetailsError(String orderId, Order o) { if (!finalSiteMsgShown) { - LOG.info("There was an error in payment. Your account/card details " - + "may have been incorrect. " - + "Meanwhile, your order has been converted to COD and will be shipped."); + LOG.info( + "There was an error in payment. Your account/card details " + + "may have been incorrect. " + + "Meanwhile, your order has been converted to COD and will be shipped."); finalSiteMsgShown = true; } LOG.error(ORDER_ID + ": Payment details incorrect, failed..", orderId); @@ -238,8 +272,8 @@ private Retry.Operation getRetryOperation(Order order) { return l -> { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug(ORDER_ID + ": Error in connecting to payment service," - + " trying again..", order.id); + LOG.debug( + ORDER_ID + ": Error in connecting to payment service," + " trying again..", order.id); } else { LOG.debug(ORDER_ID + ": Error in creating payment request..", order.id); } @@ -248,8 +282,7 @@ private Retry.Operation getRetryOperation(Order order) { if (order.paid.equals(PaymentStatus.TRYING)) { var transactionId = paymentService.receiveRequest(order.price); order.paid = PaymentStatus.DONE; - LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}", - order.id, transactionId); + LOG.info(ORDER_ID + ": Payment successful, transaction Id: {}", order.id, transactionId); if (!finalSiteMsgShown) { LOG.info("Payment made successfully, thank you for shopping with us!!"); finalSiteMsgShown = true; @@ -266,92 +299,122 @@ private void updateQueue(QueueTask qt) { LOG.trace(ORDER_ID + ": Queue time for order over, failed..", qt.order.id); return; } else if (qt.taskType.equals(TaskType.PAYMENT) && !qt.order.paid.equals(PaymentStatus.TRYING) - || qt.taskType.equals(TaskType.MESSAGING) && (qt.messageType == 1 - && !qt.order.messageSent.equals(MessageSent.NONE_SENT) - || qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) - || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) + || qt.taskType.equals(TaskType.MESSAGING) + && (qt.messageType == 1 && !qt.order.messageSent.equals(MessageSent.NONE_SENT) + || qt.order.messageSent.equals(MessageSent.PAYMENT_FAIL) + || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) || qt.taskType.equals(TaskType.EMPLOYEE_DB) && qt.order.addedToEmployeeHandle) { LOG.trace(ORDER_ID + ": Not queueing task since task already done..", qt.order.id); return; } var list = queue.exceptionsList; - Thread t = new Thread(() -> { - Retry.Operation op = list1 -> { - if (!list1.isEmpty()) { - LOG.warn(ORDER_ID + ": Error in connecting to queue db, trying again..", qt.order.id); - throw list1.remove(0); - } - queue.add(qt); - queueItems++; - LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType()); - tryDoingTasksInQueue(); - }; - Retry.HandleErrorIssue handleError = (qt1, err) -> { - if (qt1.taskType.equals(TaskType.PAYMENT)) { - qt1.order.paid = PaymentStatus.NOT_DONE; - sendPaymentFailureMessage(qt1.order); - LOG.error(ORDER_ID + ": Unable to enqueue payment task," - + " payment failed..", qt1.order.id); - } - LOG.error(ORDER_ID + ": Unable to enqueue task of type {}" - + ", trying to add to employee handle..", qt1.order.id, qt1.getType()); - employeeHandleIssue(qt1.order); - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, qt); - } catch (Exception e1) { - LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); - } - }); + Thread t = + new Thread( + () -> { + Retry.Operation op = + list1 -> { + if (!list1.isEmpty()) { + LOG.warn( + ORDER_ID + ": Error in connecting to queue db, trying again..", + qt.order.id); + throw list1.remove(0); + } + queue.add(qt); + queueItems++; + LOG.info(ORDER_ID + ": {}" + " task enqueued..", qt.order.id, qt.getType()); + tryDoingTasksInQueue(); + }; + Retry.HandleErrorIssue handleError = + (qt1, err) -> { + if (qt1.taskType.equals(TaskType.PAYMENT)) { + qt1.order.paid = PaymentStatus.NOT_DONE; + sendPaymentFailureMessage(qt1.order); + LOG.error( + ORDER_ID + ": Unable to enqueue payment task," + " payment failed..", + qt1.order.id); + } + LOG.error( + ORDER_ID + + ": Unable to enqueue task of type {}" + + ", trying to add to employee handle..", + qt1.order.id, + qt1.getType()); + employeeHandleIssue(qt1.order); + }; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, qt); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } - private void tryDoingTasksInQueue() { //commander controls operations done to queue + private void tryDoingTasksInQueue() { // commander controls operations done to queue var list = queue.exceptionsList; - var t2 = new Thread(() -> { - Retry.Operation op = list1 -> { - if (!list1.isEmpty()) { - LOG.warn("Error in accessing queue db to do tasks, trying again.."); - throw list1.remove(0); - } - doTasksInQueue(); - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, null); - } catch (Exception e1) { - LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); - } - }); + var t2 = + new Thread( + () -> { + Retry.Operation op = + list1 -> { + if (!list1.isEmpty()) { + LOG.warn("Error in accessing queue db to do tasks, trying again.."); + throw list1.remove(0); + } + doTasksInQueue(); + }; + Retry.HandleErrorIssue handleError = (o, err) -> {}; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, null); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t2.start(); } private void tryDequeue() { var list = queue.exceptionsList; - var t3 = new Thread(() -> { - Retry.Operation op = list1 -> { - if (!list1.isEmpty()) { - LOG.warn("Error in accessing queue db to dequeue task, trying again.."); - throw list1.remove(0); - } - queue.dequeue(); - queueItems--; - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, null); - } catch (Exception e1) { - LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); - } - }); + var t3 = + new Thread( + () -> { + Retry.Operation op = + list1 -> { + if (!list1.isEmpty()) { + LOG.warn("Error in accessing queue db to dequeue task, trying again.."); + throw list1.remove(0); + } + queue.dequeue(); + queueItems--; + }; + Retry.HandleErrorIssue handleError = (o, err) -> {}; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, null); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t3.start(); } @@ -361,28 +424,39 @@ private void sendSuccessMessage(Order order) { return; } var list = messagingService.exceptionsList; - Thread t = new Thread(() -> { - Retry.Operation op = handleSuccessMessageRetryOperation(order); - Retry.HandleErrorIssue handleError = (o, err) -> handleSuccessMessageErrorIssue(order, o); - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); - } - }); + Thread t = + new Thread( + () -> { + Retry.Operation op = handleSuccessMessageRetryOperation(order); + Retry.HandleErrorIssue handleError = + (o, err) -> handleSuccessMessageErrorIssue(order, o); + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } private void handleSuccessMessageErrorIssue(Order order, Order o) { - if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent - .equals(MessageSent.PAYMENT_TRYING)) + if ((o.messageSent.equals(MessageSent.NONE_SENT) + || o.messageSent.equals(MessageSent.PAYMENT_TRYING)) && System.currentTimeMillis() - o.createdTime < messageTime) { var qt = new QueueTask(order, TaskType.MESSAGING, 2); updateQueue(qt); - LOG.info(ORDER_ID + ": Error in sending Payment Success message, trying to" - + " queue task and add to employee handle..", order.id); + LOG.info( + ORDER_ID + + ": Error in sending Payment Success message, trying to" + + " queue task and add to employee handle..", + order.id); employeeHandleIssue(order); } } @@ -391,11 +465,12 @@ private Retry.Operation handleSuccessMessageRetryOperation(Order order) { return l -> { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC - + "(Payment Success msg), trying again..", order.id); + LOG.debug( + ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Success msg), trying again..", + order.id); } else { - LOG.debug(ORDER_ID + ": Error in creating Payment Success" - + " messaging request..", order.id); + LOG.debug( + ORDER_ID + ": Error in creating Payment Success" + " messaging request..", order.id); } throw l.remove(0); } @@ -403,8 +478,7 @@ private Retry.Operation handleSuccessMessageRetryOperation(Order order) { && !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { var requestId = messagingService.receiveRequest(2); order.messageSent = MessageSent.PAYMENT_SUCCESSFUL; - LOG.info(ORDER_ID + ": Payment Success message sent," - + REQUEST_ID, order.id, requestId); + LOG.info(ORDER_ID + ": Payment Success message sent," + REQUEST_ID, order.id, requestId); } }; } @@ -415,38 +489,53 @@ private void sendPaymentFailureMessage(Order order) { return; } var list = messagingService.exceptionsList; - var t = new Thread(() -> { - Retry.Operation op = l -> handlePaymentFailureRetryOperation(order, l); - Retry.HandleErrorIssue handleError = (o, err) -> handlePaymentErrorIssue(order, o); - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); - } - }); + var t = + new Thread( + () -> { + Retry.Operation op = l -> handlePaymentFailureRetryOperation(order, l); + Retry.HandleErrorIssue handleError = + (o, err) -> handlePaymentErrorIssue(order, o); + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } private void handlePaymentErrorIssue(Order order, Order o) { - if ((o.messageSent.equals(MessageSent.NONE_SENT) || o.messageSent - .equals(MessageSent.PAYMENT_TRYING)) + if ((o.messageSent.equals(MessageSent.NONE_SENT) + || o.messageSent.equals(MessageSent.PAYMENT_TRYING)) && System.currentTimeMillis() - o.createdTime < messageTime) { var qt = new QueueTask(order, TaskType.MESSAGING, 0); updateQueue(qt); - LOG.warn(ORDER_ID + ": Error in sending Payment Failure message, " - + "trying to queue task and add to employee handle..", order.id); + LOG.warn( + ORDER_ID + + ": Error in sending Payment Failure message, " + + "trying to queue task and add to employee handle..", + order.id); employeeHandleIssue(o); } } - private void handlePaymentFailureRetryOperation(Order order, List l) throws IndexOutOfBoundsException, DatabaseUnavailableException { + private void handlePaymentFailureRetryOperation(Order order, List l) + throws IndexOutOfBoundsException, DatabaseUnavailableException { if (!l.isEmpty()) { if (DatabaseUnavailableException.class.isAssignableFrom(l.get(0).getClass())) { - LOG.debug(ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Failure msg), trying again..", order.id); + LOG.debug( + ORDER_ID + ERROR_CONNECTING_MSG_SVC + "(Payment Failure msg), trying again..", + order.id); } else { - LOG.debug(ORDER_ID + ": Error in creating Payment Failure" + " message request..", order.id); + LOG.debug( + ORDER_ID + ": Error in creating Payment Failure" + " message request..", order.id); } throw new IndexOutOfBoundsException(); } @@ -454,8 +543,10 @@ private void handlePaymentFailureRetryOperation(Order order, List l) && !order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { var requestId = messagingService.receiveRequest(0); order.messageSent = MessageSent.PAYMENT_FAIL; - LOG.info(ORDER_ID + ": Payment Failure message sent successfully," - + REQUEST_ID, order.id, requestId); + LOG.info( + ORDER_ID + ": Payment Failure message sent successfully," + REQUEST_ID, + order.id, + requestId); } } @@ -465,28 +556,37 @@ private void sendPaymentPossibleErrorMsg(Order order) { return; } var list = messagingService.exceptionsList; - var t = new Thread(() -> { - Retry.Operation op = l -> handlePaymentPossibleErrorMsgRetryOperation(order, l); - Retry.HandleErrorIssue handleError = (o, err) -> handlePaymentPossibleErrorMsgErrorIssue(order, o); - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); - } - }); + var t = + new Thread( + () -> { + Retry.Operation op = l -> handlePaymentPossibleErrorMsgRetryOperation(order, l); + Retry.HandleErrorIssue handleError = + (o, err) -> handlePaymentPossibleErrorMsgErrorIssue(order, o); + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } private void handlePaymentPossibleErrorMsgErrorIssue(Order order, Order o) { - if (o.messageSent.equals(MessageSent.NONE_SENT) && order.paid - .equals(PaymentStatus.TRYING) + if (o.messageSent.equals(MessageSent.NONE_SENT) + && order.paid.equals(PaymentStatus.TRYING) && System.currentTimeMillis() - o.createdTime < messageTime) { var qt = new QueueTask(order, TaskType.MESSAGING, 1); updateQueue(qt); - LOG.warn("Order {}: Error in sending Payment Error message, trying to queue task and add to employee handle..", - order.id); + LOG.warn( + "Order {}: Error in sending Payment Error message, trying to queue task and add to employee handle..", + order.id); employeeHandleIssue(o); } } @@ -495,20 +595,22 @@ private void handlePaymentPossibleErrorMsgRetryOperation(Order order, List { - Retry.Operation op = l -> { - if (!l.isEmpty()) { - LOG.warn(ORDER_ID + ": Error in connecting to employee handle," - + " trying again..", order.id); - throw l.remove(0); - } - if (!order.addedToEmployeeHandle) { - employeeDb.receiveRequest(order); - order.addedToEmployeeHandle = true; - LOG.info(ORDER_ID + ": Added order to employee database", order.id); - } - }; - Retry.HandleErrorIssue handleError = (o, err) -> { - if (!o.addedToEmployeeHandle && System - .currentTimeMillis() - order.createdTime < employeeTime) { - var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1); - updateQueue(qt); - LOG.warn(ORDER_ID + ": Error in adding to employee db," - + " trying to queue task..", order.id); - } - }; - var r = new Retry<>(op, handleError, numOfRetries, retryDuration, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - try { - r.perform(list, order); - } catch (Exception e1) { - LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); - } - }); + var t = + new Thread( + () -> { + Retry.Operation op = + l -> { + if (!l.isEmpty()) { + LOG.warn( + ORDER_ID + + ": Error in connecting to employee handle," + + " trying again..", + order.id); + throw l.remove(0); + } + if (!order.addedToEmployeeHandle) { + employeeDb.receiveRequest(order); + order.addedToEmployeeHandle = true; + LOG.info(ORDER_ID + ": Added order to employee database", order.id); + } + }; + Retry.HandleErrorIssue handleError = + (o, err) -> { + if (!o.addedToEmployeeHandle + && System.currentTimeMillis() - order.createdTime < employeeTime) { + var qt = new QueueTask(order, TaskType.EMPLOYEE_DB, -1); + updateQueue(qt); + LOG.warn( + ORDER_ID + + ": Error in adding to employee db," + + " trying to queue task..", + order.id); + } + }; + var r = + new Retry<>( + op, + handleError, + numOfRetries, + retryDuration, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + try { + r.perform(list, order); + } catch (Exception e1) { + LOG.error(DEFAULT_EXCEPTION_MESSAGE, e1); + } + }); t.start(); } private void doTasksInQueue() throws IsEmptyException, InterruptedException { if (queueItems != 0) { - var qt = queue.peek(); //this should probably be cloned here - //this is why we have retry for doTasksInQueue + var qt = queue.peek(); // this should probably be cloned here + // this is why we have retry for doTasksInQueue LOG.trace(ORDER_ID + ": Started doing task of type {}", qt.order.id, qt.getType()); if (qt.isFirstAttempt()) { qt.setFirstAttemptTime(System.currentTimeMillis()); } if (System.currentTimeMillis() - qt.getFirstAttemptTime() >= queueTaskTime) { tryDequeue(); - LOG.trace(ORDER_ID + ": This queue task of type {}" - + " does not need to be done anymore (timeout), dequeue..", qt.order.id, qt.getType()); + LOG.trace( + ORDER_ID + + ": This queue task of type {}" + + " does not need to be done anymore (timeout), dequeue..", + qt.order.id, + qt.getType()); } else { switch (qt.taskType) { case PAYMENT -> doPaymentTask(qt); @@ -583,8 +704,7 @@ private void doTasksInQueue() throws IsEmptyException, InterruptedException { private void doEmployeeDbTask(QueueTask qt) { if (qt.order.addedToEmployeeHandle) { tryDequeue(); - LOG.trace(ORDER_ID + ": This employee handle task already done," - + " dequeue..", qt.order.id); + LOG.trace(ORDER_ID + ": This employee handle task already done," + " dequeue..", qt.order.id); } else { employeeHandleIssue(qt.order); LOG.debug(ORDER_ID + ": Trying to connect to employee handle..", qt.order.id); @@ -596,11 +716,12 @@ private void doMessagingTask(QueueTask qt) { || qt.order.messageSent.equals(MessageSent.PAYMENT_SUCCESSFUL)) { tryDequeue(); LOG.trace(ORDER_ID + ": This messaging task already done, dequeue..", qt.order.id); - } else if (qt.messageType == 1 && (!qt.order.messageSent.equals(MessageSent.NONE_SENT) - || !qt.order.paid.equals(PaymentStatus.TRYING))) { + } else if (qt.messageType == 1 + && (!qt.order.messageSent.equals(MessageSent.NONE_SENT) + || !qt.order.paid.equals(PaymentStatus.TRYING))) { tryDequeue(); - LOG.trace(ORDER_ID + ": This messaging task does not need to be done," - + " dequeue..", qt.order.id); + LOG.trace( + ORDER_ID + ": This messaging task does not need to be done," + " dequeue..", qt.order.id); } else if (qt.messageType == 0) { sendPaymentFailureMessage(qt.order); LOG.debug(ORDER_ID + TRY_CONNECTING_MSG_SVC, qt.order.id); @@ -622,5 +743,4 @@ private void doPaymentTask(QueueTask qt) { LOG.debug(ORDER_ID + ": Trying to connect to payment service..", qt.order.id); } } - -} \ No newline at end of file +} diff --git a/commander/src/main/java/com/iluwatar/commander/Database.java b/commander/src/main/java/com/iluwatar/commander/Database.java index e118ed3ad894..a3e3da1693a8 100644 --- a/commander/src/main/java/com/iluwatar/commander/Database.java +++ b/commander/src/main/java/com/iluwatar/commander/Database.java @@ -32,7 +32,6 @@ * * @param T is the type of object being held by database. */ - public abstract class Database { public abstract T add(T obj) throws DatabaseUnavailableException; diff --git a/commander/src/main/java/com/iluwatar/commander/Order.java b/commander/src/main/java/com/iluwatar/commander/Order.java index 262aba6fe07a..b998a02974f1 100644 --- a/commander/src/main/java/com/iluwatar/commander/Order.java +++ b/commander/src/main/java/com/iluwatar/commander/Order.java @@ -28,11 +28,8 @@ import java.util.HashMap; import java.util.Map; -/** - * Order class holds details of the order. - */ - -public class Order { //can store all transactions ids also +/** Order class holds details of the order. */ +public class Order { // can store all transactions ids also enum PaymentStatus { NOT_DONE, @@ -56,8 +53,8 @@ enum MessageSent { private static final String ALL_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; private static final Map USED_IDS = new HashMap<>(); PaymentStatus paid; - MessageSent messageSent; //to avoid sending error msg on page and text more than once - boolean addedToEmployeeHandle; //to avoid creating more to enqueue + MessageSent messageSent; // to avoid sending error msg on page and text more than once + boolean addedToEmployeeHandle; // to avoid creating more to enqueue Order(User user, String item, float price) { this.createdTime = System.currentTimeMillis(); @@ -85,5 +82,4 @@ private String createUniqueId() { } return random.toString(); } - } diff --git a/commander/src/main/java/com/iluwatar/commander/Retry.java b/commander/src/main/java/com/iluwatar/commander/Retry.java index 71614668254b..661f479174a3 100644 --- a/commander/src/main/java/com/iluwatar/commander/Retry.java +++ b/commander/src/main/java/com/iluwatar/commander/Retry.java @@ -36,13 +36,9 @@ * * @param is the type of object passed into HandleErrorIssue as a parameter. */ - public class Retry { - /** - * Operation Interface will define method to be implemented. - */ - + /** Operation Interface will define method to be implemented. */ public interface Operation { void operation(List list) throws Exception; } @@ -52,7 +48,6 @@ public interface Operation { * * @param is the type of object to be passed into the method as parameter. */ - public interface HandleErrorIssue { void handleIssue(T obj, Exception e); } @@ -67,8 +62,12 @@ public interface HandleErrorIssue { private final Predicate test; private final List errors; - Retry(Operation op, HandleErrorIssue handleError, int maxAttempts, - long maxDelay, Predicate... ignoreTests) { + Retry( + Operation op, + HandleErrorIssue handleError, + int maxAttempts, + long maxDelay, + Predicate... ignoreTests) { this.op = op; this.handleError = handleError; this.maxAttempts = maxAttempts; @@ -82,9 +81,8 @@ public interface HandleErrorIssue { * Performing the operation with retries. * * @param list is the exception list - * @param obj is the parameter to be passed into handleIsuue method + * @param obj is the parameter to be passed into handleIsuue method */ - public void perform(List list, T obj) { do { try { @@ -94,7 +92,7 @@ public void perform(List list, T obj) { this.errors.add(e); if (this.attempts.incrementAndGet() >= this.maxAttempts || !this.test.test(e)) { this.handleError.handleIssue(obj, e); - return; //return here... don't go further + return; // return here... don't go further } try { long testDelay = @@ -102,10 +100,9 @@ public void perform(List list, T obj) { long delay = Math.min(testDelay, this.maxDelay); Thread.sleep(delay); } catch (InterruptedException f) { - //ignore + // ignore } } } while (true); } - } diff --git a/commander/src/main/java/com/iluwatar/commander/RetryParams.java b/commander/src/main/java/com/iluwatar/commander/RetryParams.java index ee3bf537f88c..9988af8a42e8 100644 --- a/commander/src/main/java/com/iluwatar/commander/RetryParams.java +++ b/commander/src/main/java/com/iluwatar/commander/RetryParams.java @@ -26,9 +26,10 @@ /** * Record to hold the parameters related to retries. + * * @param numOfRetries number of retries * @param retryDuration retry duration */ public record RetryParams(int numOfRetries, long retryDuration) { public static final RetryParams DEFAULT = new RetryParams(3, 30000L); -} \ No newline at end of file +} diff --git a/commander/src/main/java/com/iluwatar/commander/Service.java b/commander/src/main/java/com/iluwatar/commander/Service.java index e3177fc307e0..06aa9a3ce103 100644 --- a/commander/src/main/java/com/iluwatar/commander/Service.java +++ b/commander/src/main/java/com/iluwatar/commander/Service.java @@ -38,7 +38,6 @@ * for the transactions/requests, which are then sent back. These could be stored by the {@link * Commander} class in a separate database for reference (though we are not doing that here). */ - public abstract class Service { protected final Database database; diff --git a/commander/src/main/java/com/iluwatar/commander/TimeLimits.java b/commander/src/main/java/com/iluwatar/commander/TimeLimits.java index bdb7caaf4b32..9051fdf29d1a 100644 --- a/commander/src/main/java/com/iluwatar/commander/TimeLimits.java +++ b/commander/src/main/java/com/iluwatar/commander/TimeLimits.java @@ -25,16 +25,17 @@ package com.iluwatar.commander; /** - * Record to hold parameters related to time limit - * for various tasks. + * Record to hold parameters related to time limit for various tasks. + * * @param queueTime time limit for queue * @param queueTaskTime time limit for queuing task * @param paymentTime time limit for payment error message * @param messageTime time limit for message time order * @param employeeTime time limit for employee handle time */ -public record TimeLimits(long queueTime, long queueTaskTime, long paymentTime, - long messageTime, long employeeTime) { +public record TimeLimits( + long queueTime, long queueTaskTime, long paymentTime, long messageTime, long employeeTime) { - public static final TimeLimits DEFAULT = new TimeLimits(240000L, 60000L, 120000L, 150000L, 240000L); -} \ No newline at end of file + public static final TimeLimits DEFAULT = + new TimeLimits(240000L, 60000L, 120000L, 150000L, 240000L); +} diff --git a/commander/src/main/java/com/iluwatar/commander/User.java b/commander/src/main/java/com/iluwatar/commander/User.java index b6989e1d9178..e32d0024af02 100644 --- a/commander/src/main/java/com/iluwatar/commander/User.java +++ b/commander/src/main/java/com/iluwatar/commander/User.java @@ -26,9 +26,7 @@ import lombok.AllArgsConstructor; -/** - * User class contains details of user who places order. - */ +/** User class contains details of user who places order. */ @AllArgsConstructor public class User { String name; diff --git a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java index c584d2ebd212..a139ab323556 100644 --- a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeDatabase.java @@ -30,10 +30,7 @@ import java.util.HashMap; import java.util.Map; -/** - * The Employee Database is where orders which have encountered some issue(s) are added. - */ - +/** The Employee Database is where orders which have encountered some issue(s) are added. */ public class EmployeeDatabase extends Database { private final Map data = new HashMap<>(); diff --git a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java index 441ce920feae..745b71d90114 100644 --- a/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java +++ b/commander/src/main/java/com/iluwatar/commander/employeehandle/EmployeeHandle.java @@ -32,7 +32,6 @@ * The EmployeeHandle class is the middle-man between {@link com.iluwatar.commander.Commander} and * {@link EmployeeDatabase}. */ - public class EmployeeHandle extends Service { public EmployeeHandle(EmployeeDatabase db, Exception... exc) { @@ -47,9 +46,8 @@ protected String updateDb(Object... parameters) throws DatabaseUnavailableExcept var o = (Order) parameters[0]; if (database.get(o.id) == null) { database.add(o); - return o.id; //true rcvd - change addedToEmployeeHandle to true else don't do anything + return o.id; // true rcvd - change addedToEmployeeHandle to true else don't do anything } return null; } - } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java index 5d368180ce39..51f27832f481 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/DatabaseUnavailableException.java @@ -28,7 +28,6 @@ * DatabaseUnavailableException is thrown when database is unavailable and nothing can be added or * retrieved. */ - public class DatabaseUnavailableException extends Exception { private static final long serialVersionUID = 2459603L; } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java index 3ab6b22b3387..5cb06214f527 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/IsEmptyException.java @@ -24,10 +24,7 @@ */ package com.iluwatar.commander.exceptions; -/** - * IsEmptyException is thrown when it is attempted to dequeue from an empty queue. - */ - +/** IsEmptyException is thrown when it is attempted to dequeue from an empty queue. */ public class IsEmptyException extends Exception { private static final long serialVersionUID = 123546L; } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java index 4ff39543ef72..9d6f2849ff77 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/ItemUnavailableException.java @@ -24,10 +24,7 @@ */ package com.iluwatar.commander.exceptions; -/** - * ItemUnavailableException is thrown when item is not available for shipping. - */ - +/** ItemUnavailableException is thrown when item is not available for shipping. */ public class ItemUnavailableException extends Exception { private static final long serialVersionUID = 575940L; } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java index 844dab389941..1406b43de2b1 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/PaymentDetailsErrorException.java @@ -28,7 +28,6 @@ * PaymentDetailsErrorException is thrown when the details entered are incorrect or payment cannot * be made with the details given. */ - public class PaymentDetailsErrorException extends Exception { private static final long serialVersionUID = 867203L; } diff --git a/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java b/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java index ac6267dfc42d..51036aaea3aa 100644 --- a/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java +++ b/commander/src/main/java/com/iluwatar/commander/exceptions/ShippingNotPossibleException.java @@ -28,7 +28,6 @@ * ShippingNotPossibleException is thrown when the address entered cannot be shipped to by service * currently for some reason. */ - public class ShippingNotPossibleException extends Exception { private static final long serialVersionUID = 342055L; } diff --git a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java index e4a000f1cf62..b6af7d7b52d2 100644 --- a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingDatabase.java @@ -29,10 +29,7 @@ import java.util.Hashtable; import java.util.Map; -/** - * The MessagingDatabase is where the MessageRequest is added. - */ - +/** The MessagingDatabase is where the MessageRequest is added. */ public class MessagingDatabase extends Database { private final Map data = new Hashtable<>(); @@ -45,5 +42,4 @@ public MessageRequest add(MessageRequest r) { public MessageRequest get(String requestId) { return data.get(requestId); } - } diff --git a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java index e00bde4997b6..44b58e5e07ba 100644 --- a/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java +++ b/commander/src/main/java/com/iluwatar/commander/messagingservice/MessagingService.java @@ -26,7 +26,6 @@ import com.iluwatar.commander.Service; import com.iluwatar.commander.exceptions.DatabaseUnavailableException; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; /** @@ -34,7 +33,6 @@ * In case an error is encountered in payment and this service is found to be unavailable, the order * is added to the {@link com.iluwatar.commander.employeehandle.EmployeeDatabase}. */ - @Slf4j public class MessagingService extends Service { @@ -50,9 +48,7 @@ public MessagingService(MessagingDatabase db, Exception... exc) { super(db, exc); } - /** - * Public method which will receive request from {@link com.iluwatar.commander.Commander}. - */ + /** Public method which will receive request from {@link com.iluwatar.commander.Commander}. */ public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { var messageToSend = (int) parameters[0]; var id = generateId(); @@ -61,7 +57,7 @@ public String receiveRequest(Object... parameters) throws DatabaseUnavailableExc msg = MessageToSend.PAYMENT_FAIL; } else if (messageToSend == 1) { msg = MessageToSend.PAYMENT_TRYING; - } else { //messageToSend == 2 + } else { // messageToSend == 2 msg = MessageToSend.PAYMENT_SUCCESSFUL; } var req = new MessageRequest(id, msg); @@ -70,8 +66,8 @@ public String receiveRequest(Object... parameters) throws DatabaseUnavailableExc protected String updateDb(Object... parameters) throws DatabaseUnavailableException { var req = (MessageRequest) parameters[0]; - if (this.database.get(req.reqId) == null) { //idempotence, in case db fails here - database.add(req); //if successful: + if (this.database.get(req.reqId) == null) { // idempotence, in case db fails here + database.add(req); // if successful: LOGGER.info(sendMessage(req.msg)); return req.reqId; } diff --git a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java index 3c2a6129ba8c..354c1c958cae 100644 --- a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentDatabase.java @@ -29,12 +29,10 @@ import java.util.Hashtable; import java.util.Map; -/** - * PaymentDatabase is where the PaymentRequest is added, along with details. - */ +/** PaymentDatabase is where the PaymentRequest is added, along with details. */ public class PaymentDatabase extends Database { - //0-fail, 1-error, 2-success + // 0-fail, 1-error, 2-success private final Map data = new Hashtable<>(); @Override @@ -46,5 +44,4 @@ public PaymentRequest add(PaymentRequest r) { public PaymentRequest get(String requestId) { return data.get(requestId); } - } diff --git a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java index 28fda2eb2106..953c46165351 100644 --- a/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java +++ b/commander/src/main/java/com/iluwatar/commander/paymentservice/PaymentService.java @@ -32,7 +32,6 @@ * The PaymentService class receives request from the {@link com.iluwatar.commander.Commander} and * adds to the {@link PaymentDatabase}. */ - public class PaymentService extends Service { @RequiredArgsConstructor @@ -46,12 +45,9 @@ public PaymentService(PaymentDatabase db, Exception... exc) { super(db, exc); } - /** - * Public method which will receive request from {@link com.iluwatar.commander.Commander}. - */ - + /** Public method which will receive request from {@link com.iluwatar.commander.Commander}. */ public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { - //it could also be sending an userid, payment details here or something, not added here + // it could also be sending an userid, payment details here or something, not added here var id = generateId(); var req = new PaymentRequest(id, (float) parameters[0]); return updateDb(req); diff --git a/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java b/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java index b8189e6f0025..41b54a276c5c 100644 --- a/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/queue/QueueDatabase.java @@ -29,10 +29,7 @@ import java.util.ArrayList; import java.util.List; -/** - * QueueDatabase id where the instructions to be implemented are queued. - */ - +/** QueueDatabase id where the instructions to be implemented are queued. */ public class QueueDatabase extends Database { private final Queue data; @@ -47,16 +44,15 @@ public QueueDatabase(Exception... exc) { public QueueTask add(QueueTask t) { data.enqueue(t); return t; - //even if same thing queued twice, it is taken care of in other dbs + // even if same thing queued twice, it is taken care of in other dbs } /** * peek method returns object at front without removing it from queue. * * @return object at front of queue - * @throws IsEmptyException if queue is empty + * @throws IsEmptyException if queue is empty */ - public QueueTask peek() throws IsEmptyException { return this.data.peek(); } @@ -65,9 +61,8 @@ public QueueTask peek() throws IsEmptyException { * dequeue method removes the object at front and returns it. * * @return object at front of queue - * @throws IsEmptyException if queue is empty + * @throws IsEmptyException if queue is empty */ - public QueueTask dequeue() throws IsEmptyException { return this.data.dequeue(); } @@ -76,5 +71,4 @@ public QueueTask dequeue() throws IsEmptyException { public QueueTask get(String taskId) { return null; } - } diff --git a/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java b/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java index e0194508dba6..aedf55a9e1a3 100644 --- a/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java +++ b/commander/src/main/java/com/iluwatar/commander/queue/QueueTask.java @@ -29,15 +29,11 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; -/** - * QueueTask object is the object enqueued in queue. - */ +/** QueueTask object is the object enqueued in queue. */ @RequiredArgsConstructor public class QueueTask { - /** - * TaskType is the type of task to be done. - */ + /** TaskType is the type of task to be done. */ public enum TaskType { MESSAGING, PAYMENT, @@ -46,13 +42,11 @@ public enum TaskType { public final Order order; public final TaskType taskType; - public final int messageType; //0-fail, 1-error, 2-success - + public final int messageType; // 0-fail, 1-error, 2-success + /*we could have varargs Object instead to pass in any parameter instead of just message type but keeping it simple here*/ - @Getter - @Setter - private long firstAttemptTime = -1L; //when first time attempt made to do task + @Getter @Setter private long firstAttemptTime = -1L; // when first time attempt made to do task /** * getType method. @@ -76,4 +70,4 @@ public String getType() { public boolean isFirstAttempt() { return this.firstAttemptTime == -1L; } -} \ No newline at end of file +} diff --git a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java index f1a11d591c69..3906834279b8 100644 --- a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java +++ b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingDatabase.java @@ -29,10 +29,7 @@ import java.util.Hashtable; import java.util.Map; -/** - * ShippingDatabase is where the ShippingRequest objects are added. - */ - +/** ShippingDatabase is where the ShippingRequest objects are added. */ public class ShippingDatabase extends Database { private final Map data = new Hashtable<>(); @@ -45,5 +42,4 @@ public ShippingRequest add(ShippingRequest r) { public ShippingRequest get(String trasnactionId) { return data.get(trasnactionId); } - } diff --git a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java index 8144fde90810..cc97159b763f 100644 --- a/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java +++ b/commander/src/main/java/com/iluwatar/commander/shippingservice/ShippingService.java @@ -32,7 +32,6 @@ * ShippingService class receives request from {@link com.iluwatar.commander.Commander} class and * adds it to the {@link ShippingDatabase}. */ - public class ShippingService extends Service { @AllArgsConstructor @@ -46,10 +45,7 @@ public ShippingService(ShippingDatabase db, Exception... exc) { super(db, exc); } - /** - * Public method which will receive request from {@link com.iluwatar.commander.Commander}. - */ - + /** Public method which will receive request from {@link com.iluwatar.commander.Commander}. */ public String receiveRequest(Object... parameters) throws DatabaseUnavailableException { var id = generateId(); var item = (String) parameters[0]; diff --git a/commander/src/test/java/com/iluwatar/commander/CommanderTest.java b/commander/src/test/java/com/iluwatar/commander/CommanderTest.java index 87d065a30537..664268965bf7 100644 --- a/commander/src/test/java/com/iluwatar/commander/CommanderTest.java +++ b/commander/src/test/java/com/iluwatar/commander/CommanderTest.java @@ -24,6 +24,8 @@ */ package com.iluwatar.commander; +import static org.junit.jupiter.api.Assertions.assertFalse; + import com.iluwatar.commander.employeehandle.EmployeeDatabase; import com.iluwatar.commander.employeehandle.EmployeeHandle; import com.iluwatar.commander.exceptions.DatabaseUnavailableException; @@ -37,589 +39,645 @@ import com.iluwatar.commander.queue.QueueDatabase; import com.iluwatar.commander.shippingservice.ShippingDatabase; import com.iluwatar.commander.shippingservice.ShippingService; -import org.junit.jupiter.api.Test; -import org.junit.platform.commons.util.StringUtils; import java.util.ArrayList; import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertFalse; +import org.junit.jupiter.api.Test; +import org.junit.platform.commons.util.StringUtils; class CommanderTest { - private final RetryParams retryParams = new RetryParams(1, 1_000L); - - private final TimeLimits timeLimits = new TimeLimits(1L, 1000L, 6000L, 5000L, 2000L); - - private static final List exceptionList = new ArrayList<>(); - - private static final AppAllCases appAllCases = new AppAllCases(); - - static { - exceptionList.add(new DatabaseUnavailableException()); - exceptionList.add(new ShippingNotPossibleException()); - exceptionList.add(new ItemUnavailableException()); - exceptionList.add(new PaymentDetailsErrorException()); - exceptionList.add(new IllegalStateException()); - } - - private Commander buildCommanderObject() { - return buildCommanderObject(false); - } - - private Commander buildCommanderObject(boolean nonPaymentException) { - PaymentService paymentService = new PaymentService - (new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - - ShippingService shippingService; - MessagingService messagingService; - if (nonPaymentException) { - shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); - messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - - } else { - shippingService = new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); - messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - - } - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, retryParams, timeLimits); + private final RetryParams retryParams = new RetryParams(1, 1_000L); + + private final TimeLimits timeLimits = new TimeLimits(1L, 1000L, 6000L, 5000L, 2000L); + + private static final List exceptionList = new ArrayList<>(); + + private static final AppAllCases appAllCases = new AppAllCases(); + + static { + exceptionList.add(new DatabaseUnavailableException()); + exceptionList.add(new ShippingNotPossibleException()); + exceptionList.add(new ItemUnavailableException()); + exceptionList.add(new PaymentDetailsErrorException()); + exceptionList.add(new IllegalStateException()); + } + + private Commander buildCommanderObject() { + return buildCommanderObject(false); + } + + private Commander buildCommanderObject(boolean nonPaymentException) { + PaymentService paymentService = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + + ShippingService shippingService; + MessagingService messagingService; + if (nonPaymentException) { + shippingService = + new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); + messagingService = + new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + + } else { + shippingService = + new ShippingService(new ShippingDatabase(), new DatabaseUnavailableException()); + messagingService = + new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); } - - private Commander buildCommanderObjectVanilla() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException(), - new DatabaseUnavailableException(), new DatabaseUnavailableException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, retryParams, timeLimits); - } - - private Commander buildCommanderObjectUnknownException() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase(), new IllegalStateException()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new IllegalStateException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new IllegalStateException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, retryParams, timeLimits); + var employeeHandle = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectVanilla() { + PaymentService paymentService = + new PaymentService( + new PaymentDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase()); + var employeeHandle = + new EmployeeHandle( + new EmployeeDatabase(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + var qdb = + new QueueDatabase( + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException(), + new DatabaseUnavailableException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectUnknownException() { + PaymentService paymentService = + new PaymentService(new PaymentDatabase(), new IllegalStateException()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase()); + var employeeHandle = new EmployeeHandle(new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase(new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectNoPaymentException1() { + PaymentService paymentService = new PaymentService(new PaymentDatabase()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = new MessagingService(new MessagingDatabase()); + var employeeHandle = new EmployeeHandle(new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase(new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectNoPaymentException2() { + PaymentService paymentService = new PaymentService(new PaymentDatabase()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = + new MessagingService(new MessagingDatabase(), new IllegalStateException()); + var employeeHandle = new EmployeeHandle(new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase(new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectNoPaymentException3() { + PaymentService paymentService = new PaymentService(new PaymentDatabase()); + var shippingService = new ShippingService(new ShippingDatabase()); + var messagingService = + new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); + var employeeHandle = new EmployeeHandle(new EmployeeDatabase(), new IllegalStateException()); + var qdb = new QueueDatabase(new DatabaseUnavailableException(), new IllegalStateException()); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + qdb, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectWithDB() { + return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + private Commander buildCommanderObjectWithDB( + boolean includeException, boolean includeDBException, Exception e) { + var l = includeDBException ? new DatabaseUnavailableException() : e; + PaymentService paymentService; + ShippingService shippingService; + MessagingService messagingService; + EmployeeHandle employeeHandle; + if (includeException) { + paymentService = new PaymentService(new PaymentDatabase(), l); + shippingService = new ShippingService(new ShippingDatabase(), l); + messagingService = new MessagingService(new MessagingDatabase(), l); + employeeHandle = new EmployeeHandle(new EmployeeDatabase(), l); + } else { + paymentService = new PaymentService(null); + shippingService = new ShippingService(null); + messagingService = new MessagingService(null); + employeeHandle = new EmployeeHandle(null); } - private Commander buildCommanderObjectNoPaymentException1() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new IllegalStateException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new IllegalStateException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, retryParams, timeLimits); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + null, + retryParams, + timeLimits); + } + + private Commander buildCommanderObjectWithoutDB() { + return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + private Commander buildCommanderObjectWithoutDB( + boolean includeException, boolean includeDBException, Exception e) { + var l = includeDBException ? new DatabaseUnavailableException() : e; + PaymentService paymentService; + ShippingService shippingService; + MessagingService messagingService; + EmployeeHandle employeeHandle; + if (includeException) { + paymentService = new PaymentService(null, l); + shippingService = new ShippingService(null, l); + messagingService = new MessagingService(null, l); + employeeHandle = new EmployeeHandle(null, l); + } else { + paymentService = new PaymentService(null); + shippingService = new ShippingService(null); + messagingService = new MessagingService(null); + employeeHandle = new EmployeeHandle(null); } - private Commander buildCommanderObjectNoPaymentException2() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase(), new IllegalStateException()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new IllegalStateException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new IllegalStateException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, retryParams, timeLimits); + return new Commander( + employeeHandle, + paymentService, + shippingService, + messagingService, + null, + retryParams, + timeLimits); + } + + @Test + void testPlaceOrderVanilla() { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectVanilla(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectNoPaymentException3() { - PaymentService paymentService = new PaymentService - (new PaymentDatabase()); - var shippingService = new ShippingService(new ShippingDatabase()); - var messagingService = new MessagingService(new MessagingDatabase(), new DatabaseUnavailableException()); - var employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), new IllegalStateException()); - var qdb = new QueueDatabase - (new DatabaseUnavailableException(), new IllegalStateException()); - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, qdb, retryParams, timeLimits); + } + + @Test + void testPlaceOrder() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObject(true); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectWithDB() { - return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + @Test + void testPlaceOrder2() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObject(false); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectWithDB(boolean includeException, boolean includeDBException, Exception e) { - var l = includeDBException ? new DatabaseUnavailableException() : e; - PaymentService paymentService; - ShippingService shippingService; - MessagingService messagingService; - EmployeeHandle employeeHandle; - if (includeException) { - paymentService = new PaymentService - (new PaymentDatabase(), l); - shippingService = new ShippingService(new ShippingDatabase(), l); - messagingService = new MessagingService(new MessagingDatabase(), l); - employeeHandle = new EmployeeHandle - (new EmployeeDatabase(), l); - } else { - paymentService = new PaymentService - (null); - shippingService = new ShippingService(null); - messagingService = new MessagingService(null); - employeeHandle = new EmployeeHandle - (null); - } - - - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, null, retryParams, timeLimits); + } + + @Test + void testPlaceOrderNoException1() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException1(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectWithoutDB() { - return buildCommanderObjectWithoutDB(false, false, new IllegalStateException()); + } + + @Test + void testPlaceOrderNoException2() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException2(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - private Commander buildCommanderObjectWithoutDB(boolean includeException, boolean includeDBException, Exception e) { - var l = includeDBException ? new DatabaseUnavailableException() : e; - PaymentService paymentService; - ShippingService shippingService; - MessagingService messagingService; - EmployeeHandle employeeHandle; - if (includeException) { - paymentService = new PaymentService - (null, l); - shippingService = new ShippingService(null, l); - messagingService = new MessagingService(null, l); - employeeHandle = new EmployeeHandle - (null, l); - } else { - paymentService = new PaymentService - (null); - shippingService = new ShippingService(null); - messagingService = new MessagingService(null); - employeeHandle = new EmployeeHandle - (null); - } - - - return new Commander(employeeHandle, paymentService, shippingService, - messagingService, null, retryParams, timeLimits); + } + + @Test + void testPlaceOrderNoException3() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException3(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderVanilla() { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectVanilla(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderNoException4() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + Commander c = buildCommanderObjectNoPaymentException3(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + c.placeOrder(order); + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrder() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObject(true); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderUnknownException() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectUnknownException(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrder2() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObject(false); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderShortDuration() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObject(true); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderNoException1() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectNoPaymentException1(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderShortDuration2() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObject(false); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderNoException2() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectNoPaymentException2(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderNoExceptionShortMsgDuration() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectNoPaymentException1(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderNoException3() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectNoPaymentException3(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderNoExceptionShortQueueDuration() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectUnknownException(); + var order = new Order(new User("K", "J"), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderNoException4() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - Commander c = buildCommanderObjectNoPaymentException3(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - c.placeOrder(order); - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderWithDatabase() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectWithDB(); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderUnknownException() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - long messageTime = timeLimits.messageTime(); - long employeeTime = timeLimits.employeeTime(); - long queueTime = timeLimits.queueTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectUnknownException(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + } + + @Test + void testPlaceOrderWithDatabaseAndExceptions() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + + for (Exception e : exceptionList) { + + Commander c = buildCommanderObjectWithDB(true, true, e); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderShortDuration() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - long messageTime = timeLimits.messageTime(); - long employeeTime = timeLimits.employeeTime(); - long queueTime = timeLimits.queueTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObject(true); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithDB(true, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderShortDuration2() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - long messageTime = timeLimits.messageTime(); - long employeeTime = timeLimits.employeeTime(); - long queueTime = timeLimits.queueTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObject(false); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithDB(false, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderNoExceptionShortMsgDuration() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - long messageTime = timeLimits.messageTime(); - long employeeTime = timeLimits.employeeTime(); - long queueTime = timeLimits.queueTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectNoPaymentException1(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithDB(false, true, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } + } } - - @Test - void testPlaceOrderNoExceptionShortQueueDuration() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - long messageTime = timeLimits.messageTime(); - long employeeTime = timeLimits.employeeTime(); - long queueTime = timeLimits.queueTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectUnknownException(); - var order = new Order(new User("K", "J"), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + } + + @Test + void testPlaceOrderWithoutDatabase() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + Commander c = buildCommanderObjectWithoutDB(); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); + } } - - @Test - void testPlaceOrderWithDatabase() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - long messageTime = timeLimits.messageTime(); - long employeeTime = timeLimits.employeeTime(); - long queueTime = timeLimits.queueTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectWithDB(); - var order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + } + + @Test + void testPlaceOrderWithoutDatabaseAndExceptions() throws Exception { + long paymentTime = timeLimits.paymentTime(); + long queueTaskTime = timeLimits.queueTaskTime(); + long messageTime = timeLimits.messageTime(); + long employeeTime = timeLimits.employeeTime(); + long queueTime = timeLimits.queueTime(); + for (double d = 0.1; d < 2; d = d + 0.1) { + paymentTime *= d; + queueTaskTime *= d; + messageTime *= d; + employeeTime *= d; + queueTime *= d; + + for (Exception e : exceptionList) { + + Commander c = buildCommanderObjectWithoutDB(true, true, e); + var order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderWithDatabaseAndExceptions() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - long messageTime = timeLimits.messageTime(); - long employeeTime = timeLimits.employeeTime(); - long queueTime = timeLimits.queueTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - - for (Exception e : exceptionList) { - - Commander c = buildCommanderObjectWithDB(true, true, e); - var order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithDB(true, false, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithDB(false, false, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithDB(false, true, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + c = buildCommanderObjectWithoutDB(true, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderWithoutDatabase() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - long messageTime = timeLimits.messageTime(); - long employeeTime = timeLimits.employeeTime(); - long queueTime = timeLimits.queueTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - Commander c = buildCommanderObjectWithoutDB(); - var order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } + c = buildCommanderObjectWithoutDB(false, false, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } - } - @Test - void testPlaceOrderWithoutDatabaseAndExceptions() throws Exception { - long paymentTime = timeLimits.paymentTime(); - long queueTaskTime = timeLimits.queueTaskTime(); - long messageTime = timeLimits.messageTime(); - long employeeTime = timeLimits.employeeTime(); - long queueTime = timeLimits.queueTime(); - for (double d = 0.1; d < 2; d = d + 0.1) { - paymentTime *= d; - queueTaskTime *= d; - messageTime *= d; - employeeTime *= d; - queueTime *= d; - - for (Exception e : exceptionList) { - - Commander c = buildCommanderObjectWithoutDB(true, true, e); - var order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithoutDB(true, false, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithoutDB(false, false, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - - c = buildCommanderObjectWithoutDB(false, true, e); - order = new Order(new User("K", null), "pen", 1f); - for (Order.MessageSent ms : Order.MessageSent.values()) { - c.placeOrder(order); - assertFalse(StringUtils.isBlank(order.id)); - } - } + c = buildCommanderObjectWithoutDB(false, true, e); + order = new Order(new User("K", null), "pen", 1f); + for (Order.MessageSent ms : Order.MessageSent.values()) { + c.placeOrder(order); + assertFalse(StringUtils.isBlank(order.id)); } + } } - - @Test - void testAllSuccessCases() throws Exception{ - appAllCases.employeeDbSuccessCase(); - appAllCases.messagingSuccessCase(); - appAllCases.paymentSuccessCase(); - appAllCases.queueSuccessCase(); - appAllCases.shippingSuccessCase(); - } - - @Test - void testAllUnavailableCase() throws Exception { - appAllCases.employeeDatabaseUnavailableCase(); - appAllCases.messagingDatabaseUnavailableCasePaymentSuccess(); - appAllCases.messagingDatabaseUnavailableCasePaymentError(); - appAllCases.messagingDatabaseUnavailableCasePaymentFailure(); - appAllCases.paymentDatabaseUnavailableCase(); - appAllCases.queuePaymentTaskDatabaseUnavailableCase(); - appAllCases.queueMessageTaskDatabaseUnavailableCase(); - appAllCases.queueEmployeeDbTaskDatabaseUnavailableCase(); - appAllCases.itemUnavailableCase(); - appAllCases.shippingDatabaseUnavailableCase(); - } - - @Test - void testAllNotPossibleCase() throws Exception { - appAllCases.paymentNotPossibleCase(); - appAllCases.shippingItemNotPossibleCase(); - } -} \ No newline at end of file + } + + @Test + void testAllSuccessCases() throws Exception { + appAllCases.employeeDbSuccessCase(); + appAllCases.messagingSuccessCase(); + appAllCases.paymentSuccessCase(); + appAllCases.queueSuccessCase(); + appAllCases.shippingSuccessCase(); + } + + @Test + void testAllUnavailableCase() throws Exception { + appAllCases.employeeDatabaseUnavailableCase(); + appAllCases.messagingDatabaseUnavailableCasePaymentSuccess(); + appAllCases.messagingDatabaseUnavailableCasePaymentError(); + appAllCases.messagingDatabaseUnavailableCasePaymentFailure(); + appAllCases.paymentDatabaseUnavailableCase(); + appAllCases.queuePaymentTaskDatabaseUnavailableCase(); + appAllCases.queueMessageTaskDatabaseUnavailableCase(); + appAllCases.queueEmployeeDbTaskDatabaseUnavailableCase(); + appAllCases.itemUnavailableCase(); + appAllCases.shippingDatabaseUnavailableCase(); + } + + @Test + void testAllNotPossibleCase() throws Exception { + appAllCases.paymentNotPossibleCase(); + appAllCases.shippingItemNotPossibleCase(); + } +} diff --git a/commander/src/test/java/com/iluwatar/commander/RetryTest.java b/commander/src/test/java/com/iluwatar/commander/RetryTest.java index c74fb94d0f9d..389a45c2a52f 100644 --- a/commander/src/test/java/com/iluwatar/commander/RetryTest.java +++ b/commander/src/test/java/com/iluwatar/commander/RetryTest.java @@ -40,33 +40,47 @@ class RetryTest { @Test void performTest() { - Retry.Operation op = (l) -> { - if (!l.isEmpty()) { - throw l.remove(0); - } - }; - Retry.HandleErrorIssue handleError = (o, e) -> { - }; - var r1 = new Retry<>(op, handleError, 3, 30000, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); - var r2 = new Retry<>(op, handleError, 3, 30000, - e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + Retry.Operation op = + (l) -> { + if (!l.isEmpty()) { + throw l.remove(0); + } + }; + Retry.HandleErrorIssue handleError = (o, e) -> {}; + var r1 = + new Retry<>( + op, + handleError, + 3, + 30000, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); + var r2 = + new Retry<>( + op, + handleError, + 3, + 30000, + e -> DatabaseUnavailableException.class.isAssignableFrom(e.getClass())); var user = new User("Jim", "ABCD"); var order = new Order(user, "book", 10f); - var arr1 = new ArrayList<>(List.of(new ItemUnavailableException(), new DatabaseUnavailableException())); + var arr1 = + new ArrayList<>( + List.of(new ItemUnavailableException(), new DatabaseUnavailableException())); try { r1.perform(arr1, order); } catch (Exception e1) { LOG.error("An exception occurred", e1); } - var arr2 = new ArrayList<>(List.of(new DatabaseUnavailableException(), new ItemUnavailableException())); + var arr2 = + new ArrayList<>( + List.of(new DatabaseUnavailableException(), new ItemUnavailableException())); try { r2.perform(arr2, order); } catch (Exception e1) { LOG.error("An exception occurred", e1); } - //r1 stops at ItemUnavailableException, r2 retries because it encounters DatabaseUnavailableException + // r1 stops at ItemUnavailableException, r2 retries because it encounters + // DatabaseUnavailableException assertTrue(arr1.size() == 1 && arr2.isEmpty()); } - } diff --git a/component/README.md b/component/README.md index 638133265aa3..6ab6284e280e 100644 --- a/component/README.md +++ b/component/README.md @@ -30,6 +30,10 @@ In plain words > The component design pattern provides a single attribute to be accessible by numerous objects without requiring the existence of a relationship between the objects themselves. +Architecture diagram + +![Component architecture diagram](./etc/component-architecture-diagram.png) + ## Programmatic Example of Component Pattern in Java The `App` class creates a demonstration of the use of the component pattern by creating two different objects which inherit a small collection of individual components that are modifiable. diff --git a/component/etc/component-architecture-diagram.png b/component/etc/component-architecture-diagram.png new file mode 100644 index 000000000000..8256d5719c18 Binary files /dev/null and b/component/etc/component-architecture-diagram.png differ diff --git a/component/pom.xml b/component/pom.xml index e71b1cc59716..e666e283489b 100644 --- a/component/pom.xml +++ b/component/pom.xml @@ -37,9 +37,17 @@ component + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter - junit-jupiter-api + junit-jupiter-engine test diff --git a/component/src/main/java/com/iluwatar/component/App.java b/component/src/main/java/com/iluwatar/component/App.java index e6e005a1b553..9401483e92e2 100644 --- a/component/src/main/java/com/iluwatar/component/App.java +++ b/component/src/main/java/com/iluwatar/component/App.java @@ -28,19 +28,18 @@ import lombok.extern.slf4j.Slf4j; /** - * The component design pattern is a common game design structure. This pattern is often - * used to reduce duplication of code as well as to improve maintainability. - * In this implementation, component design pattern has been used to provide two game - * objects with varying component interfaces (features). As opposed to copying and - * pasting same code for the two game objects, the component interfaces allow game - * objects to inherit these components from the component classes. + * The component design pattern is a common game design structure. This pattern is often used to + * reduce duplication of code as well as to improve maintainability. In this implementation, + * component design pattern has been used to provide two game objects with varying component + * interfaces (features). As opposed to copying and pasting same code for the two game objects, the + * component interfaces allow game objects to inherit these components from the component classes. * - *

    The implementation has decoupled graphic, physics and input components from - * the player and NPC objects. As a result, it avoids the creation of monolithic java classes. + *

    The implementation has decoupled graphic, physics and input components from the player and NPC + * objects. As a result, it avoids the creation of monolithic java classes. * - *

    The below example in this App class demonstrates the use of the component interfaces - * for separate objects (player & NPC) and updating of these components as per the - * implementations in GameObject class and the component classes. + *

    The below example in this App class demonstrates the use of the component interfaces for + * separate objects (player & NPC) and updating of these components as per the implementations in + * GameObject class and the component classes. */ @Slf4j public final class App { @@ -53,7 +52,6 @@ public static void main(String[] args) { final var player = GameObject.createPlayer(); final var npc = GameObject.createNpc(); - LOGGER.info("Player Update:"); player.update(KeyEvent.KEY_LOCATION_LEFT); LOGGER.info("NPC Update:"); diff --git a/component/src/main/java/com/iluwatar/component/GameObject.java b/component/src/main/java/com/iluwatar/component/GameObject.java index c67970d0625f..87c35b24e8c4 100644 --- a/component/src/main/java/com/iluwatar/component/GameObject.java +++ b/component/src/main/java/com/iluwatar/component/GameObject.java @@ -35,8 +35,8 @@ import lombok.RequiredArgsConstructor; /** - * The GameObject class has three component class instances that allow - * the creation of different game objects based on the game design requirements. + * The GameObject class has three component class instances that allow the creation of different + * game objects based on the game design requirements. */ @Getter @RequiredArgsConstructor @@ -55,13 +55,13 @@ public class GameObject { * @return player object */ public static GameObject createPlayer() { - return new GameObject(new PlayerInputComponent(), + return new GameObject( + new PlayerInputComponent(), new ObjectPhysicComponent(), new ObjectGraphicComponent(), "player"); } - /** * Creates a NPC game object. * @@ -69,16 +69,12 @@ public static GameObject createPlayer() { */ public static GameObject createNpc() { return new GameObject( - new DemoInputComponent(), - new ObjectPhysicComponent(), - new ObjectGraphicComponent(), - "npc"); + new DemoInputComponent(), new ObjectPhysicComponent(), new ObjectGraphicComponent(), "npc"); } /** - * Updates the three components of the NPC object used in the demo in App.java - * note that this is simply a duplicate of update() without the key event for - * demonstration purposes. + * Updates the three components of the NPC object used in the demo in App.java note that this is + * simply a duplicate of update() without the key event for demonstration purposes. * *

    This method is usually used in games if the player becomes inactive. */ @@ -108,10 +104,7 @@ public void updateVelocity(int acceleration) { this.velocity += acceleration; } - - /** - * Set the c based on the current velocity. - */ + /** Set the c based on the current velocity. */ public void updateCoordinate() { this.coordinate += this.velocity; } diff --git a/component/src/main/java/com/iluwatar/component/component/graphiccomponent/GraphicComponent.java b/component/src/main/java/com/iluwatar/component/component/graphiccomponent/GraphicComponent.java index f8e1f7094cbb..600ee8e5275d 100644 --- a/component/src/main/java/com/iluwatar/component/component/graphiccomponent/GraphicComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/graphiccomponent/GraphicComponent.java @@ -26,9 +26,7 @@ import com.iluwatar.component.GameObject; -/** - * Generic GraphicComponent interface. - */ +/** Generic GraphicComponent interface. */ public interface GraphicComponent { void update(GameObject gameObject); } diff --git a/component/src/main/java/com/iluwatar/component/component/graphiccomponent/ObjectGraphicComponent.java b/component/src/main/java/com/iluwatar/component/component/graphiccomponent/ObjectGraphicComponent.java index fc260b45ebfb..7f595763f10c 100644 --- a/component/src/main/java/com/iluwatar/component/component/graphiccomponent/ObjectGraphicComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/graphiccomponent/ObjectGraphicComponent.java @@ -27,9 +27,7 @@ import com.iluwatar.component.GameObject; import lombok.extern.slf4j.Slf4j; -/** - * ObjectGraphicComponent class mimics the graphic component of the Game Object. - */ +/** ObjectGraphicComponent class mimics the graphic component of the Game Object. */ @Slf4j public class ObjectGraphicComponent implements GraphicComponent { diff --git a/component/src/main/java/com/iluwatar/component/component/inputcomponent/DemoInputComponent.java b/component/src/main/java/com/iluwatar/component/component/inputcomponent/DemoInputComponent.java index ccf05738ef1d..b7ff51c3f2e7 100644 --- a/component/src/main/java/com/iluwatar/component/component/inputcomponent/DemoInputComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/inputcomponent/DemoInputComponent.java @@ -28,11 +28,11 @@ import lombok.extern.slf4j.Slf4j; /** - * Take this component class to control player or the NPC for demo mode. - * and implemented the InputComponent interface. + * Take this component class to control player or the NPC for demo mode. and implemented the + * InputComponent interface. * - *

    Essentially, the demo mode is utilised during a game if the user become inactive. - * Please see: http://gameprogrammingpatterns.com/component.html + *

    Essentially, the demo mode is utilised during a game if the user become inactive. Please see: + * http://gameprogrammingpatterns.com/component.html */ @Slf4j public class DemoInputComponent implements InputComponent { @@ -42,7 +42,7 @@ public class DemoInputComponent implements InputComponent { * Redundant method in the demo mode. * * @param gameObject the gameObject instance - * @param e key event instance + * @param e key event instance */ @Override public void update(GameObject gameObject, int e) { diff --git a/component/src/main/java/com/iluwatar/component/component/inputcomponent/InputComponent.java b/component/src/main/java/com/iluwatar/component/component/inputcomponent/InputComponent.java index 3ab30c148cdc..65bab37fc59d 100644 --- a/component/src/main/java/com/iluwatar/component/component/inputcomponent/InputComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/inputcomponent/InputComponent.java @@ -26,9 +26,7 @@ import com.iluwatar.component.GameObject; -/** - * Generic InputComponent interface. - */ +/** Generic InputComponent interface. */ public interface InputComponent { void update(GameObject gameObject, int e); } diff --git a/component/src/main/java/com/iluwatar/component/component/inputcomponent/PlayerInputComponent.java b/component/src/main/java/com/iluwatar/component/component/inputcomponent/PlayerInputComponent.java index eb9e9d800059..d38682de49f9 100644 --- a/component/src/main/java/com/iluwatar/component/component/inputcomponent/PlayerInputComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/inputcomponent/PlayerInputComponent.java @@ -29,8 +29,8 @@ import lombok.extern.slf4j.Slf4j; /** - * PlayerInputComponent is used to handle user key event inputs, - * and thus it implements the InputComponent interface. + * PlayerInputComponent is used to handle user key event inputs, and thus it implements the + * InputComponent interface. */ @Slf4j public class PlayerInputComponent implements InputComponent { @@ -40,7 +40,7 @@ public class PlayerInputComponent implements InputComponent { * The update method to change the velocity based on the input key event. * * @param gameObject the gameObject instance - * @param e key event instance + * @param e key event instance */ @Override public void update(GameObject gameObject, int e) { diff --git a/component/src/main/java/com/iluwatar/component/component/physiccomponent/ObjectPhysicComponent.java b/component/src/main/java/com/iluwatar/component/component/physiccomponent/ObjectPhysicComponent.java index b8d880138534..a7c189efc463 100644 --- a/component/src/main/java/com/iluwatar/component/component/physiccomponent/ObjectPhysicComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/physiccomponent/ObjectPhysicComponent.java @@ -27,9 +27,7 @@ import com.iluwatar.component.GameObject; import lombok.extern.slf4j.Slf4j; -/** - * Take this component class to update the x coordinate for the Game Object instance. - */ +/** Take this component class to update the x coordinate for the Game Object instance. */ @Slf4j public class ObjectPhysicComponent implements PhysicComponent { diff --git a/component/src/main/java/com/iluwatar/component/component/physiccomponent/PhysicComponent.java b/component/src/main/java/com/iluwatar/component/component/physiccomponent/PhysicComponent.java index 25e2dfd40b8b..67c33026024d 100644 --- a/component/src/main/java/com/iluwatar/component/component/physiccomponent/PhysicComponent.java +++ b/component/src/main/java/com/iluwatar/component/component/physiccomponent/PhysicComponent.java @@ -26,9 +26,7 @@ import com.iluwatar.component.GameObject; -/** - * Generic PhysicComponent interface. - */ +/** Generic PhysicComponent interface. */ public interface PhysicComponent { void update(GameObject gameObject); } diff --git a/component/src/test/java/com/iluwatar/component/AppTest.java b/component/src/test/java/com/iluwatar/component/AppTest.java index 6fa191804c84..7de0745e7685 100644 --- a/component/src/test/java/com/iluwatar/component/AppTest.java +++ b/component/src/test/java/com/iluwatar/component/AppTest.java @@ -24,18 +24,18 @@ */ package com.iluwatar.component; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + /** - * Tests App class : src/main/java/com/iluwatar/component/App.java - * General execution test of the application. + * Tests App class : src/main/java/com/iluwatar/component/App.java General execution test of the + * application. */ class AppTest { - @Test - void shouldExecuteComponentWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + @Test + void shouldExecuteComponentWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/component/src/test/java/com/iluwatar/component/GameObjectTest.java b/component/src/test/java/com/iluwatar/component/GameObjectTest.java index 66b6c6a5523a..09b3b3995268 100644 --- a/component/src/test/java/com/iluwatar/component/GameObjectTest.java +++ b/component/src/test/java/com/iluwatar/component/GameObjectTest.java @@ -24,72 +24,64 @@ */ package com.iluwatar.component; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; + import java.awt.event.KeyEvent; import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -/** - * Tests GameObject class. - * src/main/java/com/iluwatar/component/GameObject.java - */ +/** Tests GameObject class. src/main/java/com/iluwatar/component/GameObject.java */ @Slf4j class GameObjectTest { - GameObject playerTest; - GameObject npcTest; - @BeforeEach - public void initEach() { - //creates player & npc objects for testing - //note that velocity and coordinates are initialised to 0 in GameObject.java - playerTest = GameObject.createPlayer(); - npcTest = GameObject.createNpc(); - } + GameObject playerTest; + GameObject npcTest; + + @BeforeEach + public void initEach() { + // creates player & npc objects for testing + // note that velocity and coordinates are initialised to 0 in GameObject.java + playerTest = GameObject.createPlayer(); + npcTest = GameObject.createNpc(); + } - /** - * Tests the create methods - createPlayer() and createNPC(). - */ - @Test - void objectTest(){ - LOGGER.info("objectTest:"); - assertEquals("player",playerTest.getName()); - assertEquals("npc",npcTest.getName()); - } + /** Tests the create methods - createPlayer() and createNPC(). */ + @Test + void objectTest() { + LOGGER.info("objectTest:"); + assertEquals("player", playerTest.getName()); + assertEquals("npc", npcTest.getName()); + } - /** - * Tests the input component with varying key event inputs. - * Targets the player game object. - */ - @Test - void eventInputTest(){ - LOGGER.info("eventInputTest:"); - playerTest.update(KeyEvent.KEY_LOCATION_LEFT); - assertEquals(-1, playerTest.getVelocity()); - assertEquals(-1, playerTest.getCoordinate()); + /** Tests the input component with varying key event inputs. Targets the player game object. */ + @Test + void eventInputTest() { + LOGGER.info("eventInputTest:"); + playerTest.update(KeyEvent.KEY_LOCATION_LEFT); + assertEquals(-1, playerTest.getVelocity()); + assertEquals(-1, playerTest.getCoordinate()); - playerTest.update(KeyEvent.KEY_LOCATION_RIGHT); - playerTest.update(KeyEvent.KEY_LOCATION_RIGHT); - assertEquals(1, playerTest.getVelocity()); - assertEquals(0, playerTest.getCoordinate()); + playerTest.update(KeyEvent.KEY_LOCATION_RIGHT); + playerTest.update(KeyEvent.KEY_LOCATION_RIGHT); + assertEquals(1, playerTest.getVelocity()); + assertEquals(0, playerTest.getCoordinate()); - LOGGER.info(Integer.toString(playerTest.getCoordinate())); - LOGGER.info(Integer.toString(playerTest.getVelocity())); + LOGGER.info(Integer.toString(playerTest.getCoordinate())); + LOGGER.info(Integer.toString(playerTest.getVelocity())); - GameObject p2 = GameObject.createPlayer(); - p2.update(KeyEvent.KEY_LOCATION_LEFT); - //in the case of an unknown, object stats are set to default - p2.update(KeyEvent.KEY_LOCATION_UNKNOWN); - assertEquals(-1, p2.getVelocity()); - } + GameObject p2 = GameObject.createPlayer(); + p2.update(KeyEvent.KEY_LOCATION_LEFT); + // in the case of an unknown, object stats are set to default + p2.update(KeyEvent.KEY_LOCATION_UNKNOWN); + assertEquals(-1, p2.getVelocity()); + } - /** - * Tests the demo component interface. - */ - @Test - void npcDemoTest(){ - LOGGER.info("npcDemoTest:"); - npcTest.demoUpdate(); - assertEquals(2, npcTest.getVelocity()); - assertEquals(2, npcTest.getCoordinate()); - } + /** Tests the demo component interface. */ + @Test + void npcDemoTest() { + LOGGER.info("npcDemoTest:"); + npcTest.demoUpdate(); + assertEquals(2, npcTest.getVelocity()); + assertEquals(2, npcTest.getCoordinate()); + } } diff --git a/composite-entity/README.md b/composite-entity/README.md index 9ac14d97e3c7..5946663a85ae 100644 --- a/composite-entity/README.md +++ b/composite-entity/README.md @@ -36,6 +36,10 @@ Wikipedia says > Composite entity is a Java EE Software design pattern and it is used to model, represent, and manage a set of interrelated persistent objects rather than representing them as individual fine-grained entity beans, and also a composite entity bean represents a graph of objects. +Flowchart + +![Composite Entity flowchart](./etc/composite-entity-flowchart.png) + ## Programmatic Example of Composite Entity in Java For a console, there may be many interfaces that need to be managed and controlled. Using the composite entity pattern, dependent objects such as messages and signals can be combined and controlled using a single object. diff --git a/composite-entity/etc/composite-entity-flowchart.png b/composite-entity/etc/composite-entity-flowchart.png new file mode 100644 index 000000000000..2954db2db2fe Binary files /dev/null and b/composite-entity/etc/composite-entity-flowchart.png differ diff --git a/composite-entity/pom.xml b/composite-entity/pom.xml index bed5c3a40048..5d11234c3a0a 100644 --- a/composite-entity/pom.xml +++ b/composite-entity/pom.xml @@ -34,6 +34,14 @@ 4.0.0 composite-entity + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/App.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/App.java index 68569b37d26e..c3ae1181a800 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/App.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/App.java @@ -27,7 +27,6 @@ import java.util.Arrays; import lombok.extern.slf4j.Slf4j; - /** * Composite entity is a Java EE Software design pattern and it is used to model, represent, and * manage a set of interrelated persistent objects rather than representing them as individual @@ -36,10 +35,7 @@ @Slf4j public class App { - - /** - * An instance that a console manages two related objects. - */ + /** An instance that a console manages two related objects. */ public App(String message, String signal) { var console = new CompositeEntity(); console.init(); @@ -57,6 +53,5 @@ public App(String message, String signal) { public static void main(String[] args) { new App("No Danger", "Green Light"); - } } diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/CoarseGrainedObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/CoarseGrainedObject.java index 1eefbf2e4659..fa195c2cd9fd 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/CoarseGrainedObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/CoarseGrainedObject.java @@ -32,7 +32,6 @@ * other objects. It can be an object contained in the composite entity, or, composite entity itself * can be the coarse-grained object which holds dependent objects. */ - public abstract class CoarseGrainedObject { DependentObject[] dependentObjects; diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/CompositeEntity.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/CompositeEntity.java index cd52841ca114..0c4a05155ca8 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/CompositeEntity.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/CompositeEntity.java @@ -28,7 +28,6 @@ * Composite entity is the coarse-grained entity bean which may be the coarse-grained object, or may * contain a reference to the coarse-grained object. */ - public class CompositeEntity { private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); @@ -44,4 +43,4 @@ public String[] getData() { public void init() { console.init(); } -} \ No newline at end of file +} diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/ConsoleCoarseGrainedObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/ConsoleCoarseGrainedObject.java index 4ec8413a99fb..422fdc541079 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/ConsoleCoarseGrainedObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/ConsoleCoarseGrainedObject.java @@ -24,21 +24,16 @@ */ package com.iluwatar.compositeentity; -/** - * A specific CoarseGrainedObject to implement a console. - */ - +/** A specific CoarseGrainedObject to implement a console. */ public class ConsoleCoarseGrainedObject extends CoarseGrainedObject { @Override public String[] getData() { - return new String[]{ - dependentObjects[0].getData(), dependentObjects[1].getData() - }; + return new String[] {dependentObjects[0].getData(), dependentObjects[1].getData()}; } public void init() { - dependentObjects = new DependentObject[]{ - new MessageDependentObject(), new SignalDependentObject()}; + dependentObjects = + new DependentObject[] {new MessageDependentObject(), new SignalDependentObject()}; } -} \ No newline at end of file +} diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/DependentObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/DependentObject.java index 29380af543cf..90f2024bc127 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/DependentObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/DependentObject.java @@ -37,5 +37,4 @@ public abstract class DependentObject { T data; - } diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/MessageDependentObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/MessageDependentObject.java index 98b351fe62e8..199e5ee1d859 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/MessageDependentObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/MessageDependentObject.java @@ -24,10 +24,5 @@ */ package com.iluwatar.compositeentity; -/** - * The first DependentObject to show message. - */ - -public class MessageDependentObject extends DependentObject { - -} \ No newline at end of file +/** The first DependentObject to show message. */ +public class MessageDependentObject extends DependentObject {} diff --git a/composite-entity/src/main/java/com/iluwatar/compositeentity/SignalDependentObject.java b/composite-entity/src/main/java/com/iluwatar/compositeentity/SignalDependentObject.java index 58b868e8d8ee..cc3847c2905d 100644 --- a/composite-entity/src/main/java/com/iluwatar/compositeentity/SignalDependentObject.java +++ b/composite-entity/src/main/java/com/iluwatar/compositeentity/SignalDependentObject.java @@ -24,10 +24,5 @@ */ package com.iluwatar.compositeentity; -/** - * The second DependentObject to show message. - */ - -public class SignalDependentObject extends DependentObject { - -} \ No newline at end of file +/** The second DependentObject to show message. */ +public class SignalDependentObject extends DependentObject {} diff --git a/composite-entity/src/test/java/com/iluwatar/compositeentity/AppTest.java b/composite-entity/src/test/java/com/iluwatar/compositeentity/AppTest.java index 85b8da7d600f..4010c2912dae 100644 --- a/composite-entity/src/test/java/com/iluwatar/compositeentity/AppTest.java +++ b/composite-entity/src/test/java/com/iluwatar/compositeentity/AppTest.java @@ -28,22 +28,18 @@ import org.junit.jupiter.api.Test; -/** - * com.iluwatar.compositeentity.App running test - */ +/** com.iluwatar.compositeentity.App running 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 + * + *

    Solution: Inserted assertion to check whether the execution of the main method in {@link * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/composite-entity/src/test/java/com/iluwatar/compositeentity/PersistenceTest.java b/composite-entity/src/test/java/com/iluwatar/compositeentity/PersistenceTest.java index b90272f3b5b5..08e7899b4a58 100644 --- a/composite-entity/src/test/java/com/iluwatar/compositeentity/PersistenceTest.java +++ b/composite-entity/src/test/java/com/iluwatar/compositeentity/PersistenceTest.java @@ -24,13 +24,13 @@ */ package com.iluwatar.compositeentity; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + class PersistenceTest { - final static ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); + static final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); @Test void dependentObjectChangedForPersistenceTest() { diff --git a/composite-view/README.md b/composite-view/README.md index bb2704eed94f..a670f00ffdb6 100644 --- a/composite-view/README.md +++ b/composite-view/README.md @@ -29,6 +29,10 @@ Wikipedia says > Composite views that are composed of multiple atomic subviews. Each component of the template may be included dynamically into the whole and the layout of the page may be managed independently of the content. This solution provides for the creation of a composite view based on the inclusion and substitution of modular dynamic and static template fragments. It promotes the reuse of atomic portions of the view by encouraging modular design. +Flowchart + +![Composite View flowchart](./etc/composite-view-flowchart.png) + ## Programmatic Example of Composite View Pattern in Java A news site wants to display the current date and news to different users based on that user's preferences. The news site will substitute in different news feed components depending on the user's interest, defaulting to local news. diff --git a/composite-view/etc/composite-view-flowchart.png b/composite-view/etc/composite-view-flowchart.png new file mode 100644 index 000000000000..bee1d8d9b006 Binary files /dev/null and b/composite-view/etc/composite-view-flowchart.png differ diff --git a/composite-view/pom.xml b/composite-view/pom.xml index d7913c3b0342..f8b38b5233f6 100644 --- a/composite-view/pom.xml +++ b/composite-view/pom.xml @@ -37,13 +37,16 @@ composite-view - org.junit.jupiter - junit-jupiter-engine - test + org.slf4j + slf4j-api - junit - junit + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java b/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java index a10d98c52b98..3b803a6152f3 100644 --- a/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java +++ b/composite-view/src/main/java/com/iluwatar/compositeview/AppServlet.java @@ -32,16 +32,14 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * A servlet object that extends HttpServlet. - * Runs on Tomcat 10 and handles Http requests - */ +/** A servlet object that extends HttpServlet. Runs on Tomcat 10 and handles Http requests */ @Slf4j @NoArgsConstructor public final class AppServlet extends HttpServlet { private static final String CONTENT_TYPE = "text/html"; private String msgPartOne = "

    This Server Doesn't Support"; - private String msgPartTwo = """ + private String msgPartTwo = + """ Requests

    Use a GET request with boolean values for the following parameters

    'name'

    @@ -93,4 +91,4 @@ public void doPut(HttpServletRequest req, HttpServletResponse resp) { LOGGER.error("Exception occurred PUT request processing ", e); } } -} \ No newline at end of file +} diff --git a/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java b/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java index 8baf2ee538ab..ecda0cb38e6e 100644 --- a/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java +++ b/composite-view/src/main/java/com/iluwatar/compositeview/ClientPropertiesBean.java @@ -30,15 +30,13 @@ import lombok.NoArgsConstructor; import lombok.Setter; - /** - * A Java beans class that parses a http request and stores parameters. - * Java beans used in JSP's to dynamically include elements in view. - * DEFAULT_NAME = a constant, default name to be used for the default constructor - * worldNewsInterest = whether current request has world news interest - * sportsInterest = whether current request has a sportsInterest - * businessInterest = whether current request has a businessInterest - * scienceNewsInterest = whether current request has a scienceNewsInterest + * A Java beans class that parses a http request and stores parameters. Java beans used in JSP's to + * dynamically include elements in view. DEFAULT_NAME = a constant, default name to be used for the + * default constructor worldNewsInterest = whether current request has world news interest + * sportsInterest = whether current request has a sportsInterest businessInterest = whether current + * request has a businessInterest scienceNewsInterest = whether current request has a + * scienceNewsInterest */ @Getter @Setter @@ -51,7 +49,7 @@ public class ClientPropertiesBean implements Serializable { private static final String BUSINESS_PARAM = "bus"; private static final String NAME_PARAM = "name"; - private static final String DEFAULT_NAME = "DEFAULT_NAME"; + private static final String DEFAULT_NAME = "DEFAULT_NAME"; private boolean worldNewsInterest = true; private boolean sportsInterest = true; private boolean businessInterest = true; diff --git a/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java b/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java index c585be29ebfb..8219a56e4f39 100644 --- a/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java +++ b/composite-view/src/test/java/com/iluwatar/compositeview/AppServletTest.java @@ -24,88 +24,93 @@ */ package com.iluwatar.compositeview; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + import jakarta.servlet.RequestDispatcher; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import java.io.PrintWriter; import java.io.StringWriter; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; - -/* Written with reference from https://stackoverflow.com/questions/5434419/how-to-test-my-servlet-using-junit -and https://stackoverflow.com/questions/50211433/servlets-unit-testing - */ +class AppServletTest { -class AppServletTest extends Mockito{ - private String msgPartOne = "

    This Server Doesn't Support"; - private String msgPartTwo = """ - Requests

    -

    Use a GET request with boolean values for the following parameters

    -

    'name'

    -

    'bus'

    -

    'sports'

    -

    'sci'

    -

    'world'

    """; - private String destination = "newsDisplay.jsp"; + private final String msgPartOne = "

    This Server Doesn't Support"; + private final String msgPartTwo = + """ + Requests

    +

    Use a GET request with boolean values for the following parameters

    +

    'name'

    +

    'bus'

    +

    'sports'

    +

    'sci'

    +

    'world'

    """; + private final String destination = "newsDisplay.jsp"; @Test void testDoGet() throws Exception { - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); - RequestDispatcher mockDispatcher = Mockito.mock(RequestDispatcher.class); + HttpServletRequest mockReq = mock(HttpServletRequest.class); + HttpServletResponse mockResp = mock(HttpServletResponse.class); + RequestDispatcher mockDispatcher = mock(RequestDispatcher.class); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); when(mockReq.getRequestDispatcher(destination)).thenReturn(mockDispatcher); + AppServlet curServlet = new AppServlet(); curServlet.doGet(mockReq, mockResp); + verify(mockReq, times(1)).getRequestDispatcher(destination); verify(mockDispatcher).forward(mockReq, mockResp); - - } @Test void testDoPost() throws Exception { - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + HttpServletRequest mockReq = mock(HttpServletRequest.class); + HttpServletResponse mockResp = mock(HttpServletResponse.class); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); AppServlet curServlet = new AppServlet(); curServlet.doPost(mockReq, mockResp); printWriter.flush(); + assertTrue(stringWriter.toString().contains(msgPartOne + " Post " + msgPartTwo)); } @Test void testDoPut() throws Exception { - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + HttpServletRequest mockReq = mock(HttpServletRequest.class); + HttpServletResponse mockResp = mock(HttpServletResponse.class); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); AppServlet curServlet = new AppServlet(); curServlet.doPut(mockReq, mockResp); printWriter.flush(); + assertTrue(stringWriter.toString().contains(msgPartOne + " Put " + msgPartTwo)); } @Test void testDoDelete() throws Exception { - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - HttpServletResponse mockResp = Mockito.mock(HttpServletResponse.class); + HttpServletRequest mockReq = mock(HttpServletRequest.class); + HttpServletResponse mockResp = mock(HttpServletResponse.class); StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter); + when(mockResp.getWriter()).thenReturn(printWriter); AppServlet curServlet = new AppServlet(); curServlet.doDelete(mockReq, mockResp); printWriter.flush(); + assertTrue(stringWriter.toString().contains(msgPartOne + " Delete " + msgPartTwo)); } } diff --git a/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java b/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java index 826a0088119b..8e27a20e06f9 100644 --- a/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java +++ b/composite-view/src/test/java/com/iluwatar/compositeview/JavaBeansTest.java @@ -24,72 +24,78 @@ */ package com.iluwatar.compositeview; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import static org.junit.Assert.*; class JavaBeansTest { - @Test - void testDefaultConstructor() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertEquals("DEFAULT_NAME", newBean.getName()); - assertTrue(newBean.isBusinessInterest()); - assertTrue(newBean.isScienceNewsInterest()); - assertTrue(newBean.isSportsInterest()); - assertTrue(newBean.isWorldNewsInterest()); - - } - - @Test - void testNameGetterSetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertEquals("DEFAULT_NAME", newBean.getName()); - newBean.setName("TEST_NAME_ONE"); - assertEquals("TEST_NAME_ONE", newBean.getName()); - } - - @Test - void testBusinessSetterGetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertTrue(newBean.isBusinessInterest()); - newBean.setBusinessInterest(false); - assertFalse(newBean.isBusinessInterest()); - } - - @Test - void testScienceSetterGetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertTrue(newBean.isScienceNewsInterest()); - newBean.setScienceNewsInterest(false); - assertFalse(newBean.isScienceNewsInterest()); - } - - @Test - void testSportsSetterGetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertTrue(newBean.isSportsInterest()); - newBean.setSportsInterest(false); - assertFalse(newBean.isSportsInterest()); - } - - @Test - void testWorldSetterGetter() { - ClientPropertiesBean newBean = new ClientPropertiesBean(); - assertTrue(newBean.isWorldNewsInterest()); - newBean.setWorldNewsInterest(false); - assertFalse(newBean.isWorldNewsInterest()); - } - - @Test - void testRequestConstructor(){ - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - ClientPropertiesBean newBean = new ClientPropertiesBean((mockReq)); - assertEquals("DEFAULT_NAME", newBean.getName()); - assertFalse(newBean.isWorldNewsInterest()); - assertFalse(newBean.isBusinessInterest()); - assertFalse(newBean.isScienceNewsInterest()); - assertFalse(newBean.isSportsInterest()); - } + + @Test + void testDefaultConstructor() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertEquals("DEFAULT_NAME", newBean.getName()); + assertTrue(newBean.isBusinessInterest()); + assertTrue(newBean.isScienceNewsInterest()); + assertTrue(newBean.isSportsInterest()); + assertTrue(newBean.isWorldNewsInterest()); + } + + @Test + void testNameGetterSetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertEquals("DEFAULT_NAME", newBean.getName()); + + newBean.setName("TEST_NAME_ONE"); + assertEquals("TEST_NAME_ONE", newBean.getName()); + } + + @Test + void testBusinessSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isBusinessInterest()); + + newBean.setBusinessInterest(false); + assertFalse(newBean.isBusinessInterest()); + } + + @Test + void testScienceSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isScienceNewsInterest()); + + newBean.setScienceNewsInterest(false); + assertFalse(newBean.isScienceNewsInterest()); + } + + @Test + void testSportsSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isSportsInterest()); + + newBean.setSportsInterest(false); + assertFalse(newBean.isSportsInterest()); + } + + @Test + void testWorldSetterGetter() { + ClientPropertiesBean newBean = new ClientPropertiesBean(); + assertTrue(newBean.isWorldNewsInterest()); + + newBean.setWorldNewsInterest(false); + assertFalse(newBean.isWorldNewsInterest()); + } + + @Test + void testRequestConstructor() { + HttpServletRequest mockReq = mock(HttpServletRequest.class); + ClientPropertiesBean newBean = new ClientPropertiesBean(mockReq); + + assertEquals("DEFAULT_NAME", newBean.getName()); + assertFalse(newBean.isWorldNewsInterest()); + assertFalse(newBean.isBusinessInterest()); + assertFalse(newBean.isScienceNewsInterest()); + assertFalse(newBean.isSportsInterest()); + } } diff --git a/composite-view/web/index.jsp b/composite-view/web/index.jsp index cb268a7637d4..47a8d774caad 100644 --- a/composite-view/web/index.jsp +++ b/composite-view/web/index.jsp @@ -26,21 +26,53 @@ --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> - + + Composite Patterns Mock News Site - - + +

    Welcome To The Composite Patterns Mock News Site

    -

    Send a GET request to the "/news" path to see the composite view with mock news

    -

    Use the following parameters:

    -

    name: string name to be dynamically displayed

    -

    bus: boolean for whether you want to see the mock business news

    -

    world: boolean for whether you want to see the mock world news

    -

    sci: boolean for whether you want to see the mock world news

    -

    sport: boolean for whether you want to see the mock world news

    - +
    +

    Send a GET request to the "/news" path to see the composite view with mock news

    +

    Use the following parameters:

    +

    name: string - Your name to be dynamically displayed

    +

    bus: boolean - Set to true to see mock business news

    +

    world: boolean - Set to true to see mock world news

    +

    sci: boolean - Set to true to see mock science news

    +

    sport: boolean - Set to true to see mock sports news

    +

    Example Request:

    +

    /news?name=John&bus=true&world=false&sci=true&sport=false

    +

    If the request fails, ensure you have the correct parameters and try again.

    +
    + diff --git a/composite/README.md b/composite/README.md index 0dc740c3bc1f..91f5e3230ff6 100644 --- a/composite/README.md +++ b/composite/README.md @@ -34,6 +34,10 @@ Wikipedia says > In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes that a group of objects is to be treated in the same way as a single instance of an object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly. +Flowchart + +![Composite flowchart](./etc/composite-flowchart.png) + ## Programmatic Example of Composite Pattern in Java Every sentence is composed of words which are in turn composed of characters. Each of these objects are printable, and they can have something printed before or after them like sentence always ends with full stop and word always has space before it. diff --git a/composite/etc/composite-flowchart.png b/composite/etc/composite-flowchart.png new file mode 100644 index 000000000000..7938250ec332 Binary files /dev/null and b/composite/etc/composite-flowchart.png differ diff --git a/composite/pom.xml b/composite/pom.xml index d9893de052ea..7beb910d901e 100644 --- a/composite/pom.xml +++ b/composite/pom.xml @@ -34,6 +34,14 @@ composite + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/composite/src/main/java/com/iluwatar/composite/App.java b/composite/src/main/java/com/iluwatar/composite/App.java index d890328de3ec..366ceffa45e9 100644 --- a/composite/src/main/java/com/iluwatar/composite/App.java +++ b/composite/src/main/java/com/iluwatar/composite/App.java @@ -35,7 +35,6 @@ * *

    In this example we have sentences composed of words composed of letters. All of the objects * can be treated through the same interface ({@link LetterComposite}). - * */ @Slf4j public class App { diff --git a/composite/src/main/java/com/iluwatar/composite/Letter.java b/composite/src/main/java/com/iluwatar/composite/Letter.java index 152299d3cc64..4adcba864643 100644 --- a/composite/src/main/java/com/iluwatar/composite/Letter.java +++ b/composite/src/main/java/com/iluwatar/composite/Letter.java @@ -26,9 +26,7 @@ import lombok.RequiredArgsConstructor; -/** - * Letter. - */ +/** Letter. */ @RequiredArgsConstructor public class Letter extends LetterComposite { diff --git a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java index 2b11d1cc4202..a5b0d1cc3d55 100644 --- a/composite/src/main/java/com/iluwatar/composite/LetterComposite.java +++ b/composite/src/main/java/com/iluwatar/composite/LetterComposite.java @@ -27,9 +27,7 @@ import java.util.ArrayList; import java.util.List; -/** - * Composite interface. - */ +/** Composite interface. */ public abstract class LetterComposite { private final List children = new ArrayList<>(); @@ -42,15 +40,11 @@ public int count() { return children.size(); } - protected void printThisBefore() { - } + protected void printThisBefore() {} - protected void printThisAfter() { - } + protected void printThisAfter() {} - /** - * Print. - */ + /** Print. */ public void print() { printThisBefore(); children.forEach(LetterComposite::print); diff --git a/composite/src/main/java/com/iluwatar/composite/Messenger.java b/composite/src/main/java/com/iluwatar/composite/Messenger.java index 011f91b82a89..3af972bc6ca0 100644 --- a/composite/src/main/java/com/iluwatar/composite/Messenger.java +++ b/composite/src/main/java/com/iluwatar/composite/Messenger.java @@ -26,42 +26,37 @@ import java.util.List; -/** - * Messenger. - */ +/** Messenger. */ public class Messenger { LetterComposite messageFromOrcs() { - var words = List.of( - new Word('W', 'h', 'e', 'r', 'e'), - new Word('t', 'h', 'e', 'r', 'e'), - new Word('i', 's'), - new Word('a'), - new Word('w', 'h', 'i', 'p'), - new Word('t', 'h', 'e', 'r', 'e'), - new Word('i', 's'), - new Word('a'), - new Word('w', 'a', 'y') - ); + var words = + List.of( + new Word('W', 'h', 'e', 'r', 'e'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'h', 'i', 'p'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'a', 'y')); return new Sentence(words); - } LetterComposite messageFromElves() { - var words = List.of( - new Word('M', 'u', 'c', 'h'), - new Word('w', 'i', 'n', 'd'), - new Word('p', 'o', 'u', 'r', 's'), - new Word('f', 'r', 'o', 'm'), - new Word('y', 'o', 'u', 'r'), - new Word('m', 'o', 'u', 't', 'h') - ); + var words = + List.of( + new Word('M', 'u', 'c', 'h'), + new Word('w', 'i', 'n', 'd'), + new Word('p', 'o', 'u', 'r', 's'), + new Word('f', 'r', 'o', 'm'), + new Word('y', 'o', 'u', 'r'), + new Word('m', 'o', 'u', 't', 'h')); return new Sentence(words); - } - } diff --git a/composite/src/main/java/com/iluwatar/composite/Sentence.java b/composite/src/main/java/com/iluwatar/composite/Sentence.java index 95143b83f020..448d2096cc5d 100644 --- a/composite/src/main/java/com/iluwatar/composite/Sentence.java +++ b/composite/src/main/java/com/iluwatar/composite/Sentence.java @@ -26,14 +26,10 @@ import java.util.List; -/** - * Sentence. - */ +/** Sentence. */ public class Sentence extends LetterComposite { - /** - * Constructor. - */ + /** Constructor. */ public Sentence(List words) { words.forEach(this::add); } diff --git a/composite/src/main/java/com/iluwatar/composite/Word.java b/composite/src/main/java/com/iluwatar/composite/Word.java index e84bd0791445..5b0f82049d9c 100644 --- a/composite/src/main/java/com/iluwatar/composite/Word.java +++ b/composite/src/main/java/com/iluwatar/composite/Word.java @@ -26,20 +26,17 @@ import java.util.List; -/** - * Word. - */ +/** Word. */ public class Word extends LetterComposite { - /** - * Constructor. - */ + /** Constructor. */ public Word(List letters) { letters.forEach(this::add); } /** * Constructor. + * * @param letters to include */ public Word(char... letters) { diff --git a/composite/src/test/java/com/iluwatar/composite/AppTest.java b/composite/src/test/java/com/iluwatar/composite/AppTest.java index a8cd66afeb57..b1c79d7357b1 100644 --- a/composite/src/test/java/com/iluwatar/composite/AppTest.java +++ b/composite/src/test/java/com/iluwatar/composite/AppTest.java @@ -27,20 +27,17 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * 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#main(String[])} - * throws an exception. + *

    Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - Assertions.assertDoesNotThrow(() -> App.main(new String[]{})); + Assertions.assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/composite/src/test/java/com/iluwatar/composite/MessengerTest.java b/composite/src/test/java/com/iluwatar/composite/MessengerTest.java index 53698ff972df..b22b94cc6b89 100644 --- a/composite/src/test/java/com/iluwatar/composite/MessengerTest.java +++ b/composite/src/test/java/com/iluwatar/composite/MessengerTest.java @@ -33,20 +33,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * MessengerTest - * - */ +/** MessengerTest */ class MessengerTest { - /** - * The buffer used to capture every write to {@link System#out} - */ + /** The buffer used to capture every write to {@link System#out} */ private ByteArrayOutputStream stdOutBuffer = new ByteArrayOutputStream(); - /** - * Keep the original std-out so it can be restored after the test - */ + /** Keep the original std-out so it can be restored after the test */ private final PrintStream realStdOut = System.out; /** @@ -58,43 +51,31 @@ void setUp() { System.setOut(new PrintStream(stdOutBuffer)); } - /** - * Removed the mocked std-out {@link PrintStream} again from the {@link System} class - */ + /** Removed the mocked std-out {@link PrintStream} again from the {@link System} class */ @AfterEach void tearDown() { System.setOut(realStdOut); } - /** - * Test the message from the orcs - */ + /** Test the message from the orcs */ @Test void testMessageFromOrcs() { final var messenger = new Messenger(); - testMessage( - messenger.messageFromOrcs(), - "Where there is a whip there is a way." - ); + testMessage(messenger.messageFromOrcs(), "Where there is a whip there is a way."); } - /** - * Test the message from the elves - */ + /** Test the message from the elves */ @Test void testMessageFromElves() { final var messenger = new Messenger(); - testMessage( - messenger.messageFromElves(), - "Much wind pours from your mouth." - ); + testMessage(messenger.messageFromElves(), "Much wind pours from your mouth."); } /** * Test if the given composed message matches the expected message * * @param composedMessage The composed message, received from the messenger - * @param message The expected message + * @param message The expected message */ private void testMessage(final LetterComposite composedMessage, final String message) { // Test is the composed message has the correct number of words @@ -108,5 +89,4 @@ private void testMessage(final LetterComposite composedMessage, final String mes // ... and verify if the message matches with the expected one assertEquals(message, new String(this.stdOutBuffer.toByteArray()).trim()); } - } diff --git a/context-object/README.md b/context-object/README.md index 831abb7c7323..71e231681bcb 100644 --- a/context-object/README.md +++ b/context-object/README.md @@ -36,6 +36,10 @@ In plain words > Use a Context Object to encapsulate state in a protocol-independent way to be shared throughout your application. +Sequence diagram + +![Context Object sequence diagram](./etc/context-object-sequence-diagram.png) + ## Programmatic Example of Context Object in Java In a multi-layered Java application, different layers such as A, B, and C extract specific information from a shared context. Passing each piece of information individually is inefficient. The Context Object pattern efficiently stores and passes this information, improving the overall performance and maintainability of the Java application. diff --git a/context-object/etc/context-object-sequence-diagram.png b/context-object/etc/context-object-sequence-diagram.png new file mode 100644 index 000000000000..f8cc87ff38e2 Binary files /dev/null and b/context-object/etc/context-object-sequence-diagram.png differ diff --git a/context-object/pom.xml b/context-object/pom.xml index 4eb0237a6263..82f8862b6905 100644 --- a/context-object/pom.xml +++ b/context-object/pom.xml @@ -34,6 +34,14 @@ context-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/context-object/src/main/java/com/iluwatar/context/object/App.java b/context-object/src/main/java/com/iluwatar/context/object/App.java index 440239f0c1ea..f0fc505c31e3 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/App.java +++ b/context-object/src/main/java/com/iluwatar/context/object/App.java @@ -27,12 +27,14 @@ import lombok.extern.slf4j.Slf4j; /** - * In the context object pattern, information and data from underlying protocol-specific classes/systems is decoupled - * and stored into a protocol-independent object in an organised format. The pattern ensures the data contained within - * the context object can be shared and further structured between different layers of a software system. + * In the context object pattern, information and data from underlying protocol-specific + * classes/systems is decoupled and stored into a protocol-independent object in an organised + * format. The pattern ensures the data contained within the context object can be shared and + * further structured between different layers of a software system. * - *

    In this example we show how a context object {@link ServiceContext} can be initiated, edited and passed/retrieved - * in different layers of the program ({@link LayerA}, {@link LayerB}, {@link LayerC}) through use of static methods.

    + *

    In this example we show how a context object {@link ServiceContext} can be initiated, edited + * and passed/retrieved in different layers of the program ({@link LayerA}, {@link LayerB}, {@link + * LayerC}) through use of static methods. */ @Slf4j public class App { @@ -45,19 +47,21 @@ public class App { * @param args command line args */ public static void main(String[] args) { - //Initiate first layer and add service information into context + // Initiate first layer and add service information into context var layerA = new LayerA(); layerA.addAccountInfo(SERVICE); logContext(layerA.getContext()); - //Initiate second layer and preserving information retrieved in first layer through passing context object + // Initiate second layer and preserving information retrieved in first layer through passing + // context object var layerB = new LayerB(layerA); layerB.addSessionInfo(SERVICE); logContext(layerB.getContext()); - //Initiate third layer and preserving information retrieved in first and second layer through passing context object + // Initiate third layer and preserving information retrieved in first and second layer through + // passing context object var layerC = new LayerC(layerB); layerC.addSearchInfo(SERVICE); @@ -67,4 +71,4 @@ public static void main(String[] args) { private static void logContext(ServiceContext context) { LOGGER.info("Context = {}", context); } -} \ No newline at end of file +} diff --git a/context-object/src/main/java/com/iluwatar/context/object/LayerA.java b/context-object/src/main/java/com/iluwatar/context/object/LayerA.java index 87faec2a82f0..4d37f079052e 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/LayerA.java +++ b/context-object/src/main/java/com/iluwatar/context/object/LayerA.java @@ -26,9 +26,7 @@ import lombok.Getter; -/** - * Layer A in the context object pattern. - */ +/** Layer A in the context object pattern. */ @Getter public class LayerA { diff --git a/context-object/src/main/java/com/iluwatar/context/object/LayerB.java b/context-object/src/main/java/com/iluwatar/context/object/LayerB.java index d4991f17f2f5..a67aba9bd5f7 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/LayerB.java +++ b/context-object/src/main/java/com/iluwatar/context/object/LayerB.java @@ -26,9 +26,7 @@ import lombok.Getter; -/** - * Layer B in the context object pattern. - */ +/** Layer B in the context object pattern. */ @Getter public class LayerB { diff --git a/context-object/src/main/java/com/iluwatar/context/object/LayerC.java b/context-object/src/main/java/com/iluwatar/context/object/LayerC.java index 78c6dcec50f2..d33a62998030 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/LayerC.java +++ b/context-object/src/main/java/com/iluwatar/context/object/LayerC.java @@ -26,9 +26,7 @@ import lombok.Getter; -/** - * Layer C in the context object pattern. - */ +/** Layer C in the context object pattern. */ @Getter public class LayerC { diff --git a/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java b/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java index 2092ddf7898b..3de91a20e672 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java +++ b/context-object/src/main/java/com/iluwatar/context/object/ServiceContext.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.Setter; -/** - * Where context objects are defined. - */ +/** Where context objects are defined. */ @Getter @Setter public class ServiceContext { diff --git a/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java b/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java index d7094a0c080b..bee65e828914 100644 --- a/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java +++ b/context-object/src/main/java/com/iluwatar/context/object/ServiceContextFactory.java @@ -24,9 +24,7 @@ */ package com.iluwatar.context.object; -/** - * An interface to create context objects passed through layers. - */ +/** An interface to create context objects passed through layers. */ public class ServiceContextFactory { public static ServiceContext createContext() { diff --git a/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java b/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java index 9c5d72fde8ba..2cb5e901a80e 100644 --- a/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java +++ b/context-object/src/test/java/com/iluwatar/contect/object/AppTest.java @@ -24,16 +24,14 @@ */ package com.iluwatar.contect.object; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.iluwatar.context.object.App; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - public class AppTest { - /** - * Test example app runs without error. - */ + /** Test example app runs without error. */ @Test void shouldExecuteWithoutException() { assertDoesNotThrow(() -> App.main(new String[] {})); diff --git a/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java b/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java index ab72de567c65..fdfd56af7948 100644 --- a/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java +++ b/context-object/src/test/java/com/iluwatar/contect/object/ServiceContextTest.java @@ -24,6 +24,11 @@ */ package com.iluwatar.contect.object; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; + import com.iluwatar.context.object.LayerA; import com.iluwatar.context.object.LayerB; import com.iluwatar.context.object.LayerC; @@ -31,15 +36,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; - -/** - * ServiceContextTest - * - */ +/** ServiceContextTest */ public class ServiceContextTest { private static final String SERVICE = "SERVICE"; @@ -82,27 +79,23 @@ void testLayerContexts() { assertAll( () -> assertNull(layerA.getContext().getAccountService()), () -> assertNull(layerA.getContext().getSearchService()), - () -> assertNull(layerA.getContext().getSessionService()) - ); + () -> assertNull(layerA.getContext().getSessionService())); layerA.addAccountInfo(SERVICE); assertAll( () -> assertEquals(SERVICE, layerA.getContext().getAccountService()), () -> assertNull(layerA.getContext().getSearchService()), - () -> assertNull(layerA.getContext().getSessionService()) - ); + () -> assertNull(layerA.getContext().getSessionService())); var layerB = new LayerB(layerA); layerB.addSessionInfo(SERVICE); assertAll( () -> assertEquals(SERVICE, layerB.getContext().getAccountService()), () -> assertEquals(SERVICE, layerB.getContext().getSessionService()), - () -> assertNull(layerB.getContext().getSearchService()) - ); + () -> assertNull(layerB.getContext().getSearchService())); var layerC = new LayerC(layerB); layerC.addSearchInfo(SERVICE); assertAll( () -> assertEquals(SERVICE, layerC.getContext().getAccountService()), () -> assertEquals(SERVICE, layerC.getContext().getSearchService()), - () -> assertEquals(SERVICE, layerC.getContext().getSessionService()) - ); + () -> assertEquals(SERVICE, layerC.getContext().getSessionService())); } } diff --git a/converter/README.md b/converter/README.md index adf29383d8a3..7fbb1fe9e559 100644 --- a/converter/README.md +++ b/converter/README.md @@ -32,6 +32,10 @@ In plain words > The Converter Pattern simplifies mapping instances of one class to instances of another class, ensuring consistent and clean data transformation. +Sequence diagram + +![Converter sequence diagram](./etc/converter-sequence-diagram.png) + ## Programmatic Example of Converter Pattern in Java In applications, it's common for the database layer to have entities that need mapping to DTOs (Data Transfer Objects) for business logic. This mapping often involves many classes, necessitating a generic solution. diff --git a/converter/etc/converter-sequence-diagram.png b/converter/etc/converter-sequence-diagram.png new file mode 100644 index 000000000000..65192915c2af Binary files /dev/null and b/converter/etc/converter-sequence-diagram.png differ diff --git a/converter/pom.xml b/converter/pom.xml index c26e68dd1c2b..525237ed17f5 100644 --- a/converter/pom.xml +++ b/converter/pom.xml @@ -34,6 +34,14 @@ converter 4.0.0 + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/converter/src/main/java/com/iluwatar/converter/App.java b/converter/src/main/java/com/iluwatar/converter/App.java index 38d4f101d749..100913501018 100644 --- a/converter/src/main/java/com/iluwatar/converter/App.java +++ b/converter/src/main/java/com/iluwatar/converter/App.java @@ -48,11 +48,11 @@ public static void main(String[] args) { User user = userConverter.convertFromDto(dtoUser); LOGGER.info("Entity converted from DTO: {}", user); - var users = List.of( - new User("Camile", "Tough", false, "124sad"), - new User("Marti", "Luther", true, "42309fd"), - new User("Kate", "Smith", true, "if0243") - ); + var users = + List.of( + new User("Camile", "Tough", false, "124sad"), + new User("Marti", "Luther", true, "42309fd"), + new User("Kate", "Smith", true, "if0243")); LOGGER.info("Domain entities:"); users.stream().map(User::toString).forEach(LOGGER::info); diff --git a/converter/src/main/java/com/iluwatar/converter/Converter.java b/converter/src/main/java/com/iluwatar/converter/Converter.java index bd7a73f98897..374b2ce5da7e 100644 --- a/converter/src/main/java/com/iluwatar/converter/Converter.java +++ b/converter/src/main/java/com/iluwatar/converter/Converter.java @@ -86,5 +86,4 @@ public final List createFromDtos(final Collection dtos) { public final List createFromEntities(final Collection entities) { return entities.stream().map(this::convertFromEntity).toList(); } - } diff --git a/converter/src/main/java/com/iluwatar/converter/User.java b/converter/src/main/java/com/iluwatar/converter/User.java index a8287af76614..9f68047f1dcc 100644 --- a/converter/src/main/java/com/iluwatar/converter/User.java +++ b/converter/src/main/java/com/iluwatar/converter/User.java @@ -24,7 +24,5 @@ */ package com.iluwatar.converter; -/** - * User record. - */ +/** User record. */ public record User(String firstName, String lastName, boolean active, String userId) {} diff --git a/converter/src/main/java/com/iluwatar/converter/UserConverter.java b/converter/src/main/java/com/iluwatar/converter/UserConverter.java index 4ded637129c8..f7dbd3138047 100644 --- a/converter/src/main/java/com/iluwatar/converter/UserConverter.java +++ b/converter/src/main/java/com/iluwatar/converter/UserConverter.java @@ -24,9 +24,7 @@ */ package com.iluwatar.converter; -/** - * Example implementation of the simple User converter. - */ +/** Example implementation of the simple User converter. */ public class UserConverter extends Converter { public UserConverter() { diff --git a/converter/src/main/java/com/iluwatar/converter/UserDto.java b/converter/src/main/java/com/iluwatar/converter/UserDto.java index 9703292b7058..dc1d861dd971 100644 --- a/converter/src/main/java/com/iluwatar/converter/UserDto.java +++ b/converter/src/main/java/com/iluwatar/converter/UserDto.java @@ -24,7 +24,5 @@ */ package com.iluwatar.converter; -/** - * UserDto record. - */ +/** UserDto record. */ public record UserDto(String firstName, String lastName, boolean active, String email) {} diff --git a/converter/src/test/java/com/iluwatar/converter/AppTest.java b/converter/src/test/java/com/iluwatar/converter/AppTest.java index c22a0e7d66da..366adef7b8a7 100644 --- a/converter/src/test/java/com/iluwatar/converter/AppTest.java +++ b/converter/src/test/java/com/iluwatar/converter/AppTest.java @@ -24,25 +24,20 @@ */ package com.iluwatar.converter; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * App running test - */ +import org.junit.jupiter.api.Test; + +/** App running 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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/converter/src/test/java/com/iluwatar/converter/ConverterTest.java b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java index d2f9c6a85123..da14e3e5a67c 100644 --- a/converter/src/test/java/com/iluwatar/converter/ConverterTest.java +++ b/converter/src/test/java/com/iluwatar/converter/ConverterTest.java @@ -30,16 +30,12 @@ import java.util.Random; import org.junit.jupiter.api.Test; -/** - * Tests for {@link Converter} - */ +/** Tests for {@link Converter} */ class ConverterTest { private final UserConverter userConverter = new UserConverter(); - /** - * Tests whether a converter created of opposite functions holds equality as a bijection. - */ + /** Tests whether a converter created of opposite functions holds equality as a bijection. */ @Test void testConversionsStartingFromDomain() { var u1 = new User("Tom", "Hanks", true, "tom@hanks.com"); @@ -47,9 +43,7 @@ void testConversionsStartingFromDomain() { assertEquals(u1, u2); } - /** - * Tests whether a converter created of opposite functions holds equality as a bijection. - */ + /** Tests whether a converter created of opposite functions holds equality as a bijection. */ @Test void testConversionsStartingFromDto() { var u1 = new UserDto("Tom", "Hanks", true, "tom@hanks.com"); @@ -63,19 +57,22 @@ void testConversionsStartingFromDto() { */ @Test void testCustomConverter() { - var converter = new Converter( - userDto -> new User( - userDto.firstName(), - userDto.lastName(), - userDto.active(), - String.valueOf(new Random().nextInt()) - ), - user -> new UserDto( - user.firstName(), - user.lastName(), - user.active(), - user.firstName().toLowerCase() + user.lastName().toLowerCase() + "@whatever.com") - ); + var converter = + new Converter( + userDto -> + new User( + userDto.firstName(), + userDto.lastName(), + userDto.active(), + String.valueOf(new Random().nextInt())), + user -> + new UserDto( + user.firstName(), + user.lastName(), + user.active(), + user.firstName().toLowerCase() + + user.lastName().toLowerCase() + + "@whatever.com")); var u1 = new User("John", "Doe", false, "12324"); var userDto = converter.convertFromEntity(u1); assertEquals("johndoe@whatever.com", userDto.email()); @@ -87,11 +84,11 @@ void testCustomConverter() { */ @Test void testCollectionConversion() { - var users = List.of( - new User("Camile", "Tough", false, "124sad"), - new User("Marti", "Luther", true, "42309fd"), - new User("Kate", "Smith", true, "if0243") - ); + var users = + List.of( + new User("Camile", "Tough", false, "124sad"), + new User("Marti", "Luther", true, "42309fd"), + new User("Kate", "Smith", true, "if0243")); var fromDtos = userConverter.createFromDtos(userConverter.createFromEntities(users)); assertEquals(users, fromDtos); } diff --git a/curiously-recurring-template-pattern/README.md b/curiously-recurring-template-pattern/README.md index 0cf7505040c2..e44bd7d53097 100644 --- a/curiously-recurring-template-pattern/README.md +++ b/curiously-recurring-template-pattern/README.md @@ -40,6 +40,10 @@ Wikipedia says > The curiously recurring template pattern (CRTP) is an idiom, originally in C++, in which a class X derives from a class template instantiation using X itself as a template argument. +Flowchart + +![Curiously Recurring Template Pattern flowchart](./etc/crtp-flowchart.png) + ## Programmatic example of CRTP in Java For a mixed martial arts promotion that is planning an event, ensuring that the fights are organized between athletes of the same weight class is crucial. This prevents mismatches between fighters of significantly different sizes, such as a heavyweight facing off against a bantamweight. diff --git a/curiously-recurring-template-pattern/etc/crtp-flowchart.png b/curiously-recurring-template-pattern/etc/crtp-flowchart.png new file mode 100644 index 000000000000..a205e1638ca0 Binary files /dev/null and b/curiously-recurring-template-pattern/etc/crtp-flowchart.png differ diff --git a/curiously-recurring-template-pattern/pom.xml b/curiously-recurring-template-pattern/pom.xml index 5f222768419c..ab7ed0b19a36 100644 --- a/curiously-recurring-template-pattern/pom.xml +++ b/curiously-recurring-template-pattern/pom.xml @@ -36,6 +36,14 @@ curiously-recurring-template-pattern + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/curiously-recurring-template-pattern/src/main/java/crtp/App.java b/curiously-recurring-template-pattern/src/main/java/crtp/App.java index a9683115e8b5..d592dd717f89 100644 --- a/curiously-recurring-template-pattern/src/main/java/crtp/App.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/App.java @@ -40,12 +40,16 @@ public class App { */ public static void main(String[] args) { - MmaBantamweightFighter fighter1 = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); - MmaBantamweightFighter fighter2 = new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); + MmaBantamweightFighter fighter1 = + new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); + MmaBantamweightFighter fighter2 = + new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); fighter1.fight(fighter2); - MmaHeavyweightFighter fighter3 = new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); - MmaHeavyweightFighter fighter4 = new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); + MmaHeavyweightFighter fighter3 = + new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); + MmaHeavyweightFighter fighter4 = + new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); fighter3.fight(fighter4); } } diff --git a/curiously-recurring-template-pattern/src/main/java/crtp/Fighter.java b/curiously-recurring-template-pattern/src/main/java/crtp/Fighter.java index 40a7367ce0e5..da149229c8ba 100644 --- a/curiously-recurring-template-pattern/src/main/java/crtp/Fighter.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/Fighter.java @@ -32,5 +32,4 @@ public interface Fighter { void fight(T t); - } diff --git a/curiously-recurring-template-pattern/src/main/java/crtp/MmaBantamweightFighter.java b/curiously-recurring-template-pattern/src/main/java/crtp/MmaBantamweightFighter.java index 25258c8bc7a5..08886a3b371c 100644 --- a/curiously-recurring-template-pattern/src/main/java/crtp/MmaBantamweightFighter.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/MmaBantamweightFighter.java @@ -24,13 +24,10 @@ */ package crtp; -/** - * MmaBantamweightFighter class. - */ +/** MmaBantamweightFighter class. */ class MmaBantamweightFighter extends MmaFighter { public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { super(name, surname, nickName, speciality); } - -} \ No newline at end of file +} diff --git a/curiously-recurring-template-pattern/src/main/java/crtp/MmaHeavyweightFighter.java b/curiously-recurring-template-pattern/src/main/java/crtp/MmaHeavyweightFighter.java index 74c1f9147ab0..1ed545ede7ec 100644 --- a/curiously-recurring-template-pattern/src/main/java/crtp/MmaHeavyweightFighter.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/MmaHeavyweightFighter.java @@ -24,13 +24,10 @@ */ package crtp; -/** - * MmaHeavyweightFighter. - */ +/** MmaHeavyweightFighter. */ public class MmaHeavyweightFighter extends MmaFighter { public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { super(name, surname, nickName, speciality); } - } diff --git a/curiously-recurring-template-pattern/src/main/java/crtp/MmaLightweightFighter.java b/curiously-recurring-template-pattern/src/main/java/crtp/MmaLightweightFighter.java index 4a3d606a8502..433b1934fa15 100644 --- a/curiously-recurring-template-pattern/src/main/java/crtp/MmaLightweightFighter.java +++ b/curiously-recurring-template-pattern/src/main/java/crtp/MmaLightweightFighter.java @@ -24,13 +24,10 @@ */ package crtp; -/** - * MmaLightweightFighter class. - */ +/** MmaLightweightFighter class. */ class MmaLightweightFighter extends MmaFighter { public MmaLightweightFighter(String name, String surname, String nickName, String speciality) { super(name, surname, nickName, speciality); } - } diff --git a/curiously-recurring-template-pattern/src/test/java/crtp/AppTest.java b/curiously-recurring-template-pattern/src/test/java/crtp/AppTest.java index ee3e7978612b..fb19aa31a8d6 100644 --- a/curiously-recurring-template-pattern/src/test/java/crtp/AppTest.java +++ b/curiously-recurring-template-pattern/src/test/java/crtp/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/curiously-recurring-template-pattern/src/test/java/crtp/FightTest.java b/curiously-recurring-template-pattern/src/test/java/crtp/FightTest.java index 279730d483a2..a70edd0d674d 100644 --- a/curiously-recurring-template-pattern/src/test/java/crtp/FightTest.java +++ b/curiously-recurring-template-pattern/src/test/java/crtp/FightTest.java @@ -24,37 +24,38 @@ */ package crtp; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.ArrayList; import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; @Slf4j public class FightTest { /** - * A fighter has signed a contract with a promotion, and he will face some other fighters. A list of opponents is ready - * but for some reason not all of them belong to the same weight class. Let's ensure that the fighter will only face - * opponents in the same weight class. + * A fighter has signed a contract with a promotion, and he will face some other fighters. A list + * of opponents is ready but for some reason not all of them belong to the same weight class. + * Let's ensure that the fighter will only face opponents in the same weight class. */ @Test void testFighterCanFightOnlyAgainstSameWeightOpponents() { - MmaBantamweightFighter fighter = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); + MmaBantamweightFighter fighter = + new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); List> opponents = getOpponents(); List> challenged = new ArrayList<>(); - opponents.forEach(challenger -> { - try { - ((MmaBantamweightFighter) challenger).fight(fighter); - challenged.add(challenger); - } catch (ClassCastException e) { - LOGGER.error(e.getMessage()); - } - }); + opponents.forEach( + challenger -> { + try { + ((MmaBantamweightFighter) challenger).fight(fighter); + challenged.add(challenger); + } catch (ClassCastException e) { + LOGGER.error(e.getMessage()); + } + }); assertFalse(challenged.isEmpty()); assertTrue(challenged.stream().allMatch(c -> c instanceof MmaBantamweightFighter)); @@ -62,13 +63,10 @@ void testFighterCanFightOnlyAgainstSameWeightOpponents() { private static List> getOpponents() { return List.of( - new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"), - new MmaLightweightFighter("Evan", "Evans", "Clean Coder", "Sambo"), - new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"), - new MmaBantamweightFighter("Ray", "Raymond", "Scrum Master", "Karate"), - new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu") - ); + new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"), + new MmaLightweightFighter("Evan", "Evans", "Clean Coder", "Sambo"), + new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"), + new MmaBantamweightFighter("Ray", "Raymond", "Scrum Master", "Karate"), + new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu")); } - - } diff --git a/currying/README.md b/currying/README.md index 22806a50d2e7..b0e86856df03 100644 --- a/currying/README.md +++ b/currying/README.md @@ -33,6 +33,10 @@ Wikipedia says > In mathematics and computer science, currying is the technique of translating a function that takes multiple arguments into a sequence of families of functions, each taking a single argument. +Sequence diagram + +![Currying sequence diagram](./etc/currying-sequence-diagram.png) + ## Programmatic example of Currying Pattern in Java Consider a librarian who wants to populate their library with books. The librarian wants functions which can create books corresponding to specific genres and authors. Currying makes this possible by writing a curried book builder function and utilising partial application. diff --git a/currying/etc/currying-sequence-diagram.png b/currying/etc/currying-sequence-diagram.png new file mode 100644 index 000000000000..7c3300263566 Binary files /dev/null and b/currying/etc/currying-sequence-diagram.png differ diff --git a/currying/pom.xml b/currying/pom.xml index 5b5385c45a59..5244aa2792d8 100644 --- a/currying/pom.xml +++ b/currying/pom.xml @@ -36,6 +36,14 @@ currying + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/currying/src/main/java/com/iluwatar/currying/App.java b/currying/src/main/java/com/iluwatar/currying/App.java index d3ca262b56c4..d919887530a2 100644 --- a/currying/src/main/java/com/iluwatar/currying/App.java +++ b/currying/src/main/java/com/iluwatar/currying/App.java @@ -28,19 +28,17 @@ import lombok.extern.slf4j.Slf4j; /** -* Currying decomposes a function with multiple arguments in multiple functions that -* take a single argument. A curried function which has only been passed some of its -* arguments is called a partial application. Partial application is useful since it can -* be used to create specialised functions in a concise way. -* -*

    In this example, a librarian uses a curried book builder function create books belonging to -* desired genres and written by specific authors. -*/ + * Currying decomposes a function with multiple arguments in multiple functions that take a single + * argument. A curried function which has only been passed some of its arguments is called a partial + * application. Partial application is useful since it can be used to create specialised functions + * in a concise way. + * + *

    In this example, a librarian uses a curried book builder function create books belonging to + * desired genres and written by specific authors. + */ @Slf4j public class App { - /** - * Main entry point of the program. - */ + /** Main entry point of the program. */ public static void main(String[] args) { LOGGER.info("Librarian begins their work."); @@ -55,20 +53,28 @@ public static void main(String[] args) { Book.AddTitle rowlingFantasyBooksFunc = fantasyBookFunc.withAuthor("J.K. Rowling"); // Creates books by Stephen King (horror and fantasy genres) - Book shining = kingHorrorBooksFunc.withTitle("The Shining") - .withPublicationDate(LocalDate.of(1977, 1, 28)); - Book darkTower = kingFantasyBooksFunc.withTitle("The Dark Tower: Gunslinger") + Book shining = + kingHorrorBooksFunc.withTitle("The Shining").withPublicationDate(LocalDate.of(1977, 1, 28)); + Book darkTower = + kingFantasyBooksFunc + .withTitle("The Dark Tower: Gunslinger") .withPublicationDate(LocalDate.of(1982, 6, 10)); // Creates fantasy books by J.K. Rowling - Book chamberOfSecrets = rowlingFantasyBooksFunc.withTitle("Harry Potter and the Chamber of Secrets") + Book chamberOfSecrets = + rowlingFantasyBooksFunc + .withTitle("Harry Potter and the Chamber of Secrets") .withPublicationDate(LocalDate.of(1998, 7, 2)); // Create sci-fi books - Book dune = scifiBookFunc.withAuthor("Frank Herbert") + Book dune = + scifiBookFunc + .withAuthor("Frank Herbert") .withTitle("Dune") .withPublicationDate(LocalDate.of(1965, 8, 1)); - Book foundation = scifiBookFunc.withAuthor("Isaac Asimov") + Book foundation = + scifiBookFunc + .withAuthor("Isaac Asimov") .withTitle("Foundation") .withPublicationDate(LocalDate.of(1942, 5, 1)); @@ -83,4 +89,4 @@ public static void main(String[] args) { LOGGER.info(dune.toString()); LOGGER.info(foundation.toString()); } -} \ No newline at end of file +} diff --git a/currying/src/main/java/com/iluwatar/currying/Book.java b/currying/src/main/java/com/iluwatar/currying/Book.java index f9b7be05138a..7ec4ed8cb448 100644 --- a/currying/src/main/java/com/iluwatar/currying/Book.java +++ b/currying/src/main/java/com/iluwatar/currying/Book.java @@ -29,9 +29,7 @@ import java.util.function.Function; import lombok.AllArgsConstructor; -/** - * Book class. - */ +/** Book class. */ @AllArgsConstructor public class Book { private final Genre genre; @@ -49,9 +47,9 @@ public boolean equals(Object o) { } Book book = (Book) o; return Objects.equals(author, book.author) - && Objects.equals(genre, book.genre) - && Objects.equals(title, book.title) - && Objects.equals(publicationDate, book.publicationDate); + && Objects.equals(genre, book.genre) + && Objects.equals(title, book.title) + && Objects.equals(publicationDate, book.publicationDate); } @Override @@ -61,57 +59,55 @@ public int hashCode() { @Override public String toString() { - return "Book{" + "genre=" + genre + ", author='" + author + '\'' - + ", title='" + title + '\'' + ", publicationDate=" + publicationDate + '}'; + return "Book{" + + "genre=" + + genre + + ", author='" + + author + + '\'' + + ", title='" + + title + + '\'' + + ", publicationDate=" + + publicationDate + + '}'; } - /** - * Curried book builder/creator function. - */ - static Function>>> book_creator - = bookGenre - -> bookAuthor - -> bookTitle - -> bookPublicationDate - -> new Book(bookGenre, bookAuthor, bookTitle, bookPublicationDate); + /** Curried book builder/creator function. */ + static Function>>> + book_creator = + bookGenre -> + bookAuthor -> + bookTitle -> + bookPublicationDate -> + new Book(bookGenre, bookAuthor, bookTitle, bookPublicationDate); /** * Implements the builder pattern using functional interfaces to create a more readable book * creator function. This function is equivalent to the BOOK_CREATOR function. */ public static AddGenre builder() { - return genre - -> author - -> title - -> publicationDate - -> new Book(genre, author, title, publicationDate); + return genre -> + author -> title -> publicationDate -> new Book(genre, author, title, publicationDate); } - /** - * Functional interface which adds the genre to a book. - */ + /** Functional interface which adds the genre to a book. */ public interface AddGenre { Book.AddAuthor withGenre(Genre genre); } - /** - * Functional interface which adds the author to a book. - */ + /** Functional interface which adds the author to a book. */ public interface AddAuthor { Book.AddTitle withAuthor(String author); } - /** - * Functional interface which adds the title to a book. - */ + /** Functional interface which adds the title to a book. */ public interface AddTitle { Book.AddPublicationDate withTitle(String title); } - /** - * Functional interface which adds the publication date to a book. - */ + /** Functional interface which adds the publication date to a book. */ public interface AddPublicationDate { Book withPublicationDate(LocalDate publicationDate); } -} \ No newline at end of file +} diff --git a/currying/src/main/java/com/iluwatar/currying/Genre.java b/currying/src/main/java/com/iluwatar/currying/Genre.java index 8e9fbfd63e6d..ad41f4004190 100644 --- a/currying/src/main/java/com/iluwatar/currying/Genre.java +++ b/currying/src/main/java/com/iluwatar/currying/Genre.java @@ -24,9 +24,7 @@ */ package com.iluwatar.currying; -/** - * Enum representing different book genres. - */ +/** Enum representing different book genres. */ public enum Genre { FANTASY, HORROR, diff --git a/currying/src/test/java/com/iluwatar/currying/AppTest.java b/currying/src/test/java/com/iluwatar/currying/AppTest.java index 83fd99363e52..3074628f29e6 100644 --- a/currying/src/test/java/com/iluwatar/currying/AppTest.java +++ b/currying/src/test/java/com/iluwatar/currying/AppTest.java @@ -28,12 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * Tests that the App can be run without throwing any exceptions. - */ +/** Tests that the App can be run without throwing any exceptions. */ class AppTest { @Test void executesWithoutExceptions() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java b/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java index 74ac4ead321b..f22ff62850e5 100644 --- a/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java +++ b/currying/src/test/java/com/iluwatar/currying/BookCurryingTest.java @@ -26,36 +26,31 @@ import static org.junit.jupiter.api.Assertions.*; +import java.time.LocalDate; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.time.LocalDate; -/** - * Unit tests for the Book class - */ +/** Unit tests for the Book class */ class BookCurryingTest { private static Book expectedBook; @BeforeAll public static void initialiseBook() { - expectedBook = new Book(Genre.FANTASY, - "Dave", - "Into the Night", - LocalDate.of(2002, 4, 7)); + expectedBook = new Book(Genre.FANTASY, "Dave", "Into the Night", LocalDate.of(2002, 4, 7)); } - /** - * Tests that the expected book can be created via curried functions - */ + /** Tests that the expected book can be created via curried functions */ @Test void createsExpectedBook() { - Book builderCurriedBook = Book.builder() - .withGenre(Genre.FANTASY) - .withAuthor("Dave") - .withTitle("Into the Night") - .withPublicationDate(LocalDate.of(2002, 4, 7)); + Book builderCurriedBook = + Book.builder() + .withGenre(Genre.FANTASY) + .withAuthor("Dave") + .withTitle("Into the Night") + .withPublicationDate(LocalDate.of(2002, 4, 7)); - Book funcCurriedBook = Book.book_creator + Book funcCurriedBook = + Book.book_creator .apply(Genre.FANTASY) .apply("Dave") .apply("Into the Night") @@ -65,17 +60,15 @@ void createsExpectedBook() { assertEquals(expectedBook, funcCurriedBook); } - /** - * Tests that an intermediate curried function can be used to create the expected book - */ + /** Tests that an intermediate curried function can be used to create the expected book */ @Test void functionCreatesExpectedBook() { - Book.AddTitle daveFantasyBookFunc = Book.builder() - .withGenre(Genre.FANTASY) - .withAuthor("Dave"); + Book.AddTitle daveFantasyBookFunc = Book.builder().withGenre(Genre.FANTASY).withAuthor("Dave"); - Book curriedBook = daveFantasyBookFunc.withTitle("Into the Night") - .withPublicationDate(LocalDate.of(2002, 4, 7)); + Book curriedBook = + daveFantasyBookFunc + .withTitle("Into the Night") + .withPublicationDate(LocalDate.of(2002, 4, 7)); assertEquals(expectedBook, curriedBook); } diff --git a/data-access-object/README.md b/data-access-object/README.md index 1491eacf3299..7e84299e23e1 100644 --- a/data-access-object/README.md +++ b/data-access-object/README.md @@ -36,6 +36,10 @@ Wikipedia says > In computer software, a data access object (DAO) is a pattern that provides an abstract interface to some type of database or other persistence mechanism. +Sequence diagram + +![Data Access Object sequence diagram](./etc/dao-sequence-diagram.png) + ## Programmatic Example of DAO Pattern in Java There's a set of customers that need to be persisted to database. Additionally, we need the whole set of CRUD (create/read/update/delete) operations, so we can operate on customers easily. @@ -195,10 +199,6 @@ The program output: 10:02:09.898 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@f2f2cc1 ``` -## Detailed Explanation of Data Access Object Pattern with Real-World Examples - -![Data Access Object](./etc/dao.png "Data Access Object") - ## When to Use the Data Access Object Pattern in Java Use the Data Access Object in any of the following situations: diff --git a/data-access-object/etc/dao-sequence-diagram.png b/data-access-object/etc/dao-sequence-diagram.png new file mode 100644 index 000000000000..74488bd78107 Binary files /dev/null and b/data-access-object/etc/dao-sequence-diagram.png differ diff --git a/data-access-object/pom.xml b/data-access-object/pom.xml index 9685cbb96dd2..f96bf54cd818 100644 --- a/data-access-object/pom.xml +++ b/data-access-object/pom.xml @@ -34,6 +34,14 @@ data-access-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/data-access-object/src/main/java/com/iluwatar/dao/App.java b/data-access-object/src/main/java/com/iluwatar/dao/App.java index f0d19749c64b..106b7458c9cf 100644 --- a/data-access-object/src/main/java/com/iluwatar/dao/App.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/App.java @@ -66,14 +66,14 @@ public static void main(final String[] args) throws Exception { private static void deleteSchema(DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL); } } private static void createSchema(DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL); } } diff --git a/data-access-object/src/main/java/com/iluwatar/dao/CustomException.java b/data-access-object/src/main/java/com/iluwatar/dao/CustomException.java index de907a64136c..79470bd810ab 100644 --- a/data-access-object/src/main/java/com/iluwatar/dao/CustomException.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/CustomException.java @@ -26,13 +26,10 @@ import java.io.Serial; -/** - * Custom exception. - */ +/** Custom exception. */ public class CustomException extends Exception { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; public CustomException(String message, Throwable cause) { super(message, cause); diff --git a/data-access-object/src/main/java/com/iluwatar/dao/Customer.java b/data-access-object/src/main/java/com/iluwatar/dao/Customer.java index 89e0bfb5ea32..17a15fc1b92f 100644 --- a/data-access-object/src/main/java/com/iluwatar/dao/Customer.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/Customer.java @@ -30,9 +30,7 @@ import lombok.Setter; import lombok.ToString; -/** - * A customer POJO that represents the data that will be read from the data source. - */ +/** A customer POJO that represents the data that will be read from the data source. */ @Setter @Getter @ToString @@ -40,8 +38,7 @@ @AllArgsConstructor public class Customer { - @EqualsAndHashCode.Include - private int id; + @EqualsAndHashCode.Include private int id; private String firstName; private String lastName; } diff --git a/data-access-object/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java b/data-access-object/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java index 501b4155a670..aab41423a748 100644 --- a/data-access-object/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/CustomerSchemaSql.java @@ -24,18 +24,13 @@ */ package com.iluwatar.dao; -/** - * Customer Schema SQL Class. - */ +/** Customer Schema SQL Class. */ public final class CustomerSchemaSql { - private CustomerSchemaSql() { - } + private CustomerSchemaSql() {} public static final String CREATE_SCHEMA_SQL = - "CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), " - + "LNAME VARCHAR(100))"; + "CREATE TABLE CUSTOMERS (ID NUMBER, FNAME VARCHAR(100), " + "LNAME VARCHAR(100))"; public static final String DELETE_SCHEMA_SQL = "DROP TABLE CUSTOMERS"; - } diff --git a/data-access-object/src/main/java/com/iluwatar/dao/DbCustomerDao.java b/data-access-object/src/main/java/com/iluwatar/dao/DbCustomerDao.java index f369cb69ea62..cb75b195ddc4 100644 --- a/data-access-object/src/main/java/com/iluwatar/dao/DbCustomerDao.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/DbCustomerDao.java @@ -38,9 +38,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * An implementation of {@link CustomerDao} that persists customers in RDBMS. - */ +/** An implementation of {@link CustomerDao} that persists customers in RDBMS. */ @Slf4j @RequiredArgsConstructor public class DbCustomerDao implements CustomerDao { @@ -60,22 +58,24 @@ public Stream getAll() throws Exception { var connection = getConnection(); var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS"); // NOSONAR var resultSet = statement.executeQuery(); // NOSONAR - return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, - Spliterator.ORDERED) { - - @Override - public boolean tryAdvance(Consumer action) { - try { - if (!resultSet.next()) { - return false; - } - action.accept(createCustomer(resultSet)); - return true; - } catch (SQLException e) { - throw new RuntimeException(e); // NOSONAR - } - } - }, false).onClose(() -> mutedClose(connection, statement, resultSet)); + return StreamSupport.stream( + new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { + + @Override + public boolean tryAdvance(Consumer action) { + try { + if (!resultSet.next()) { + return false; + } + action.accept(createCustomer(resultSet)); + return true; + } catch (SQLException e) { + throw new RuntimeException(e); // NOSONAR + } + } + }, + false) + .onClose(() -> mutedClose(connection, statement, resultSet)); } catch (SQLException e) { throw new CustomException(e.getMessage(), e); } @@ -96,21 +96,18 @@ private void mutedClose(Connection connection, PreparedStatement statement, Resu } private Customer createCustomer(ResultSet resultSet) throws SQLException { - return new Customer(resultSet.getInt("ID"), - resultSet.getString("FNAME"), - resultSet.getString("LNAME")); + return new Customer( + resultSet.getInt("ID"), resultSet.getString("FNAME"), resultSet.getString("LNAME")); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Optional getById(int id) throws Exception { ResultSet resultSet = null; try (var connection = getConnection(); - var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { + var statement = connection.prepareStatement("SELECT * FROM CUSTOMERS WHERE ID = ?")) { statement.setInt(1, id); resultSet = statement.executeQuery(); @@ -128,9 +125,7 @@ public Optional getById(int id) throws Exception { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean add(Customer customer) throws Exception { if (getById(customer.getId()).isPresent()) { @@ -138,7 +133,7 @@ public boolean add(Customer customer) throws Exception { } try (var connection = getConnection(); - var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) { + var statement = connection.prepareStatement("INSERT INTO CUSTOMERS VALUES (?,?,?)")) { statement.setInt(1, customer.getId()); statement.setString(2, customer.getFirstName()); statement.setString(3, customer.getLastName()); @@ -149,15 +144,12 @@ public boolean add(Customer customer) throws Exception { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean update(Customer customer) throws Exception { try (var connection = getConnection(); - var statement = - connection - .prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) { + var statement = + connection.prepareStatement("UPDATE CUSTOMERS SET FNAME = ?, LNAME = ? WHERE ID = ?")) { statement.setString(1, customer.getFirstName()); statement.setString(2, customer.getLastName()); statement.setInt(3, customer.getId()); @@ -167,13 +159,11 @@ public boolean update(Customer customer) throws Exception { } } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean delete(Customer customer) throws Exception { try (var connection = getConnection(); - var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { + var statement = connection.prepareStatement("DELETE FROM CUSTOMERS WHERE ID = ?")) { statement.setInt(1, customer.getId()); return statement.executeUpdate() > 0; } catch (SQLException ex) { diff --git a/data-access-object/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java b/data-access-object/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java index 19979ff666ad..b5b7eb0c4ff7 100644 --- a/data-access-object/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java +++ b/data-access-object/src/main/java/com/iluwatar/dao/InMemoryCustomerDao.java @@ -31,17 +31,14 @@ /** * An in memory implementation of {@link CustomerDao}, which stores the customers in JVM memory and - * data is lost when the application exits. - *
    + * data is lost when the application exits.
    * This implementation is useful as temporary database or for testing. */ public class InMemoryCustomerDao implements CustomerDao { private final Map idToCustomer = new HashMap<>(); - /** - * An eagerly evaluated stream of customers stored in memory. - */ + /** An eagerly evaluated stream of customers stored in memory. */ @Override public Stream getAll() { return idToCustomer.values().stream(); diff --git a/data-access-object/src/test/java/com/iluwatar/dao/AppTest.java b/data-access-object/src/test/java/com/iluwatar/dao/AppTest.java index 64d8b2805eee..a43f37353341 100644 --- a/data-access-object/src/test/java/com/iluwatar/dao/AppTest.java +++ b/data-access-object/src/test/java/com/iluwatar/dao/AppTest.java @@ -24,23 +24,19 @@ */ package com.iluwatar.dao; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that DAO example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that DAO 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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - @Test void shouldExecuteDaoWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/data-access-object/src/test/java/com/iluwatar/dao/CustomerTest.java b/data-access-object/src/test/java/com/iluwatar/dao/CustomerTest.java index 7ceab4eae11f..f53c28935fea 100644 --- a/data-access-object/src/test/java/com/iluwatar/dao/CustomerTest.java +++ b/data-access-object/src/test/java/com/iluwatar/dao/CustomerTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests {@link Customer}. - */ +/** Tests {@link Customer}. */ class CustomerTest { private Customer customer; @@ -89,7 +87,10 @@ void equalsWithSameObjects() { @Test void testToString() { - assertEquals(String.format("Customer(id=%s, firstName=%s, lastName=%s)", - customer.getId(), customer.getFirstName(), customer.getLastName()), customer.toString()); + assertEquals( + String.format( + "Customer(id=%s, firstName=%s, lastName=%s)", + customer.getId(), customer.getFirstName(), customer.getLastName()), + customer.toString()); } } diff --git a/data-access-object/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java b/data-access-object/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java index 759ab155c2f2..2f116d108519 100644 --- a/data-access-object/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java +++ b/data-access-object/src/test/java/com/iluwatar/dao/DbCustomerDaoTest.java @@ -44,9 +44,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -/** - * Tests {@link DbCustomerDao}. - */ +/** Tests {@link DbCustomerDao}. */ class DbCustomerDaoTest { private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1"; @@ -61,14 +59,12 @@ class DbCustomerDaoTest { @BeforeEach void createSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL); } } - /** - * Represents the scenario where DB connectivity is present. - */ + /** Represents the scenario where DB connectivity is present. */ @Nested class ConnectionSuccess { @@ -160,8 +156,8 @@ void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception } @Test - void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws - Exception { + void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() + throws Exception { final var newFirstname = "Bernard"; final var newLastname = "Montgomery"; final var customer = new Customer(existingCustomer.getId(), newFirstname, newLastname); @@ -218,7 +214,9 @@ void deletingACustomerFailsWithExceptionAsFeedbackToTheClient() { void updatingACustomerFailsWithFeedbackToTheClient() { final var newFirstname = "Bernard"; final var newLastname = "Montgomery"; - assertThrows(Exception.class, () -> dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname))); + assertThrows( + Exception.class, + () -> dao.update(new Customer(existingCustomer.getId(), newFirstname, newLastname))); } @Test @@ -230,7 +228,6 @@ void retrievingACustomerByIdFailsWithExceptionAsFeedbackToClient() { void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() { assertThrows(Exception.class, () -> dao.getAll()); } - } /** @@ -241,7 +238,7 @@ void retrievingAllCustomersFailsWithExceptionAsFeedbackToClient() { @AfterEach void deleteSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL); } } diff --git a/data-access-object/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java b/data-access-object/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java index 340a0920c7f1..93ce2475f2c8 100644 --- a/data-access-object/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java +++ b/data-access-object/src/test/java/com/iluwatar/dao/InMemoryCustomerDaoTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -/** - * Tests {@link InMemoryCustomerDao}. - */ +/** Tests {@link InMemoryCustomerDao}. */ class InMemoryCustomerDaoTest { private InMemoryCustomerDao dao; @@ -48,8 +46,7 @@ void setUp() { } /** - * Represents the scenario when the DAO operations are being performed on a non-existent - * customer. + * Represents the scenario when the DAO operations are being performed on a non-existent customer. */ @Nested class NonExistingCustomer { @@ -121,8 +118,8 @@ void deletionShouldBeSuccessAndCustomerShouldBeNonAccessible() throws Exception } @Test - void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() throws - Exception { + void updationShouldBeSuccessAndAccessingTheSameCustomerShouldReturnUpdatedInformation() + throws Exception { final var newFirstname = "Bernard"; final var newLastname = "Montgomery"; final var customer = new Customer(CUSTOMER.getId(), newFirstname, newLastname); diff --git a/data-bus/README.md b/data-bus/README.md index a033253cfc04..ee687a52d1dd 100644 --- a/data-bus/README.md +++ b/data-bus/README.md @@ -31,6 +31,10 @@ In plain words > Data Bus is a design pattern that connects components of an application for communication based on the type of message or event being transferred. This pattern promotes decoupling, making it easier to scale and maintain the system by allowing components to communicate without direct dependencies. +Sequence diagram + +![Data Bus Sequence Diagram](./etc/data-bus-sequence-diagram.png) + ## Programmatic Example of Data Bus Pattern in Java Say you have an app that enables online bookings and participation in events. You want the app to send notifications, such as event advertisements, to all ordinary members of the community or organization holding the events. However, you do not want to send such advertisements to event administrators or organizers. Instead, you want to send them notifications about the timing of new advertisements sent to all members. The Data Bus enables you to selectively notify community members by type (ordinary members or event administrators) by making their classes or components only accept messages of a certain type. Thus, ordinary members and administrators do not need to know about each other or the specific classes or components used to notify the entire community, except for knowing the type of messages being sent. diff --git a/data-bus/etc/data-bus-sequence-diagram.png b/data-bus/etc/data-bus-sequence-diagram.png new file mode 100644 index 000000000000..8924693923d4 Binary files /dev/null and b/data-bus/etc/data-bus-sequence-diagram.png differ diff --git a/data-bus/pom.xml b/data-bus/pom.xml index aef3a0cd6e48..ef7c88acb413 100644 --- a/data-bus/pom.xml +++ b/data-bus/pom.xml @@ -34,6 +34,14 @@ data-bus + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java b/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java index 311bed1ef6c1..aa1ce3d45598 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java +++ b/data-bus/src/main/java/com/iluwatar/databus/AbstractDataType.java @@ -51,10 +51,7 @@ of this software and associated documentation files (the "Software"), to deal import lombok.Getter; import lombok.Setter; -/** - * Base for data to send via the Data-Bus. - * - */ +/** Base for data to send via the Data-Bus. */ @Getter @Setter public class AbstractDataType implements DataType { diff --git a/data-bus/src/main/java/com/iluwatar/databus/App.java b/data-bus/src/main/java/com/iluwatar/databus/App.java index 1c0d99bd22fb..2c1d6b5e8a25 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/App.java +++ b/data-bus/src/main/java/com/iluwatar/databus/App.java @@ -35,26 +35,25 @@ * The Data Bus pattern. * * @see http://wiki.c2.com/?DataBusPattern - *

    The Data-Bus pattern provides a method where different parts of an application may - * pass messages between each other without needing to be aware of the other's existence.

    + *

    The Data-Bus pattern provides a method where different parts of an application may pass + * messages between each other without needing to be aware of the other's existence. *

    Similar to the {@code ObserverPattern}, members register themselves with the {@link * DataBus} and may then receive each piece of data that is published to the Data-Bus. The - * member may react to any given message or not.

    - *

    It allows for Many-to-Many distribution of data, as there may be any number of - * publishers to a Data-Bus, and any number of members receiving the data. All members will - * receive the same data, the order each receives a given piece of data, is an implementation - * detail.

    - *

    Members may unsubscribe from the Data-Bus to stop receiving data.

    - *

    This example of the pattern implements a Synchronous Data-Bus, meaning that - * when data is published to the Data-Bus, the publish method will not return until all members - * have received the data and returned.

    - *

    The {@link DataBus} class is a Singleton.

    - *

    Members of the Data-Bus must implement the {@link Member} interface.

    - *

    Data to be published via the Data-Bus must implement the {@link DataType} interface.

    - *

    The {@code data} package contains example {@link DataType} implementations.

    - *

    The {@code members} package contains example {@link Member} implementations.

    - *

    The {@link StatusMember} demonstrates using the DataBus to publish a message - * to the Data-Bus when it receives a message.

    + * member may react to any given message or not. + *

    It allows for Many-to-Many distribution of data, as there may be any number of publishers + * to a Data-Bus, and any number of members receiving the data. All members will receive the + * same data, the order each receives a given piece of data, is an implementation detail. + *

    Members may unsubscribe from the Data-Bus to stop receiving data. + *

    This example of the pattern implements a Synchronous Data-Bus, meaning that when data is + * published to the Data-Bus, the publish method will not return until all members have received + * the data and returned. + *

    The {@link DataBus} class is a Singleton. + *

    Members of the Data-Bus must implement the {@link Member} interface. + *

    Data to be published via the Data-Bus must implement the {@link DataType} interface. + *

    The {@code data} package contains example {@link DataType} implementations. + *

    The {@code members} package contains example {@link Member} implementations. + *

    The {@link StatusMember} demonstrates using the DataBus to publish a message to the + * Data-Bus when it receives a message. */ class App { diff --git a/data-bus/src/main/java/com/iluwatar/databus/DataBus.java b/data-bus/src/main/java/com/iluwatar/databus/DataBus.java index 4b1fe1a7489c..f302d741f776 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/DataBus.java +++ b/data-bus/src/main/java/com/iluwatar/databus/DataBus.java @@ -30,8 +30,7 @@ /** * The Data-Bus implementation. * - *

    This implementation uses a Singleton.

    - * + *

    This implementation uses a Singleton. */ public class DataBus { diff --git a/data-bus/src/main/java/com/iluwatar/databus/DataType.java b/data-bus/src/main/java/com/iluwatar/databus/DataType.java index 7281995927a7..b84797bf03ea 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/DataType.java +++ b/data-bus/src/main/java/com/iluwatar/databus/DataType.java @@ -48,11 +48,7 @@ of this software and associated documentation files (the "Software"), to deal package com.iluwatar.databus; -/** - * Events are sent via the Data-Bus. - * - */ - +/** Events are sent via the Data-Bus. */ public interface DataType { /** diff --git a/data-bus/src/main/java/com/iluwatar/databus/Member.java b/data-bus/src/main/java/com/iluwatar/databus/Member.java index 9eff9078e12c..3d4e976409ac 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/Member.java +++ b/data-bus/src/main/java/com/iluwatar/databus/Member.java @@ -50,10 +50,7 @@ of this software and associated documentation files (the "Software"), to deal import java.util.function.Consumer; -/** - * Members receive events from the Data-Bus. - * - */ +/** Members receive events from the Data-Bus. */ public interface Member extends Consumer { void accept(DataType event); diff --git a/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java b/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java index 55228975e79d..970fc6973050 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java +++ b/data-bus/src/main/java/com/iluwatar/databus/data/MessageData.java @@ -29,10 +29,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; -/** - * An event raised when a string message is sent. - * - */ +/** An event raised when a string message is sent. */ @Getter @AllArgsConstructor public class MessageData extends AbstractDataType { diff --git a/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java b/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java index 793e31446d2c..554a0ff71780 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java +++ b/data-bus/src/main/java/com/iluwatar/databus/data/StartingData.java @@ -30,10 +30,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * An event raised when applications starts, containing the start time of the application. - * - */ +/** An event raised when applications starts, containing the start time of the application. */ @RequiredArgsConstructor @Getter public class StartingData extends AbstractDataType { diff --git a/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java b/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java index 605db3937aa4..090f312650a4 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java +++ b/data-bus/src/main/java/com/iluwatar/databus/data/StoppingData.java @@ -30,10 +30,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * An event raised when applications stops, containing the stop time of the application. - * - */ +/** An event raised when applications stops, containing the stop time of the application. */ @RequiredArgsConstructor @Getter public class StoppingData extends AbstractDataType { diff --git a/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java b/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java index 1ebbdf8d8869..8eb81da1ae8b 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java +++ b/data-bus/src/main/java/com/iluwatar/databus/members/MessageCollectorMember.java @@ -31,10 +31,7 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; -/** - * Receiver of Data-Bus events that collects the messages from each {@link MessageData}. - * - */ +/** Receiver of Data-Bus events that collects the messages from each {@link MessageData}. */ @Slf4j public class MessageCollectorMember implements Member { diff --git a/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java b/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java index 5790f6b72b93..d6aab7730b02 100644 --- a/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java +++ b/data-bus/src/main/java/com/iluwatar/databus/members/StatusMember.java @@ -34,10 +34,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Receiver of Data-Bus events. - * - */ +/** Receiver of Data-Bus events. */ @Getter @Slf4j @RequiredArgsConstructor @@ -69,5 +66,4 @@ private void handleEvent(StoppingData data) { LOGGER.info("Receiver {} sending goodbye message", id); data.getDataBus().publish(MessageData.of(String.format("Goodbye cruel world from #%d!", id))); } - } diff --git a/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java b/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java index d9f0c6a6cb0b..a948201b4d76 100644 --- a/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java +++ b/data-bus/src/test/java/com/iluwatar/databus/DataBusTest.java @@ -32,17 +32,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * Tests for {@link DataBus}. - * - */ +/** Tests for {@link DataBus}. */ class DataBusTest { - @Mock - private Member member; + @Mock private Member member; - @Mock - private DataType event; + @Mock private DataType event; @BeforeEach void setUp() { @@ -51,25 +46,24 @@ void setUp() { @Test void publishedEventIsReceivedBySubscribedMember() { - //given + // given final var dataBus = DataBus.getInstance(); dataBus.subscribe(member); - //when + // when dataBus.publish(event); - //then + // then then(member).should().accept(event); } @Test void publishedEventIsNotReceivedByMemberAfterUnsubscribing() { - //given + // given final var dataBus = DataBus.getInstance(); dataBus.subscribe(member); dataBus.unsubscribe(member); - //when + // when dataBus.publish(event); - //then + // then then(member).should(never()).accept(event); } - } diff --git a/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java b/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java index 45df955a7649..e0f881108df7 100644 --- a/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java +++ b/data-bus/src/test/java/com/iluwatar/databus/members/MessageCollectorMemberTest.java @@ -32,33 +32,29 @@ import java.time.LocalDateTime; import org.junit.jupiter.api.Test; -/** - * Tests for {@link MessageCollectorMember}. - * - */ +/** Tests for {@link MessageCollectorMember}. */ class MessageCollectorMemberTest { @Test void collectMessageFromMessageData() { - //given + // given final var message = "message"; final var messageData = new MessageData(message); final var collector = new MessageCollectorMember("collector"); - //when + // when collector.accept(messageData); - //then + // then assertTrue(collector.getMessages().contains(message)); } @Test void collectIgnoresMessageFromOtherDataTypes() { - //given + // given final var startingData = new StartingData(LocalDateTime.now()); final var collector = new MessageCollectorMember("collector"); - //when + // when collector.accept(startingData); - //then + // then assertEquals(0, collector.getMessages().size()); } - } diff --git a/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java b/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java index 342ec4806f5b..b7aa8b826084 100644 --- a/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java +++ b/data-bus/src/test/java/com/iluwatar/databus/members/StatusMemberTest.java @@ -35,47 +35,43 @@ import java.time.Month; import org.junit.jupiter.api.Test; -/** - * Tests for {@link StatusMember}. - * - */ +/** Tests for {@link StatusMember}. */ class StatusMemberTest { @Test void statusRecordsTheStartTime() { - //given + // given final var startTime = LocalDateTime.of(2017, Month.APRIL, 1, 19, 9); final var startingData = new StartingData(startTime); final var statusMember = new StatusMember(1); - //when + // when statusMember.accept(startingData); - //then + // then assertEquals(startTime, statusMember.getStarted()); } @Test void statusRecordsTheStopTime() { - //given + // given final var stop = LocalDateTime.of(2017, Month.APRIL, 1, 19, 12); final var stoppingData = new StoppingData(stop); stoppingData.setDataBus(DataBus.getInstance()); final var statusMember = new StatusMember(1); - //when + // when statusMember.accept(stoppingData); - //then + // then assertEquals(stop, statusMember.getStopped()); } @Test void statusIgnoresMessageData() { - //given + // given final var messageData = new MessageData("message"); final var statusMember = new StatusMember(1); - //when + // when statusMember.accept(messageData); - //then + // then assertNull(statusMember.getStarted()); assertNull(statusMember.getStopped()); } - } diff --git a/data-locality/README.md b/data-locality/README.md index 9056b2025b3c..a59103b3f89b 100644 --- a/data-locality/README.md +++ b/data-locality/README.md @@ -31,6 +31,14 @@ In plain words > The Data Locality pattern organizes data in memory to reduce access times and improve performance by ensuring that data frequently accessed together is stored close together. This is crucial for high-performance applications like game engines and real-time data processing systems. +Mind map + +![Data Locality mind map](./etc/data-locality-mind-map.png) + +Flowchart + +![Data Locality flowchart](./etc/data-locality-flowchart.png) + ## Programmatic Example of Data Locality Pattern in Java The Data Locality pattern is a design pattern that aims to improve performance by arranging data in memory to take advantage of spatial locality. This pattern is particularly useful in high-performance computing and game development where access speed is crucial. @@ -120,10 +128,6 @@ The console output: In this way, the data-locality module demonstrates the Data Locality pattern. By updating all components of the same type together, it increases the likelihood that the data needed for the update is already in the cache, thereby improving performance. -## Detailed Explanation of Data Locality Pattern with Real-World Examples - -![Data Locality](./etc/data-locality.urm.png "Data Locality pattern class diagram") - ## When to Use the Data Locality Pattern in Java This pattern is applicable in scenarios where large datasets are processed and performance is critical. It's particularly useful in: diff --git a/data-locality/etc/data-locality-flowchart.png b/data-locality/etc/data-locality-flowchart.png new file mode 100644 index 000000000000..deedabbdfb8b Binary files /dev/null and b/data-locality/etc/data-locality-flowchart.png differ diff --git a/data-locality/etc/data-locality-mind-map.png b/data-locality/etc/data-locality-mind-map.png new file mode 100644 index 000000000000..c2761f45bf5a Binary files /dev/null and b/data-locality/etc/data-locality-mind-map.png differ diff --git a/data-locality/pom.xml b/data-locality/pom.xml index c5ba5531f1b5..593f6793473a 100644 --- a/data-locality/pom.xml +++ b/data-locality/pom.xml @@ -34,6 +34,14 @@ data-locality + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/Application.java b/data-locality/src/main/java/com/iluwatar/data/locality/Application.java index 1e87c8e3cc3e..862fa11732bf 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/Application.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/Application.java @@ -32,17 +32,15 @@ * improve performance by increasing data locality — keeping data in contiguous memory in the order * that you process it. * - *

    Example: Game loop that processes a bunch of game entities. Those entities are decomposed - * into different domains  — AI, physics, and rendering — using the Component pattern. + *

    Example: Game loop that processes a bunch of game entities. Those entities are decomposed into + * different domains  — AI, physics, and rendering — using the Component pattern. */ @Slf4j public class Application { private static final int NUM_ENTITIES = 5; - /** - * Start game loop with each component have NUM_ENTITIES instance. - */ + /** Start game loop with each component have NUM_ENTITIES instance. */ public static void main(String[] args) { LOGGER.info("Start Game Application using Data-Locality pattern"); var gameEntity = new GameEntity(NUM_ENTITIES); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java index 06989e6f68e0..92ce918a7345 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/GameEntity.java @@ -46,9 +46,7 @@ public class GameEntity { private final PhysicsComponentManager physicsComponentManager; private final RenderComponentManager renderComponentManager; - /** - * Init components. - */ + /** Init components. */ public GameEntity(int numEntities) { LOGGER.info("Init Game with #Entity : {}", numEntities); aiComponentManager = new AiComponentManager(numEntities); @@ -56,9 +54,7 @@ public GameEntity(int numEntities) { renderComponentManager = new RenderComponentManager(numEntities); } - /** - * start all component. - */ + /** start all component. */ public void start() { LOGGER.info("Start Game"); aiComponentManager.start(); @@ -66,9 +62,7 @@ public void start() { renderComponentManager.start(); } - /** - * update all component. - */ + /** update all component. */ public void update() { LOGGER.info("Update Game Component"); // Process AI. @@ -80,5 +74,4 @@ public void update() { // Draw to screen. renderComponentManager.render(); } - } diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java index efa8a948da59..2f3cb043fcb1 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/AiComponent.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * Implementation of AI component for Game. - */ +/** Implementation of AI component for Game. */ @Slf4j public class AiComponent implements Component { - /** - * Update ai component. - */ + /** Update ai component. */ @Override public void update() { LOGGER.info("update AI component"); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java index 263d09281af5..5775804fbc05 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/Component.java @@ -24,9 +24,7 @@ */ package com.iluwatar.data.locality.game.component; -/** - * Implement different Game component update and render process. - */ +/** Implement different Game component update and render process. */ public interface Component { void update(); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java index 632836ea366a..43b762aaf4ed 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/PhysicsComponent.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * Implementation of Physics Component of Game. - */ +/** Implementation of Physics Component of Game. */ @Slf4j public class PhysicsComponent implements Component { - /** - * update physics component of game. - */ + /** update physics component of game. */ @Override public void update() { LOGGER.info("Update physics component of game"); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java index 45f61eba1285..c04b2bc8fa80 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/RenderComponent.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Implementation of Render Component of Game. - */ +/** Implementation of Render Component of Game. */ @Slf4j public class RenderComponent implements Component { @@ -37,9 +35,7 @@ public void update() { // do nothing } - /** - * render. - */ + /** render. */ @Override public void render() { LOGGER.info("Render Component"); diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java index 90c234a97202..a69d77b13da4 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/AiComponentManager.java @@ -29,9 +29,7 @@ import java.util.stream.IntStream; import lombok.extern.slf4j.Slf4j; -/** - * AI component manager for Game. - */ +/** AI component manager for Game. */ @Slf4j public class AiComponentManager { @@ -45,17 +43,13 @@ public AiComponentManager(int numEntities) { this.numEntities = numEntities; } - /** - * start AI component of Game. - */ + /** start AI component of Game. */ public void start() { LOGGER.info("Start AI Game Component"); IntStream.range(0, numEntities).forEach(i -> aiComponents[i] = new AiComponent()); } - /** - * Update AI component of Game. - */ + /** Update AI component of Game. */ public void update() { LOGGER.info("Update AI Game Component"); IntStream.range(0, numEntities) diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java index 58c85f2fd05f..c9b1a9bbeb10 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/PhysicsComponentManager.java @@ -29,9 +29,7 @@ import java.util.stream.IntStream; import lombok.extern.slf4j.Slf4j; -/** - * Physics component Manager for Game. - */ +/** Physics component Manager for Game. */ @Slf4j public class PhysicsComponentManager { @@ -45,18 +43,13 @@ public PhysicsComponentManager(int numEntities) { this.numEntities = numEntities; } - /** - * Start physics component of Game. - */ + /** Start physics component of Game. */ public void start() { LOGGER.info("Start Physics Game Component "); IntStream.range(0, numEntities).forEach(i -> physicsComponents[i] = new PhysicsComponent()); } - - /** - * Update physics component of Game. - */ + /** Update physics component of Game. */ public void update() { LOGGER.info("Update Physics Game Component "); // Process physics. diff --git a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java index 40cd0815b2b1..cff4bc3ac40a 100644 --- a/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java +++ b/data-locality/src/main/java/com/iluwatar/data/locality/game/component/manager/RenderComponentManager.java @@ -29,9 +29,7 @@ import java.util.stream.IntStream; import lombok.extern.slf4j.Slf4j; -/** - * Render component manager for Game. - */ +/** Render component manager for Game. */ @Slf4j public class RenderComponentManager { @@ -45,18 +43,13 @@ public RenderComponentManager(int numEntities) { this.numEntities = numEntities; } - /** - * Start render component. - */ + /** Start render component. */ public void start() { LOGGER.info("Start Render Game Component "); IntStream.range(0, numEntities).forEach(i -> renderComponents[i] = new RenderComponent()); } - - /** - * render component. - */ + /** render component. */ public void render() { LOGGER.info("Update Render Game Component "); // Process Render. diff --git a/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java b/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java index 1f1689425f41..badf5aadd027 100644 --- a/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java +++ b/data-locality/src/test/java/com/iluwatar/data/locality/ApplicationTest.java @@ -24,24 +24,20 @@ */ package com.iluwatar.data.locality; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Test Game Application - */ +/** Test Game Application */ class ApplicationTest { /** - * Issue: Add at least one assertion to this test case. - * Solution: Inserted assertion to check whether the execution of the main method in {@link Application#main(String[])} - * throws an exception. + * Issue: Add at least one assertion to this test case. Solution: Inserted assertion to check + * whether the execution of the main method in {@link Application#main(String[])} throws an + * exception. */ - @Test void shouldExecuteGameApplicationWithoutException() { - assertDoesNotThrow(() -> Application.main(new String[]{})); + assertDoesNotThrow(() -> Application.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/data-mapper/README.md b/data-mapper/README.md index 92c8e30211ea..b3bcb97f6f7b 100644 --- a/data-mapper/README.md +++ b/data-mapper/README.md @@ -34,6 +34,10 @@ Wikipedia says > A Data Mapper is a Data Access Layer that performs bidirectional transfer of data between a persistent data store (often a relational database) and an in-memory data representation (the domain layer). The goal of the pattern is to keep the in-memory representation and the persistent data store independent of each other and the data mapper itself. This is useful when one needs to model and enforce strict business processes on the data in the domain layer that do not map neatly to the persistent data store. +Sequence diagram + +![Data Mapper sequence diagram](./etc/data-mapper-sequence-diagram.png) + ## Programmatic Example of Data Mapper Pattern in Java The Data Mapper is a design pattern that separates the in-memory objects from the database. Its responsibility is to transfer data between the two and also to isolate them from each other. This pattern promotes the [Single Responsibility Principle](https://java-design-patterns.com/principles/#single-responsibility-principle) and [Separation of Concerns](https://java-design-patterns.com/principles/#separation-of-concerns). diff --git a/data-mapper/etc/data-mapper-sequence-diagram.png b/data-mapper/etc/data-mapper-sequence-diagram.png new file mode 100644 index 000000000000..c59b23a67649 Binary files /dev/null and b/data-mapper/etc/data-mapper-sequence-diagram.png differ diff --git a/data-mapper/pom.xml b/data-mapper/pom.xml index 0c7907cb53cf..40e8c3cee51d 100644 --- a/data-mapper/pom.xml +++ b/data-mapper/pom.xml @@ -34,6 +34,14 @@ data-mapper + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/App.java b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java index 5969ee2f2a46..3a7ed69f9711 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/App.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/App.java @@ -77,6 +77,5 @@ public static void main(final String... args) { mapper.delete(student); } - private App() { - } + private App() {} } diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java b/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java index fcbe56ac0fd8..f02dcef33dfe 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/DataMapperException.java @@ -29,19 +29,17 @@ /** * Using Runtime Exception for avoiding dependency on implementation exceptions. This helps in * decoupling. - * */ public final class DataMapperException extends RuntimeException { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; /** * Constructs a new runtime exception with the specified detail message. The cause is not * initialized, and may subsequently be initialized by a call to {@link #initCause}. * * @param message the detail message. The detail message is saved for later retrieval by the - * {@link #getMessage()} method. + * {@link #getMessage()} method. */ public DataMapperException(final String message) { super(message); diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java index 1691d1233246..8d8a42116a06 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/Student.java @@ -1,53 +1,48 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.datamapper; - -import java.io.Serial; -import java.io.Serializable; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -/** - * Class defining Student. - */ -@EqualsAndHashCode(onlyExplicitlyIncluded = true) -@ToString -@Getter -@Setter -@AllArgsConstructor -public final class Student implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - @EqualsAndHashCode.Include - private int studentId; - private String name; - private char grade; - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.datamapper; + +import java.io.Serial; +import java.io.Serializable; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** Class defining Student. */ +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString +@Getter +@Setter +@AllArgsConstructor +public final class Student implements Serializable { + + @Serial private static final long serialVersionUID = 1L; + + @EqualsAndHashCode.Include private int studentId; + private String name; + private char grade; +} diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java index 2700e30f0bc4..ab28bd3dc4e7 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapper.java @@ -1,41 +1,39 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.datamapper; - -import java.util.Optional; - -/** - * Interface lists out the possible behaviour for all possible student mappers. - */ -public interface StudentDataMapper { - - Optional find(int studentId); - - void insert(Student student) throws DataMapperException; - - void update(Student student) throws DataMapperException; - - void delete(Student student) throws DataMapperException; -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.datamapper; + +import java.util.Optional; + +/** Interface lists out the possible behaviour for all possible student mappers. */ +public interface StudentDataMapper { + + Optional find(int studentId); + + void insert(Student student) throws DataMapperException; + + void update(Student student) throws DataMapperException; + + void delete(Student student) throws DataMapperException; +} diff --git a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java index 9da1c8f7d169..67ddc0ce6f1a 100644 --- a/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java +++ b/data-mapper/src/main/java/com/iluwatar/datamapper/StudentDataMapperImpl.java @@ -29,9 +29,7 @@ import java.util.Optional; import lombok.Getter; -/** - * Implementation of Actions on Students Data. - */ +/** Implementation of Actions on Students Data. */ @Getter public final class StudentDataMapperImpl implements StudentDataMapper { @@ -46,11 +44,12 @@ public Optional find(int studentId) { @Override public void update(Student studentToBeUpdated) throws DataMapperException { String name = studentToBeUpdated.getName(); - Integer index = Optional.of(studentToBeUpdated) - .map(Student::getStudentId) - .flatMap(this::find) - .map(students::indexOf) - .orElseThrow(() -> new DataMapperException("Student [" + name + "] is not found")); + Integer index = + Optional.of(studentToBeUpdated) + .map(Student::getStudentId) + .flatMap(this::find) + .map(students::indexOf) + .orElseThrow(() -> new DataMapperException("Student [" + name + "] is not found")); students.set(index, studentToBeUpdated); } diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java index f45f6cc3266e..a7118721dc36 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/AppTest.java @@ -1,48 +1,44 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.datamapper; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Tests that Data-Mapper example runs without errors. - */ -final 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#main(String[])} - * throws an exception. - */ - - @Test - void shouldExecuteApplicationWithoutException() { - - assertDoesNotThrow((Executable) App::main); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.datamapper; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +/** Tests that Data-Mapper example runs without errors. */ +final 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#main(String[])} throws an exception. + */ + @Test + void shouldExecuteApplicationWithoutException() { + + assertDoesNotThrow((Executable) App::main); + } +} diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java index ee85252eef43..53782b7eb6eb 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/DataMapperTest.java @@ -36,13 +36,12 @@ * present; they need no SQL interface code, and certainly no knowledge of the database schema. (The * database schema is always ignorant of the objects that use it.) Since it's a form of Mapper , * Data Mapper itself is even unknown to the domain layer. + * *

    */ class DataMapperTest { - /** - * This test verify that first data mapper is able to perform all CRUD operations on Student - */ + /** This test verify that first data mapper is able to perform all CRUD operations on Student */ @Test void testFirstDataMapper() { diff --git a/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java b/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java index 9a9866e3bcfc..237ea7a3cad6 100644 --- a/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java +++ b/data-mapper/src/test/java/com/iluwatar/datamapper/StudentTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests {@link Student}. - */ +/** Tests {@link Student}. */ final class StudentTest { /** diff --git a/data-transfer-object/README.md b/data-transfer-object/README.md index 40d2c1eb07c8..6248676013ae 100644 --- a/data-transfer-object/README.md +++ b/data-transfer-object/README.md @@ -35,6 +35,10 @@ Wikipedia says > In the field of programming a data transfer object (DTO) is an object that carries data between processes. The motivation for its use is that communication between processes is usually done resorting to remote interfaces (e.g. web services), where each call is an expensive operation. Because the majority of the cost of each call is related to the round-trip time between the client and the server, one way of reducing the number of calls is to use an object (the DTO) that aggregates the data that would have been transferred by the several calls, but that is served by one call only. +Sequence diagram + +![Data Transfer Object sequence diagram](./etc/dto-sequence-diagram.png) + ## Programmatic Example of DTO Pattern in Java Let's first introduce our simple `CustomerDTO` record. diff --git a/data-transfer-object/etc/dto-sequence-diagram.png b/data-transfer-object/etc/dto-sequence-diagram.png new file mode 100644 index 000000000000..3fd59695e552 Binary files /dev/null and b/data-transfer-object/etc/dto-sequence-diagram.png differ diff --git a/data-transfer-object/pom.xml b/data-transfer-object/pom.xml index b6404194b41e..5370250f6959 100644 --- a/data-transfer-object/pom.xml +++ b/data-transfer-object/pom.xml @@ -34,6 +34,14 @@ data-transfer-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java index ac96e53c416b..0a6fc9f041d5 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/App.java @@ -38,17 +38,16 @@ * The Data Transfer Object pattern is a design pattern in which an data transfer object is used to * serve related information together to avoid multiple call for each piece of information. * - *

    In the first example, {@link App} is a customer details consumer i.e. client to - * request for customer details to server. {@link CustomerResource} act as server to serve customer - * information. {@link CustomerDto} is data transfer object to share customer information. + *

    In the first example, {@link App} is a customer details consumer i.e. client to request for + * customer details to server. {@link CustomerResource} act as server to serve customer information. + * {@link CustomerDto} is data transfer object to share customer information. * - *

    In the second example, {@link App} is a product details consumer i.e. client to - * request for product details to server. {@link ProductResource} acts as server to serve - * product information. {@link ProductDto} is data transfer object to share product information. + *

    In the second example, {@link App} is a product details consumer i.e. client to request for + * product details to server. {@link ProductResource} acts as server to serve product information. + * {@link ProductDto} is data transfer object to share product information. * *

    The pattern implementation is a bit different in each of the examples. The first can be * thought as a traditional example and the second is an enum based implementation. - * */ @Slf4j public class App { @@ -88,28 +87,32 @@ public static void main(String[] args) { // Example 2: Product DTO - Product tv = Product.builder().id(1L).name("TV").supplier("Sony").price(1000D).cost(1090D).build(); + Product tv = + Product.builder().id(1L).name("TV").supplier("Sony").price(1000D).cost(1090D).build(); Product microwave = Product.builder() .id(2L) .name("microwave") .supplier("Delonghi") .price(1000D) - .cost(1090D).build(); + .cost(1090D) + .build(); Product refrigerator = Product.builder() .id(3L) .name("refrigerator") .supplier("Botsch") .price(1000D) - .cost(1090D).build(); + .cost(1090D) + .build(); Product airConditioner = Product.builder() .id(4L) .name("airConditioner") .supplier("LG") .price(1000D) - .cost(1090D).build(); + .cost(1090D) + .build(); List products = new ArrayList<>(Arrays.asList(tv, microwave, refrigerator, airConditioner)); ProductResource productResource = new ProductResource(products); diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java index 402075e4d927..64e44f64dfe4 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerDto.java @@ -24,9 +24,6 @@ */ package com.iluwatar.datatransfer.customer; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - /** * {@link CustomerDto} is a data transfer object POJO. Instead of sending individual information to * client We can send related information together in POJO. diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java index 0fcae3e91d0a..427c87521af1 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/customer/CustomerResource.java @@ -25,8 +25,6 @@ package com.iluwatar.datatransfer.customer; import java.util.List; -import lombok.Getter; -import lombok.RequiredArgsConstructor; /** * The resource class which serves customer information. This class act as server in the demo. Which diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/Product.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/Product.java index 633c0b82997d..a29fda1589cd 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/Product.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/Product.java @@ -29,9 +29,7 @@ import lombok.Data; import lombok.NoArgsConstructor; -/** - * {@link Product} is a entity class for product entity. This class act as entity in the demo. - */ +/** {@link Product} is a entity class for product entity. This class act as entity in the demo. */ @Data @Builder @NoArgsConstructor @@ -46,11 +44,18 @@ public final class Product { @Override public String toString() { return "Product{" - + "id=" + id - + ", name='" + name + '\'' - + ", price=" + price - + ", cost=" + cost - + ", supplier='" + supplier + '\'' - + '}'; + + "id=" + + id + + ", name='" + + name + + '\'' + + ", price=" + + price + + ", cost=" + + cost + + ", supplier='" + + supplier + + '\'' + + '}'; } } diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductDto.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductDto.java index 9f13398a7b1d..d254e2fc1ce3 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductDto.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductDto.java @@ -25,8 +25,7 @@ package com.iluwatar.datatransfer.product; /** - * {@link ProductDto} is a data transfer object POJO. - * Instead of sending individual information to + * {@link ProductDto} is a data transfer object POJO. Instead of sending individual information to * client We can send related information together in POJO. * *

    Dto will not have any business logic in it. @@ -35,15 +34,13 @@ public enum ProductDto { ; /** - * This is Request class which consist of Create or any other request DTO's - * you might want to use in your API. + * This is Request class which consist of Create or any other request DTO's you might want to use + * in your API. */ public enum Request { ; - /** - * This is Create dto class for requesting create new product. - */ + /** This is Create dto class for requesting create new product. */ public static final class Create implements Name, Price, Cost, Supplier { private String name; private Double price; @@ -93,15 +90,13 @@ public Create setSupplier(String supplier) { } /** - * This is Response class which consist of any response DTO's - * you might want to provide to your clients. + * This is Response class which consist of any response DTO's you might want to provide to your + * clients. */ public enum Response { ; - /** - * This is Public dto class for API response with the lowest data security. - */ + /** This is Public dto class for API response with the lowest data security. */ public static final class Public implements Id, Name, Price { private Long id; private String name; @@ -139,21 +134,11 @@ public Public setPrice(Double price) { @Override public String toString() { - return "Public{" - + "id=" - + id - + ", name='" - + name - + '\'' - + ", price=" - + price - + '}'; + return "Public{" + "id=" + id + ", name='" + name + '\'' + ", price=" + price + '}'; } } - /** - * This is Private dto class for API response with the highest data security. - */ + /** This is Private dto class for API response with the highest data security. */ public static final class Private implements Id, Name, Price, Cost { private Long id; private String name; @@ -203,28 +188,21 @@ public Private setCost(Double cost) { @Override public String toString() { return "Private{" - + - "id=" + + "id=" + id - + - ", name='" + + ", name='" + name + '\'' - + - ", price=" + + ", price=" + price - + - ", cost=" + + ", cost=" + cost - + - '}'; + + '}'; } } } - /** - * Use this interface whenever you want to provide the product Id in your DTO. - */ + /** Use this interface whenever you want to provide the product Id in your DTO. */ private interface Id { /** * Unique identifier of the product. @@ -234,9 +212,7 @@ private interface Id { Long getId(); } - /** - * Use this interface whenever you want to provide the product Name in your DTO. - */ + /** Use this interface whenever you want to provide the product Name in your DTO. */ private interface Name { /** * The name of the product. @@ -246,40 +222,32 @@ private interface Name { String getName(); } - /** - * Use this interface whenever you want to provide the product Price in your DTO. - */ + /** Use this interface whenever you want to provide the product Price in your DTO. */ private interface Price { /** - * The amount we sell a product for. - * This data is not confidential + * The amount we sell a product for. This data is not confidential * * @return : price of the product. */ Double getPrice(); } - /** - * Use this interface whenever you want to provide the product Cost in your DTO. - */ + /** Use this interface whenever you want to provide the product Cost in your DTO. */ private interface Cost { /** - * The amount that it costs us to purchase this product - * For the amount we sell a product for, see the {@link Price Price} parameter. - * This data is confidential + * The amount that it costs us to purchase this product For the amount we sell a product for, + * see the {@link Price Price} parameter. This data is confidential * * @return : cost of the product. */ Double getCost(); } - /** - * Use this interface whenever you want to provide the product Supplier in your DTO. - */ + /** Use this interface whenever you want to provide the product Supplier in your DTO. */ private interface Supplier { /** - * The name of supplier of the product or its manufacturer. - * This data is highly confidential + * The name of supplier of the product or its manufacturer. This data is highly + * confidential * * @return : supplier of the product. */ diff --git a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java index e0345b411511..d0b3fcbd7932 100644 --- a/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java +++ b/data-transfer-object/src/main/java/com/iluwatar/datatransfer/product/ProductResource.java @@ -37,11 +37,14 @@ public record ProductResource(List products) { * @return : all products in list but in the scheme of private dto. */ public List getAllProductsForAdmin() { - return products - .stream() - .map(p -> new ProductDto.Response.Private().setId(p.getId()).setName(p.getName()) - .setCost(p.getCost()) - .setPrice(p.getPrice())) + return products.stream() + .map( + p -> + new ProductDto.Response.Private() + .setId(p.getId()) + .setName(p.getName()) + .setCost(p.getCost()) + .setPrice(p.getPrice())) .toList(); } @@ -51,10 +54,13 @@ public List getAllProductsForAdmin() { * @return : all products in list but in the scheme of public dto. */ public List getAllProductsForCustomer() { - return products - .stream() - .map(p -> new ProductDto.Response.Public().setId(p.getId()).setName(p.getName()) - .setPrice(p.getPrice())) + return products.stream() + .map( + p -> + new ProductDto.Response.Public() + .setId(p.getId()) + .setName(p.getName()) + .setPrice(p.getPrice())) .toList(); } @@ -64,12 +70,13 @@ public List getAllProductsForCustomer() { * @param createProductDto save new product to list. */ public void save(ProductDto.Request.Create createProductDto) { - products.add(Product.builder() - .id((long) (products.size() + 1)) - .name(createProductDto.getName()) - .supplier(createProductDto.getSupplier()) - .price(createProductDto.getPrice()) - .cost(createProductDto.getCost()) - .build()); + products.add( + Product.builder() + .id((long) (products.size() + 1)) + .name(createProductDto.getName()) + .supplier(createProductDto.getSupplier()) + .price(createProductDto.getPrice()) + .cost(createProductDto.getCost()) + .build()); } -} \ No newline at end of file +} diff --git a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java index 49edb5c73888..d0e32976c4f7 100644 --- a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java +++ b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/AppTest.java @@ -24,21 +24,20 @@ */ package com.iluwatar.datatransfer; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.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#main(String[])} - * throws an exception. + *

    Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java index 1a90a15e1201..006fd0b23bf8 100644 --- a/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java +++ b/data-transfer-object/src/test/java/com/iluwatar/datatransfer/customer/CustomerResourceTest.java @@ -31,9 +31,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * tests {@link CustomerResource}. - */ +/** tests {@link CustomerResource}. */ class CustomerResourceTest { @Test diff --git a/decorator/README.md b/decorator/README.md index ddd8a61a0c94..a7b0bb9ad8b9 100644 --- a/decorator/README.md +++ b/decorator/README.md @@ -35,6 +35,10 @@ Wikipedia says > In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified. +Sequence diagram + +![Decorator sequence diagram](./etc/decorator-sequence-diagram.png) + ## Programmatic Example of Decorator Pattern in Java There is an angry troll living in the nearby hills. Usually, it goes bare-handed, but sometimes it has a weapon. To arm the troll it's not necessary to create a new troll but to decorate it dynamically with a suitable weapon. diff --git a/decorator/etc/decorator-sequence-diagram.png b/decorator/etc/decorator-sequence-diagram.png new file mode 100644 index 000000000000..edf3ea5c31ef Binary files /dev/null and b/decorator/etc/decorator-sequence-diagram.png differ diff --git a/decorator/pom.xml b/decorator/pom.xml index 60e8f493e7bc..380e3a6347e1 100644 --- a/decorator/pom.xml +++ b/decorator/pom.xml @@ -34,6 +34,14 @@ decorator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java b/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java index 127fd8d23ea0..10110facdacb 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java +++ b/decorator/src/main/java/com/iluwatar/decorator/ClubbedTroll.java @@ -27,9 +27,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Decorator that adds a club for the troll. - */ +/** Decorator that adds a club for the troll. */ @Slf4j @RequiredArgsConstructor public class ClubbedTroll implements Troll { diff --git a/decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java b/decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java index cc13c95dad43..2c178961190b 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java +++ b/decorator/src/main/java/com/iluwatar/decorator/SimpleTroll.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * SimpleTroll implements {@link Troll} interface directly. - */ +/** SimpleTroll implements {@link Troll} interface directly. */ @Slf4j public class SimpleTroll implements Troll { diff --git a/decorator/src/main/java/com/iluwatar/decorator/Troll.java b/decorator/src/main/java/com/iluwatar/decorator/Troll.java index aba900a9c238..8c2ff391309e 100644 --- a/decorator/src/main/java/com/iluwatar/decorator/Troll.java +++ b/decorator/src/main/java/com/iluwatar/decorator/Troll.java @@ -1,38 +1,35 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.decorator; - -/** - * Interface for trolls. - */ -public interface Troll { - - void attack(); - - int getAttackPower(); - - void fleeBattle(); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.decorator; + +/** Interface for trolls. */ +public interface Troll { + + void attack(); + + int getAttackPower(); + + void fleeBattle(); +} diff --git a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java index 15285dd11bfb..9170574468c0 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/AppTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/AppTest.java @@ -24,23 +24,19 @@ */ package com.iluwatar.decorator; -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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/decorator/src/test/java/com/iluwatar/decorator/ClubbedTrollTest.java b/decorator/src/test/java/com/iluwatar/decorator/ClubbedTrollTest.java index 1dd6cb188dbf..8f06a971236c 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/ClubbedTrollTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/ClubbedTrollTest.java @@ -32,9 +32,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for {@link ClubbedTroll} - */ +/** Tests for {@link ClubbedTroll} */ class ClubbedTrollTest { @Test diff --git a/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java b/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java index 65bde0455f5a..fd14fb9da10e 100644 --- a/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java +++ b/decorator/src/test/java/com/iluwatar/decorator/SimpleTrollTest.java @@ -36,9 +36,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Tests for {@link SimpleTroll} - */ +/** Tests for {@link SimpleTroll} */ class SimpleTrollTest { private InMemoryAppender appender; diff --git a/delegation/README.md b/delegation/README.md index 8c90d8ee8497..d12b92c370af 100644 --- a/delegation/README.md +++ b/delegation/README.md @@ -33,6 +33,10 @@ Wikipedia says > In object-oriented programming, delegation refers to evaluating a member (property or method) of one object (the receiver) in the context of another original object (the sender). Delegation can be done explicitly, by passing the sending object to the receiving object, which can be done in any object-oriented language; or implicitly, by the member lookup rules of the language, which requires language support for the feature. +Sequence diagram + +![Delegation sequence diagram](./etc/delegation-sequence-diagram.png) + ## Programmatic Example of Delegation Pattern in Java Let's consider a printing example. @@ -114,10 +118,6 @@ Canon Printer:hello world Epson Printer:hello world ``` -## Detailed Explanation of Delegation Pattern with Real-World Examples - -![Delegate class diagram](./etc/delegation.png "Delegate") - ## When to Use the Delegation Pattern in Java * When you want to pass responsibility from one class to another without inheritance. diff --git a/delegation/etc/delegation-sequence-diagram.png b/delegation/etc/delegation-sequence-diagram.png new file mode 100644 index 000000000000..2d07f712d4cb Binary files /dev/null and b/delegation/etc/delegation-sequence-diagram.png differ diff --git a/delegation/pom.xml b/delegation/pom.xml index d1f18027eacd..ec9c1c76a1cb 100644 --- a/delegation/pom.xml +++ b/delegation/pom.xml @@ -34,6 +34,14 @@ 4.0.0 delegation + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/App.java b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java index 7986643c69a9..a7c6dff3a0fd 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/App.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/App.java @@ -60,5 +60,4 @@ public static void main(String[] args) { canonPrinterController.print(MESSAGE_TO_PRINT); epsonPrinterController.print(MESSAGE_TO_PRINT); } - } diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java index 1490c1768cf4..7447522569f3 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/CanonPrinter.java @@ -36,12 +36,9 @@ @Slf4j public class CanonPrinter implements Printer { - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void print(String message) { LOGGER.info("Canon Printer : {}", message); } - } diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java index 4fcac417f370..9ede17dedf15 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/EpsonPrinter.java @@ -28,20 +28,17 @@ import lombok.extern.slf4j.Slf4j; /** - * Specialised Implementation of {@link Printer} for an Epson Printer, in this case the message to be - * printed is appended to "Epson Printer : ". + * Specialised Implementation of {@link Printer} for an Epson Printer, in this case the message to + * be printed is appended to "Epson Printer : ". * * @see Printer */ @Slf4j public class EpsonPrinter implements Printer { - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void print(String message) { LOGGER.info("Epson Printer : {}", message); } - } diff --git a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java index 1705a775e7cf..a7f210bcab44 100644 --- a/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java +++ b/delegation/src/main/java/com/iluwatar/delegation/simple/printers/HpPrinter.java @@ -36,12 +36,9 @@ @Slf4j public class HpPrinter implements Printer { - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void print(String message) { LOGGER.info("HP Printer : {}", message); } - } diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java index cd57181c260c..8ad967c9d63f 100644 --- a/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/AppTest.java @@ -24,24 +24,19 @@ */ package com.iluwatar.delegation.simple; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application Test Entry - */ +import org.junit.jupiter.api.Test; + +/** Application Test Entry */ 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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java index 4c0273f68b71..7160eb56a273 100644 --- a/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java +++ b/delegation/src/test/java/com/iluwatar/delegation/simple/DelegateTest.java @@ -39,9 +39,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * Test for Delegation Pattern - */ +/** Test for Delegation Pattern */ class DelegateTest { private InMemoryAppender appender; @@ -82,9 +80,7 @@ void testEpsonPrinter() { assertEquals("Epson Printer : Test Message Printed", appender.getLastMessage()); } - /** - * Logging Appender - */ + /** Logging Appender */ private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); @@ -107,5 +103,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/dependency-injection/README.md b/dependency-injection/README.md index 51b138c20e9e..c9a2848bdfde 100644 --- a/dependency-injection/README.md +++ b/dependency-injection/README.md @@ -35,6 +35,10 @@ Wikipedia says > In software engineering, dependency injection is a technique in which an object receives other objects that it depends on. These other objects are called dependencies. +Sequence diagram + +![Dependency Injection sequence diagram](./etc/dependency-injection-sequence-diagram.png) + ## Programmatic Example of Dependency Injection Pattern in Java The old wizard likes to fill his pipe and smoke tobacco once in a while. However, he doesn't want to depend on a single tobacco brand only but likes to be able to enjoy them all interchangeably. @@ -114,10 +118,6 @@ The program output: 11:54:05.308 [main] INFO com.iluwatar.dependency.injection.Tobacco -- GuiceWizard smoking RivendellTobacco ``` -## Detailed Explanation of Dependency Injection Pattern with Real-World Examples - -![Dependency Injection](./etc/dependency-injection.png "Dependency Injection") - ## When to Use the Dependency Injection Pattern in Java * When aiming to reduce the coupling between classes and increase the modularity of the application. diff --git a/dependency-injection/etc/dependency-injection-sequence-diagram.png b/dependency-injection/etc/dependency-injection-sequence-diagram.png new file mode 100644 index 000000000000..dbd4d817f7a0 Binary files /dev/null and b/dependency-injection/etc/dependency-injection-sequence-diagram.png differ diff --git a/dependency-injection/pom.xml b/dependency-injection/pom.xml index 71d18bd2e251..f0626f2cd3a6 100644 --- a/dependency-injection/pom.xml +++ b/dependency-injection/pom.xml @@ -34,6 +34,14 @@ dependency-injection + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedSorceress.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedSorceress.java index 95f2e08570f6..0ebf875c2120 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedSorceress.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/AdvancedSorceress.java @@ -1,42 +1,42 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.dependency.injection; - -import lombok.Setter; - -/** - * AdvancedSorceress implements inversion of control. It depends on abstraction that can be injected - * through its setter. - */ -@Setter -public class AdvancedSorceress implements Wizard { - - private Tobacco tobacco; - - @Override - public void smoke() { - tobacco.smoke(this); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dependency.injection; + +import lombok.Setter; + +/** + * AdvancedSorceress implements inversion of control. It depends on abstraction that can be injected + * through its setter. + */ +@Setter +public class AdvancedSorceress implements Wizard { + + private Tobacco tobacco; + + @Override + public void smoke() { + tobacco.smoke(this); + } +} diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/App.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/App.java index 1b52a315fc5d..2f8ecf07fcfa 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/App.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/App.java @@ -32,16 +32,15 @@ * - High-level modules should not depend on low-level modules. Both should depend on abstractions. * - Abstractions should not depend on details. Details should depend on abstractions. * - *

    In this example we show you three different wizards. The first one ({@link SimpleWizard}) is - * a naive implementation violating the inversion of control principle. It depends directly on a + *

    In this example we show you three different wizards. The first one ({@link SimpleWizard}) is a + * naive implementation violating the inversion of control principle. It depends directly on a * concrete implementation which cannot be changed. * *

    The second and third wizards({@link AdvancedWizard} and {@link AdvancedSorceress}) are more * flexible. They do not depend on any concrete implementation but abstraction. They utilize * Dependency Injection pattern allowing their {@link Tobacco} dependency to be injected through * constructor ({@link AdvancedWizard}) or setter ({@link AdvancedSorceress}). This way, handling - * the dependency is no longer the wizard's responsibility. It is resolved outside the wizard - * class. + * the dependency is no longer the wizard's responsibility. It is resolved outside the wizard class. * *

    The fourth example takes the pattern a step further. It uses Guice framework for Dependency * Injection. {@link TobaccoModule} binds a concrete implementation to abstraction. Injector is then diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/OldTobyTobacco.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/OldTobyTobacco.java index 6538415de3b7..0bdd5479cdee 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/OldTobyTobacco.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/OldTobyTobacco.java @@ -24,8 +24,5 @@ */ package com.iluwatar.dependency.injection; -/** - * OldTobyTobacco concrete {@link Tobacco} implementation. - */ -public class OldTobyTobacco extends Tobacco { -} +/** OldTobyTobacco concrete {@link Tobacco} implementation. */ +public class OldTobyTobacco extends Tobacco {} diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/RivendellTobacco.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/RivendellTobacco.java index 1f971d3cfdcc..1238a759427e 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/RivendellTobacco.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/RivendellTobacco.java @@ -24,8 +24,5 @@ */ package com.iluwatar.dependency.injection; -/** - * RivendellTobacco concrete {@link Tobacco} implementation. - */ -public class RivendellTobacco extends Tobacco { -} +/** RivendellTobacco concrete {@link Tobacco} implementation. */ +public class RivendellTobacco extends Tobacco {} diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SecondBreakfastTobacco.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SecondBreakfastTobacco.java index 951df91e6bbd..34cf201f4862 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SecondBreakfastTobacco.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/SecondBreakfastTobacco.java @@ -24,8 +24,5 @@ */ package com.iluwatar.dependency.injection; -/** - * SecondBreakfastTobacco concrete {@link Tobacco} implementation. - */ -public class SecondBreakfastTobacco extends Tobacco { -} +/** SecondBreakfastTobacco concrete {@link Tobacco} implementation. */ +public class SecondBreakfastTobacco extends Tobacco {} diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Tobacco.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Tobacco.java index b62fc6f76220..b1aa7e87cebe 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Tobacco.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Tobacco.java @@ -26,14 +26,12 @@ import lombok.extern.slf4j.Slf4j; -/** - * Tobacco abstraction. - */ +/** Tobacco abstraction. */ @Slf4j public abstract class Tobacco { public void smoke(Wizard wizard) { - LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(), - this.getClass().getSimpleName()); + LOGGER.info( + "{} smoking {}", wizard.getClass().getSimpleName(), this.getClass().getSimpleName()); } } diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/TobaccoModule.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/TobaccoModule.java index 4880878f5bd7..0dc6a6d3bb1f 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/TobaccoModule.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/TobaccoModule.java @@ -26,9 +26,7 @@ import com.google.inject.AbstractModule; -/** - * Guice module for binding certain concrete {@link Tobacco} implementation. - */ +/** Guice module for binding certain concrete {@link Tobacco} implementation. */ public class TobaccoModule extends AbstractModule { @Override diff --git a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Wizard.java b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Wizard.java index 55ceead1210d..4ba6417275a9 100644 --- a/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Wizard.java +++ b/dependency-injection/src/main/java/com/iluwatar/dependency/injection/Wizard.java @@ -24,11 +24,8 @@ */ package com.iluwatar.dependency.injection; -/** - * Wizard interface. - */ +/** Wizard interface. */ public interface Wizard { void smoke(); - } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedSorceressTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedSorceressTest.java index c00e980d3aa3..f2cf32db0038 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedSorceressTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedSorceressTest.java @@ -1,81 +1,74 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.dependency.injection; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import com.iluwatar.dependency.injection.utils.InMemoryAppender; -import java.util.List; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - - -/** - * AdvancedSorceressTest - * - */ - -class AdvancedSorceressTest { - - private InMemoryAppender appender; - - @BeforeEach - void setUp() { - appender = new InMemoryAppender(Tobacco.class); - } - - @AfterEach - void tearDown() { - appender.stop(); - } - - /** - * Test if the {@link AdvancedSorceress} smokes whatever instance of {@link Tobacco} is passed to - * her through the setter's parameter - */ - @Test - void testSmokeEveryThing() { - - List tobaccos = List.of( - new OldTobyTobacco(), - new RivendellTobacco(), - new SecondBreakfastTobacco() - ); - - // Verify if the sorceress is smoking the correct tobacco ... - tobaccos.forEach(tobacco -> { - final var advancedSorceress = new AdvancedSorceress(); - advancedSorceress.setTobacco(tobacco); - advancedSorceress.smoke(); - String lastMessage = appender.getLastMessage(); - assertEquals("AdvancedSorceress smoking " + tobacco.getClass().getSimpleName(), lastMessage); - }); - - // ... and nothing else is happening. - assertEquals(tobaccos.size(), appender.getLogSize()); - - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.dependency.injection; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.iluwatar.dependency.injection.utils.InMemoryAppender; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** AdvancedSorceressTest */ +class AdvancedSorceressTest { + + private InMemoryAppender appender; + + @BeforeEach + void setUp() { + appender = new InMemoryAppender(Tobacco.class); + } + + @AfterEach + void tearDown() { + appender.stop(); + } + + /** + * Test if the {@link AdvancedSorceress} smokes whatever instance of {@link Tobacco} is passed to + * her through the setter's parameter + */ + @Test + void testSmokeEveryThing() { + + List tobaccos = + List.of(new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco()); + + // Verify if the sorceress is smoking the correct tobacco ... + tobaccos.forEach( + tobacco -> { + final var advancedSorceress = new AdvancedSorceress(); + advancedSorceress.setTobacco(tobacco); + advancedSorceress.smoke(); + String lastMessage = appender.getLastMessage(); + assertEquals( + "AdvancedSorceress smoking " + tobacco.getClass().getSimpleName(), lastMessage); + }); + + // ... and nothing else is happening. + assertEquals(tobaccos.size(), appender.getLogSize()); + } +} diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java index 6c6b37d1acf2..7a1692db6784 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AdvancedWizardTest.java @@ -32,11 +32,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -/** - * AdvancedWizardTest - * - */ +/** AdvancedWizardTest */ class AdvancedWizardTest { private InMemoryAppender appender; @@ -58,23 +54,19 @@ void tearDown() { @Test void testSmokeEveryThing() { - List tobaccos = List.of( - new OldTobyTobacco(), - new RivendellTobacco(), - new SecondBreakfastTobacco() - ); + List tobaccos = + List.of(new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco()); // Verify if the wizard is smoking the correct tobacco ... - tobaccos.forEach(tobacco -> { - final AdvancedWizard advancedWizard = new AdvancedWizard(tobacco); - advancedWizard.smoke(); - String lastMessage = appender.getLastMessage(); - assertEquals("AdvancedWizard smoking " + tobacco.getClass().getSimpleName(), lastMessage); - }); + tobaccos.forEach( + tobacco -> { + final AdvancedWizard advancedWizard = new AdvancedWizard(tobacco); + advancedWizard.smoke(); + String lastMessage = appender.getLastMessage(); + assertEquals("AdvancedWizard smoking " + tobacco.getClass().getSimpleName(), lastMessage); + }); // ... and nothing else is happening. assertEquals(tobaccos.size(), appender.getLogSize()); - } - } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java index 3d3d4d409305..1894227ec1d0 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/AppTest.java @@ -24,23 +24,19 @@ */ package com.iluwatar.dependency.injection; -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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java index 72bd95046226..400a079d9845 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/GuiceWizardTest.java @@ -34,10 +34,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * GuiceWizardTest - * - */ +/** GuiceWizardTest */ class GuiceWizardTest { private InMemoryAppender appender; @@ -59,19 +56,17 @@ void tearDown() { @Test void testSmokeEveryThingThroughConstructor() { - List tobaccos = List.of( - new OldTobyTobacco(), - new RivendellTobacco(), - new SecondBreakfastTobacco() - ); + List tobaccos = + List.of(new OldTobyTobacco(), new RivendellTobacco(), new SecondBreakfastTobacco()); // Verify if the wizard is smoking the correct tobacco ... - tobaccos.forEach(tobacco -> { - final GuiceWizard guiceWizard = new GuiceWizard(tobacco); - guiceWizard.smoke(); - String lastMessage = appender.getLastMessage(); - assertEquals("GuiceWizard smoking " + tobacco.getClass().getSimpleName(), lastMessage); - }); + tobaccos.forEach( + tobacco -> { + final GuiceWizard guiceWizard = new GuiceWizard(tobacco); + guiceWizard.smoke(); + String lastMessage = appender.getLastMessage(); + assertEquals("GuiceWizard smoking " + tobacco.getClass().getSimpleName(), lastMessage); + }); // ... and nothing else is happening. assertEquals(tobaccos.size(), appender.getLogSize()); @@ -84,30 +79,29 @@ void testSmokeEveryThingThroughConstructor() { @Test void testSmokeEveryThingThroughInjectionFramework() { - List> tobaccos = List.of( - OldTobyTobacco.class, - RivendellTobacco.class, - SecondBreakfastTobacco.class - ); + List> tobaccos = + List.of(OldTobyTobacco.class, RivendellTobacco.class, SecondBreakfastTobacco.class); // Configure the tobacco in the injection framework ... // ... and create a new wizard with it // Verify if the wizard is smoking the correct tobacco ... - tobaccos.forEach(tobaccoClass -> { - final var injector = Guice.createInjector(new AbstractModule() { - @Override - protected void configure() { - bind(Tobacco.class).to(tobaccoClass); - } - }); - final var guiceWizard = injector.getInstance(GuiceWizard.class); - guiceWizard.smoke(); - String lastMessage = appender.getLastMessage(); - assertEquals("GuiceWizard smoking " + tobaccoClass.getSimpleName(), lastMessage); - }); + tobaccos.forEach( + tobaccoClass -> { + final var injector = + Guice.createInjector( + new AbstractModule() { + @Override + protected void configure() { + bind(Tobacco.class).to(tobaccoClass); + } + }); + final var guiceWizard = injector.getInstance(GuiceWizard.class); + guiceWizard.smoke(); + String lastMessage = appender.getLastMessage(); + assertEquals("GuiceWizard smoking " + tobaccoClass.getSimpleName(), lastMessage); + }); // ... and nothing else is happening. assertEquals(tobaccos.size(), appender.getLogSize()); } - } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java index 7e2c8f0471a6..f9354b49b9d7 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/SimpleWizardTest.java @@ -31,10 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * SimpleWizardTest - * - */ +/** SimpleWizardTest */ class SimpleWizardTest { private InMemoryAppender appender; @@ -60,5 +57,4 @@ void testSmoke() { assertEquals("SimpleWizard smoking OldTobyTobacco", appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java index f39877810afb..319216e187c4 100644 --- a/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java +++ b/dependency-injection/src/test/java/com/iluwatar/dependency/injection/utils/InMemoryAppender.java @@ -27,14 +27,11 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; -import org.slf4j.LoggerFactory; import java.util.LinkedList; import java.util.List; +import org.slf4j.LoggerFactory; - -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/dirty-flag/README.md b/dirty-flag/README.md index 6abcedad18b1..bb46e315b3c5 100644 --- a/dirty-flag/README.md +++ b/dirty-flag/README.md @@ -34,6 +34,10 @@ Wikipedia says > A dirty bit or modified bit is a bit that is associated with a block of computer memory and indicates whether the corresponding block of memory has been modified. The dirty bit is set when the processor writes to (modifies) this memory. The bit indicates that its associated block of memory has been modified and has not been saved to storage yet. When a block of memory is to be replaced, its corresponding dirty bit is checked to see if the block needs to be written back to secondary memory before being replaced or if it can simply be removed. Dirty bits are used by the CPU cache and in the page replacement algorithms of an operating system. +Flowchart + +![Dirty Flag flowchart](./etc/dirty-flag-flowchart.png) + ## Programmatic Example of Dirty Flag Pattern in Java The `DataFetcher` class is responsible for fetching data from a file. It has a dirty flag that indicates whether the data in the file has changed since the last fetch. diff --git a/dirty-flag/etc/dirty-flag-flowchart.png b/dirty-flag/etc/dirty-flag-flowchart.png new file mode 100644 index 000000000000..02db0357be1a Binary files /dev/null and b/dirty-flag/etc/dirty-flag-flowchart.png differ diff --git a/dirty-flag/pom.xml b/dirty-flag/pom.xml index 2bbbf285dbc6..d6032c589320 100644 --- a/dirty-flag/pom.xml +++ b/dirty-flag/pom.xml @@ -40,6 +40,14 @@ UTF-8 + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/App.java b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/App.java index 595f1889bc86..ed1ff06bba95 100644 --- a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/App.java +++ b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/App.java @@ -36,16 +36,16 @@ * calculated will need to be calculated again when they’re requested. Once the results are * re-calculated, then the bool value can be cleared. * - *

    There are some points that need to be considered before diving into using this pattern:- - * there are some things you’ll need to consider:- (1) Do you need it? This design pattern works - * well when the results to be calculated are difficult or resource intensive to compute. You want - * to save them. You also don’t want to be calculating them several times in a row when only the - * last one counts. (2) When do you set the dirty flag? Make sure that you set the dirty flag within - * the class itself whenever an important property changes. This property should affect the result - * of the calculated result and by changing the property, that makes the last result invalid. (3) - * When do you clear the dirty flag? It might seem obvious that the dirty flag should be cleared - * whenever the result is calculated with up-to-date information but there are other times when you - * might want to clear the flag. + *

    There are some points that need to be considered before diving into using this pattern:- there + * are some things you’ll need to consider:- (1) Do you need it? This design pattern works well when + * the results to be calculated are difficult or resource intensive to compute. You want to save + * them. You also don’t want to be calculating them several times in a row when only the last one + * counts. (2) When do you set the dirty flag? Make sure that you set the dirty flag within the + * class itself whenever an important property changes. This property should affect the result of + * the calculated result and by changing the property, that makes the last result invalid. (3) When + * do you clear the dirty flag? It might seem obvious that the dirty flag should be cleared whenever + * the result is calculated with up-to-date information but there are other times when you might + * want to clear the flag. * *

    In this example, the {@link DataFetcher} holds the dirty flag. It fetches and * re-fetches from world.txt when needed. {@link World} mainly serves the data to the @@ -54,21 +54,33 @@ @Slf4j public class App { - /** - * Program execution point. - */ + /** Program execution point. */ public void run() { final var executorService = Executors.newSingleThreadScheduledExecutor(); - executorService.scheduleAtFixedRate(new Runnable() { - final World world = new World(); + try { + executorService.scheduleAtFixedRate( + new Runnable() { + final World world = new World(); + + @Override + public void run() { + var countries = world.fetch(); + LOGGER.info("Our world currently has the following countries:-"); + countries.stream().map(country -> "\t" + country).forEach(LOGGER::info); + } + }, + 0, + 15, + TimeUnit.SECONDS); - @Override - public void run() { - var countries = world.fetch(); - LOGGER.info("Our world currently has the following countries:-"); - countries.stream().map(country -> "\t" + country).forEach(LOGGER::info); - } - }, 0, 15, TimeUnit.SECONDS); // Run at every 15 seconds. + // Keep running for 45 seconds before shutdown (for demo purpose) + TimeUnit.SECONDS.sleep(45); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.error("Thread was interrupted", e); + } finally { + executorService.shutdown(); + } } /** diff --git a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/DataFetcher.java b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/DataFetcher.java index 88997b86ee08..0d902b3c058b 100644 --- a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/DataFetcher.java +++ b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/DataFetcher.java @@ -32,10 +32,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -/** - * A mock database manager -- Fetches data from a raw file. - * - */ +/** A mock database manager -- Fetches data from a raw file. */ @Slf4j public class DataFetcher { diff --git a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java index 5c9201a621f7..bc876814b4d6 100644 --- a/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java +++ b/dirty-flag/src/main/java/com/iluwatar/dirtyflag/World.java @@ -27,10 +27,7 @@ import java.util.ArrayList; import java.util.List; -/** - * A middle-layer app that calls/passes along data from the back-end. - * - */ +/** A middle-layer app that calls/passes along data from the back-end. */ public class World { private List countries; diff --git a/dirty-flag/src/test/java/org/dirty/flag/AppTest.java b/dirty-flag/src/test/java/org/dirty/flag/AppTest.java index 8dc52ea445cc..8e09b01928c3 100644 --- a/dirty-flag/src/test/java/org/dirty/flag/AppTest.java +++ b/dirty-flag/src/test/java/org/dirty/flag/AppTest.java @@ -24,24 +24,20 @@ */ package org.dirty.flag; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.iluwatar.dirtyflag.App; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Tests that Dirty-Flag example runs without errors. - */ +/** Tests that Dirty-Flag 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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java b/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java index de0c62903103..7d512deafe7b 100644 --- a/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java +++ b/dirty-flag/src/test/java/org/dirty/flag/DirtyFlagTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class DirtyFlagTest { @Test diff --git a/domain-model/README.md b/domain-model/README.md index 5a914c7efdc3..c6e019e1bdef 100644 --- a/domain-model/README.md +++ b/domain-model/README.md @@ -34,6 +34,10 @@ In plain words > The Domain Model is an object model of the domain that incorporates both behavior and data. +Mind map + +![Domain Model mind map](./etc/domain-model-mind-map.png) + ## Programmatic Example of Domain Model Pattern in Java Let's assume that we need to build an e-commerce web application. While analyzing requirements you will notice that there are few nouns you talk about repeatedly. It’s your Customer, and a Product the customer looks for. These two are your domain-specific classes and each of that classes will include some business logic specific to its domain. @@ -225,10 +229,6 @@ The program output: 12:17:23.846 [main] INFO com.iluwatar.domainmodel.Customer -- Tom bought: Eggs - $10.00, Cheese - $20.00 ``` -## Detailed Explanation of Domain Model Pattern with Real-World Examples - -![Domain Model class diagram](./etc/domain-model.urm.png "Domain Model") - ## When to Use the Domain Model Pattern in Java * Appropriate in complex applications with rich business logic. diff --git a/domain-model/etc/domain-model-mind-map.png b/domain-model/etc/domain-model-mind-map.png new file mode 100644 index 000000000000..38dcdf26d4bc Binary files /dev/null and b/domain-model/etc/domain-model-mind-map.png differ diff --git a/domain-model/pom.xml b/domain-model/pom.xml index 9eb7e23bf3de..933c186790b0 100644 --- a/domain-model/pom.xml +++ b/domain-model/pom.xml @@ -34,6 +34,14 @@ 4.0.0 domain-model + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + com.h2database h2 diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/App.java b/domain-model/src/main/java/com/iluwatar/domainmodel/App.java index aade9924a400..3f209a35a5f8 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/App.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/App.java @@ -32,7 +32,6 @@ import org.h2.jdbcx.JdbcDataSource; import org.joda.money.Money; - /** * Domain Model pattern is a more complex solution for organizing domain logic than Transaction * Script and Table Module. It provides an object-oriented way of dealing with complicated logic. @@ -42,10 +41,10 @@ * is that in Table Module a single class encapsulates all the domain logic for all records stored * in table when in Domain Model every single class represents only one record in underlying table. * - *

    In this example, we will use the Domain Model pattern to implement buying of products - * by customers in a Shop. The main method will create a customer and a few products. - * Customer will do a few purchases, try to buy product which are too expensive for him, - * return product which he bought to return money.

    + *

    In this example, we will use the Domain Model pattern to implement buying of products by + * customers in a Shop. The main method will create a customer and a few products. Customer will do + * a few purchases, try to buy product which are too expensive for him, return product which he + * bought to return money. */ public class App { @@ -80,11 +79,7 @@ public static void main(String[] args) throws Exception { var customerDao = new CustomerDaoImpl(dataSource); var tom = - Customer.builder() - .name("Tom") - .money(Money.of(USD, 30)) - .customerDao(customerDao) - .build(); + Customer.builder().name("Tom").money(Money.of(USD, 30)).customerDao(customerDao).build(); tom.save(); diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/Customer.java b/domain-model/src/main/java/com/iluwatar/domainmodel/Customer.java index e0f646a77442..019a06c8470b 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/Customer.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/Customer.java @@ -36,9 +36,8 @@ import org.joda.money.Money; /** - * This class organizes domain logic of customer. - * A single instance of this class - * contains both the data and behavior of a single customer. + * This class organizes domain logic of customer. A single instance of this class contains both the + * data and behavior of a single customer. */ @Slf4j @Getter @@ -51,9 +50,7 @@ public class Customer { @NonNull private String name; @NonNull private Money money; - /** - * Save customer or update if customer already exist. - */ + /** Save customer or update if customer already exist. */ public void save() { try { Optional customer = customerDao.findByName(name); @@ -117,9 +114,7 @@ public void returnProduct(Product product) { } } - /** - * Print customer's purchases. - */ + /** Print customer's purchases. */ public void showPurchases() { Optional purchasesToShow = purchases.stream() @@ -133,9 +128,7 @@ public void showPurchases() { } } - /** - * Print customer's money balance. - */ + /** Print customer's money balance. */ public void showBalance() { LOGGER.info(name + " balance: " + money); } diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDao.java b/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDao.java index 1375a032373d..18db0ab0bd8d 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDao.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDao.java @@ -27,9 +27,7 @@ import java.sql.SQLException; import java.util.Optional; -/** - * DAO interface for customer transactions. - */ +/** DAO interface for customer transactions. */ public interface CustomerDao { Optional findByName(String name) throws SQLException; diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDaoImpl.java b/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDaoImpl.java index b24da7a49dee..cff4e30bc079 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDaoImpl.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/CustomerDaoImpl.java @@ -32,9 +32,7 @@ import javax.sql.DataSource; import org.joda.money.Money; -/** - * Implementations for database operations of Customer. - */ +/** Implementations for database operations of Customer. */ public class CustomerDaoImpl implements CustomerDao { private final DataSource dataSource; diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/Product.java b/domain-model/src/main/java/com/iluwatar/domainmodel/Product.java index 6d2f37a026eb..9d91779773ae 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/Product.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/Product.java @@ -40,9 +40,8 @@ import org.joda.money.Money; /** - * This class organizes domain logic of product. - * A single instance of this class - * contains both the data and behavior of a single product. + * This class organizes domain logic of product. A single instance of this class contains both the + * data and behavior of a single product. */ @Slf4j @Getter @@ -59,9 +58,7 @@ public class Product { @NonNull private Money price; @NonNull private LocalDate expirationDate; - /** - * Save product or update if product already exist. - */ + /** Save product or update if product already exist. */ public void save() { try { Optional product = productDao.findByName(name); @@ -75,16 +72,14 @@ public void save() { } } - /** - * Calculate sale price of product with discount. - */ + /** Calculate sale price of product with discount. */ public Money getSalePrice() { return price.minus(calculateDiscount()); } private Money calculateDiscount() { if (ChronoUnit.DAYS.between(LocalDate.now(), expirationDate) - < DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE) { + < DAYS_UNTIL_EXPIRATION_WHEN_DISCOUNT_ACTIVE) { return price.multipliedBy(DISCOUNT_RATE, RoundingMode.DOWN); } diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDao.java b/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDao.java index bfedc5098aad..5a2644829565 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDao.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDao.java @@ -27,9 +27,7 @@ import java.sql.SQLException; import java.util.Optional; -/** - * DAO interface for product transactions. - */ +/** DAO interface for product transactions. */ public interface ProductDao { Optional findByName(String name) throws SQLException; diff --git a/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDaoImpl.java b/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDaoImpl.java index 781bc3a80efb..6313e8afa1fb 100644 --- a/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDaoImpl.java +++ b/domain-model/src/main/java/com/iluwatar/domainmodel/ProductDaoImpl.java @@ -33,9 +33,7 @@ import javax.sql.DataSource; import org.joda.money.Money; -/** - * Implementations for database transactions of Product. - */ +/** Implementations for database transactions of Product. */ public class ProductDaoImpl implements ProductDao { private final DataSource dataSource; diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/AppTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/AppTest.java index 06a260e5fae6..f15b33c2d1b3 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/AppTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/AppTest.java @@ -24,10 +24,10 @@ */ package com.iluwatar.domainmodel; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + /** Tests that Domain Model example runs without errors. */ final class AppTest { @@ -35,5 +35,4 @@ final class AppTest { void shouldExecuteApplicationWithoutException() { assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerDaoImplTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerDaoImplTest.java index 9251420ba76a..aeab249ba7c7 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerDaoImplTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerDaoImplTest.java @@ -24,18 +24,18 @@ */ package com.iluwatar.domainmodel; +import static org.joda.money.CurrencyUnit.USD; +import static org.junit.jupiter.api.Assertions.*; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import javax.sql.DataSource; import org.joda.money.CurrencyUnit; import org.joda.money.Money; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.sql.DataSource; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; - -import static org.joda.money.CurrencyUnit.USD; -import static org.junit.jupiter.api.Assertions.*; class CustomerDaoImplTest { @@ -62,7 +62,12 @@ void setUp() throws SQLException { // setup objects customerDao = new CustomerDaoImpl(dataSource); - customer = Customer.builder().name("customer").money(Money.of(CurrencyUnit.USD,100.0)).customerDao(customerDao).build(); + customer = + Customer.builder() + .name("customer") + .money(Money.of(CurrencyUnit.USD, 100.0)) + .customerDao(customerDao) + .build(); product = Product.builder() diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerTest.java index cb55f33038a7..9d5f83a92156 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/CustomerTest.java @@ -24,89 +24,90 @@ */ package com.iluwatar.domainmodel; -import org.joda.money.CurrencyUnit; -import org.joda.money.Money; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.joda.money.CurrencyUnit.USD; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + import java.sql.SQLException; import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.Optional; - -import static org.joda.money.CurrencyUnit.USD; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; class CustomerTest { - private CustomerDao customerDao; - private Customer customer; - private Product product; + private CustomerDao customerDao; + private Customer customer; + private Product product; - @BeforeEach - void setUp() { - customerDao = mock(CustomerDao.class); + @BeforeEach + void setUp() { + customerDao = mock(CustomerDao.class); - customer = Customer.builder() - .name("customer") - .money(Money.of(CurrencyUnit.USD, 100.0)) - .customerDao(customerDao) - .build(); + customer = + Customer.builder() + .name("customer") + .money(Money.of(CurrencyUnit.USD, 100.0)) + .customerDao(customerDao) + .build(); - product = Product.builder() - .name("product") - .price(Money.of(USD, 100.0)) - .expirationDate(LocalDate.now().plusDays(10)) - .productDao(mock(ProductDao.class)) - .build(); - } + product = + Product.builder() + .name("product") + .price(Money.of(USD, 100.0)) + .expirationDate(LocalDate.now().plusDays(10)) + .productDao(mock(ProductDao.class)) + .build(); + } - @Test - void shouldSaveCustomer() throws SQLException { - when(customerDao.findByName("customer")).thenReturn(Optional.empty()); + @Test + void shouldSaveCustomer() throws SQLException { + when(customerDao.findByName("customer")).thenReturn(Optional.empty()); - customer.save(); + customer.save(); - verify(customerDao, times(1)).save(customer); + verify(customerDao, times(1)).save(customer); - when(customerDao.findByName("customer")).thenReturn(Optional.of(customer)); + when(customerDao.findByName("customer")).thenReturn(Optional.of(customer)); - customer.save(); + customer.save(); - verify(customerDao, times(1)).update(customer); - } + verify(customerDao, times(1)).update(customer); + } - @Test - void shouldAddProductToPurchases() { - product.setPrice(Money.of(USD, 200.0)); + @Test + void shouldAddProductToPurchases() { + product.setPrice(Money.of(USD, 200.0)); - customer.buyProduct(product); + customer.buyProduct(product); - assertEquals(customer.getPurchases(), new ArrayList<>()); - assertEquals(customer.getMoney(), Money.of(USD,100)); + assertEquals(customer.getPurchases(), new ArrayList<>()); + assertEquals(customer.getMoney(), Money.of(USD, 100)); - product.setPrice(Money.of(USD, 100.0)); + product.setPrice(Money.of(USD, 100.0)); - customer.buyProduct(product); + customer.buyProduct(product); - assertEquals(new ArrayList<>(Arrays.asList(product)), customer.getPurchases()); - assertEquals(Money.zero(USD), customer.getMoney()); - } + assertEquals(new ArrayList<>(Arrays.asList(product)), customer.getPurchases()); + assertEquals(Money.zero(USD), customer.getMoney()); + } - @Test - void shouldRemoveProductFromPurchases() { - customer.setPurchases(new ArrayList<>(Arrays.asList(product))); + @Test + void shouldRemoveProductFromPurchases() { + customer.setPurchases(new ArrayList<>(Arrays.asList(product))); - customer.returnProduct(product); + customer.returnProduct(product); - assertEquals(new ArrayList<>(), customer.getPurchases()); - assertEquals(Money.of(USD, 200), customer.getMoney()); + assertEquals(new ArrayList<>(), customer.getPurchases()); + assertEquals(Money.of(USD, 200), customer.getMoney()); - customer.returnProduct(product); + customer.returnProduct(product); - assertEquals(new ArrayList<>(), customer.getPurchases()); - assertEquals(Money.of(USD, 200), customer.getMoney()); - } + assertEquals(new ArrayList<>(), customer.getPurchases()); + assertEquals(Money.of(USD, 200), customer.getMoney()); + } } - diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/ProductDaoImplTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/ProductDaoImplTest.java index ae2985afa536..7631c3581ec8 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/ProductDaoImplTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/ProductDaoImplTest.java @@ -24,104 +24,104 @@ */ package com.iluwatar.domainmodel; +import static org.joda.money.CurrencyUnit.USD; +import static org.junit.jupiter.api.Assertions.*; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; +import javax.sql.DataSource; import org.joda.money.Money; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.sql.DataSource; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; - -import static org.joda.money.CurrencyUnit.USD; -import static org.junit.jupiter.api.Assertions.*; class ProductDaoImplTest { - public static final String INSERT_PRODUCT_SQL = - "insert into PRODUCTS values('product', 100, DATE '2021-06-27')"; - public static final String SELECT_PRODUCTS_SQL = - "select name, price, expiration_date from PRODUCTS"; - - private DataSource dataSource; - private ProductDao productDao; - private Product product; - - @BeforeEach - void setUp() throws SQLException { - // create schema - dataSource = TestUtils.createDataSource(); - - TestUtils.deleteSchema(dataSource); - TestUtils.createSchema(dataSource); - - // setup objects - productDao = new ProductDaoImpl(dataSource); - - product = - Product.builder() - .name("product") - .price(Money.of(USD, 100.0)) - .expirationDate(LocalDate.parse("2021-06-27")) - .productDao(productDao) - .build(); - } + public static final String INSERT_PRODUCT_SQL = + "insert into PRODUCTS values('product', 100, DATE '2021-06-27')"; + public static final String SELECT_PRODUCTS_SQL = + "select name, price, expiration_date from PRODUCTS"; - @AfterEach - void tearDown() throws SQLException { - TestUtils.deleteSchema(dataSource); - } + private DataSource dataSource; + private ProductDao productDao; + private Product product; - @Test - void shouldFindProductByName() throws SQLException { - var product = productDao.findByName("product"); + @BeforeEach + void setUp() throws SQLException { + // create schema + dataSource = TestUtils.createDataSource(); - assertTrue(product.isEmpty()); + TestUtils.deleteSchema(dataSource); + TestUtils.createSchema(dataSource); - TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource); + // setup objects + productDao = new ProductDaoImpl(dataSource); - product = productDao.findByName("product"); + product = + Product.builder() + .name("product") + .price(Money.of(USD, 100.0)) + .expirationDate(LocalDate.parse("2021-06-27")) + .productDao(productDao) + .build(); + } - assertTrue(product.isPresent()); - assertEquals("product", product.get().getName()); - assertEquals(Money.of(USD, 100), product.get().getPrice()); - assertEquals(LocalDate.parse("2021-06-27"), product.get().getExpirationDate()); - } + @AfterEach + void tearDown() throws SQLException { + TestUtils.deleteSchema(dataSource); + } - @Test - void shouldSaveProduct() throws SQLException { + @Test + void shouldFindProductByName() throws SQLException { + var product = productDao.findByName("product"); - productDao.save(product); + assertTrue(product.isEmpty()); - try (var connection = dataSource.getConnection(); - var statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) { + TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource); - assertTrue(rs.next()); - assertEquals(product.getName(), rs.getString("name")); - assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price"))); - assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate()); - } + product = productDao.findByName("product"); - assertThrows(SQLException.class, () -> productDao.save(product)); + assertTrue(product.isPresent()); + assertEquals("product", product.get().getName()); + assertEquals(Money.of(USD, 100), product.get().getPrice()); + assertEquals(LocalDate.parse("2021-06-27"), product.get().getExpirationDate()); + } + + @Test + void shouldSaveProduct() throws SQLException { + + productDao.save(product); + + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement(); + ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) { + + assertTrue(rs.next()); + assertEquals(product.getName(), rs.getString("name")); + assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price"))); + assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate()); } - @Test - void shouldUpdateProduct() throws SQLException { - TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource); + assertThrows(SQLException.class, () -> productDao.save(product)); + } + + @Test + void shouldUpdateProduct() throws SQLException { + TestUtils.executeSQL(INSERT_PRODUCT_SQL, dataSource); - product.setPrice(Money.of(USD, 99.0)); + product.setPrice(Money.of(USD, 99.0)); - productDao.update(product); + productDao.update(product); - try (var connection = dataSource.getConnection(); - var statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) { + try (var connection = dataSource.getConnection(); + var statement = connection.createStatement(); + ResultSet rs = statement.executeQuery(SELECT_PRODUCTS_SQL)) { - assertTrue(rs.next()); - assertEquals(product.getName(), rs.getString("name")); - assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price"))); - assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate()); - } + assertTrue(rs.next()); + assertEquals(product.getName(), rs.getString("name")); + assertEquals(product.getPrice(), Money.of(USD, rs.getBigDecimal("price"))); + assertEquals(product.getExpirationDate(), rs.getDate("expiration_date").toLocalDate()); } + } } diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/ProductTest.java b/domain-model/src/test/java/com/iluwatar/domainmodel/ProductTest.java index f3326fe3ba29..f3193ce9b6aa 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/ProductTest.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/ProductTest.java @@ -24,55 +24,56 @@ */ package com.iluwatar.domainmodel; -import org.joda.money.Money; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import java.sql.SQLException; -import java.time.LocalDate; -import java.util.Optional; - import static org.joda.money.CurrencyUnit.USD; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.Optional; +import org.joda.money.Money; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class ProductTest { - private ProductDao productDao; - private Product product; + private ProductDao productDao; + private Product product; - @BeforeEach - void setUp() { - productDao = mock(ProductDaoImpl.class); + @BeforeEach + void setUp() { + productDao = mock(ProductDaoImpl.class); - product = Product.builder() - .name("product") - .price(Money.of(USD, 100.0)) - .expirationDate(LocalDate.now().plusDays(10)) - .productDao(productDao) - .build(); - } + product = + Product.builder() + .name("product") + .price(Money.of(USD, 100.0)) + .expirationDate(LocalDate.now().plusDays(10)) + .productDao(productDao) + .build(); + } - @Test - void shouldSaveProduct() throws SQLException { - when(productDao.findByName("product")).thenReturn(Optional.empty()); + @Test + void shouldSaveProduct() throws SQLException { + when(productDao.findByName("product")).thenReturn(Optional.empty()); - product.save(); + product.save(); - verify(productDao, times(1)).save(product); + verify(productDao, times(1)).save(product); - when(productDao.findByName("product")).thenReturn(Optional.of(product)); + when(productDao.findByName("product")).thenReturn(Optional.of(product)); - product.save(); + product.save(); - verify(productDao, times(1)).update(product); - } + verify(productDao, times(1)).update(product); + } - @Test - void shouldGetSalePriceOfProduct() { - assertEquals(Money.of(USD, 100), product.getSalePrice()); + @Test + void shouldGetSalePriceOfProduct() { + assertEquals(Money.of(USD, 100), product.getSalePrice()); - product.setExpirationDate(LocalDate.now().plusDays(2)); + product.setExpirationDate(LocalDate.now().plusDays(2)); - assertEquals(Money.of(USD, 80), product.getSalePrice()); - } + assertEquals(Money.of(USD, 80), product.getSalePrice()); + } } diff --git a/domain-model/src/test/java/com/iluwatar/domainmodel/TestUtils.java b/domain-model/src/test/java/com/iluwatar/domainmodel/TestUtils.java index 5c788fa8ec49..8555cedc8b1d 100644 --- a/domain-model/src/test/java/com/iluwatar/domainmodel/TestUtils.java +++ b/domain-model/src/test/java/com/iluwatar/domainmodel/TestUtils.java @@ -24,13 +24,13 @@ */ package com.iluwatar.domainmodel; -import org.h2.jdbcx.JdbcDataSource; -import javax.sql.DataSource; import java.sql.SQLException; +import javax.sql.DataSource; +import org.h2.jdbcx.JdbcDataSource; public class TestUtils { - public static void executeSQL( String sql, DataSource dataSource) throws SQLException { + public static void executeSQL(String sql, DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); var statement = connection.createStatement()) { statement.executeUpdate(sql); diff --git a/double-buffer/README.md b/double-buffer/README.md index befa83a9a596..ab4d0b9b5b1a 100644 --- a/double-buffer/README.md +++ b/double-buffer/README.md @@ -34,6 +34,10 @@ Wikipedia says > In computer science, multiple buffering is the use of more than one buffer to hold a block of data, so that a "reader" will see a complete (though perhaps old) version of the data, rather than a partially updated version of the data being created by a "writer". It is very commonly used for computer display images. +Sequence diagram + +![Double Buffer sequence diagram](./etc/double-buffer-sequence-diagram.png) + ## Programmatic Example of Double Buffer Pattern in Java A typical example, and one that every game engine must address, is rendering. When the game draws the world the users see, it does so one piece at a time - the mountains in the distance, the rolling hills, the trees, each in its turn. If the user watched the view draw incrementally like that, the illusion of a coherent world would be shattered. The scene must update smoothly and quickly, displaying a series of complete frames, each appearing instantly. Double buffering solves the problem. diff --git a/double-buffer/etc/double-buffer-sequence-diagram.png b/double-buffer/etc/double-buffer-sequence-diagram.png new file mode 100644 index 000000000000..365dff15a718 Binary files /dev/null and b/double-buffer/etc/double-buffer-sequence-diagram.png differ diff --git a/double-buffer/pom.xml b/double-buffer/pom.xml index 5718eddf1eeb..ad5bf6c3e314 100644 --- a/double-buffer/pom.xml +++ b/double-buffer/pom.xml @@ -34,9 +34,18 @@ 4.0.0 double-buffer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.apache.commons commons-lang3 + 3.17.0 org.junit.jupiter diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java index bbd4ad8d4514..899f714667d3 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/App.java @@ -45,19 +45,13 @@ public class App { */ public static void main(String[] args) { final var scene = new Scene(); - var drawPixels1 = List.of( - new MutablePair<>(1, 1), - new MutablePair<>(5, 6), - new MutablePair<>(3, 2) - ); + var drawPixels1 = + List.of(new MutablePair<>(1, 1), new MutablePair<>(5, 6), new MutablePair<>(3, 2)); scene.draw(drawPixels1); var buffer1 = scene.getBuffer(); printBlackPixelCoordinate(buffer1); - var drawPixels2 = List.of( - new MutablePair<>(3, 7), - new MutablePair<>(6, 1) - ); + var drawPixels2 = List.of(new MutablePair<>(3, 7), new MutablePair<>(6, 1)); scene.draw(drawPixels2); var buffer2 = scene.getBuffer(); printBlackPixelCoordinate(buffer2); diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java index dfa2dbe48e90..191ad0e12225 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Buffer.java @@ -24,9 +24,7 @@ */ package com.iluwatar.doublebuffer; -/** - * Buffer interface. - */ +/** Buffer interface. */ public interface Buffer { /** @@ -45,9 +43,7 @@ public interface Buffer { */ void draw(int x, int y); - /** - * Clear all the pixels. - */ + /** Clear all the pixels. */ void clearAll(); /** @@ -56,5 +52,4 @@ public interface Buffer { * @return pixel list */ Pixel[] getPixels(); - } diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java index ef86549b48e8..e015bb62e8b8 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/FrameBuffer.java @@ -26,9 +26,7 @@ import java.util.Arrays; -/** - * FrameBuffer implementation class. - */ +/** FrameBuffer implementation class. */ public class FrameBuffer implements Buffer { public static final int WIDTH = 10; diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java index c6c2a1637979..eea701e8afc6 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Pixel.java @@ -24,11 +24,8 @@ */ package com.iluwatar.doublebuffer; -/** - * Pixel enum. Each pixel can be white (not drawn) or black (drawn). - */ +/** Pixel enum. Each pixel can be white (not drawn) or black (drawn). */ public enum Pixel { - WHITE, BLACK } diff --git a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java index 692249dd0b99..79a528fabe6b 100644 --- a/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java +++ b/double-buffer/src/main/java/com/iluwatar/doublebuffer/Scene.java @@ -28,9 +28,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.Pair; -/** - * Scene class. Render the output frame. - */ +/** Scene class. Render the output frame. */ @Slf4j public class Scene { @@ -40,9 +38,7 @@ public class Scene { private int next; - /** - * Constructor of Scene. - */ + /** Constructor of Scene. */ public Scene() { frameBuffers = new FrameBuffer[2]; frameBuffers[0] = new FrameBuffer(); @@ -60,11 +56,12 @@ public void draw(List> coordinateList) { LOGGER.info("Start drawing next frame"); LOGGER.info("Current buffer: " + current + " Next buffer: " + next); frameBuffers[next].clearAll(); - coordinateList.forEach(coordinate -> { - var x = coordinate.getKey(); - var y = coordinate.getValue(); - frameBuffers[next].draw(x, y); - }); + coordinateList.forEach( + coordinate -> { + var x = coordinate.getKey(); + var y = coordinate.getValue(); + frameBuffers[next].draw(x, y); + }); LOGGER.info("Swap current and next buffer"); swap(); LOGGER.info("Finish swapping"); @@ -81,5 +78,4 @@ private void swap() { next = current ^ next; current = current ^ next; } - } diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java index 2338fde5928e..4c1d8674d66f 100644 --- a/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/AppTest.java @@ -28,21 +28,17 @@ import org.junit.jupiter.api.Test; -/** - * App unit test. - */ +/** App unit 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#main(String[])} - * throws an exception. + * + *

    Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java index ea842c3c5157..2bd852a35898 100644 --- a/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/FrameBufferTest.java @@ -30,9 +30,7 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * FrameBuffer unit test. - */ +/** FrameBuffer unit test. */ class FrameBufferTest { @Test @@ -91,5 +89,4 @@ void testGetPixels() { fail("Fail to modify field access."); } } - } diff --git a/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java b/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java index 6176bf792579..41806e566ba4 100644 --- a/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java +++ b/double-buffer/src/test/java/com/iluwatar/doublebuffer/SceneTest.java @@ -30,9 +30,7 @@ import java.util.ArrayList; import org.junit.jupiter.api.Test; -/** - * Scene unit tests. - */ +/** Scene unit tests. */ class SceneTest { @Test diff --git a/double-checked-locking/README.md b/double-checked-locking/README.md index df2ddfe980e3..0c4318d11f1c 100644 --- a/double-checked-locking/README.md +++ b/double-checked-locking/README.md @@ -29,6 +29,10 @@ Wikipedia says > In software engineering, double-checked locking (also known as "double-checked locking optimization") is a software design pattern used to reduce the overhead of acquiring a lock by testing the locking criterion (the "lock hint") before acquiring the lock. Locking occurs only if the locking criterion check indicates that locking is required. +Flowchart + +![Double-Checked Locking flowchart](./etc/double-checked-locking-flowchart.png) + ## Programmatic Example of Double-Checked Locking Pattern in Java The Double-Checked Locking pattern is used in the `HolderThreadSafe` class to ensure that the `Heavy` object is only created once, even when accessed from multiple threads. Here's how it works: diff --git a/double-checked-locking/etc/double-checked-locking-flowchart.png b/double-checked-locking/etc/double-checked-locking-flowchart.png new file mode 100644 index 000000000000..89d08529238b Binary files /dev/null and b/double-checked-locking/etc/double-checked-locking-flowchart.png differ diff --git a/double-checked-locking/pom.xml b/double-checked-locking/pom.xml index a339de7c0c4a..650c76700498 100644 --- a/double-checked-locking/pom.xml +++ b/double-checked-locking/pom.xml @@ -33,6 +33,14 @@ double-checked-locking + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java index 8a5198229051..99dd6d05c628 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/App.java @@ -35,10 +35,9 @@ * lock. Only if the locking criterion check indicates that locking is required does the actual * locking logic proceed. * - *

    In {@link Inventory} we store the items with a given size. However, we do not store more - * items than the inventory size. To address concurrent access problems we use double checked - * locking to add item to inventory. In this method, the thread which gets the lock first adds the - * item. + *

    In {@link Inventory} we store the items with a given size. However, we do not store more items + * than the inventory size. To address concurrent access problems we use double checked locking to + * add item to inventory. In this method, the thread which gets the lock first adds the item. */ @Slf4j public class App { @@ -51,11 +50,15 @@ public class App { public static void main(String[] args) { final var inventory = new Inventory(1000); var executorService = Executors.newFixedThreadPool(3); - IntStream.range(0, 3).mapToObj(i -> () -> { - while (inventory.addItem(new Item())) { - LOGGER.info("Adding another item"); - } - }).forEach(executorService::execute); + IntStream.range(0, 3) + .mapToObj( + i -> + () -> { + while (inventory.addItem(new Item())) { + LOGGER.info("Adding another item"); + } + }) + .forEach(executorService::execute); executorService.shutdown(); try { diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java index 83f72fca29aa..84b76a5769da 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Inventory.java @@ -30,9 +30,7 @@ import java.util.concurrent.locks.ReentrantLock; import lombok.extern.slf4j.Slf4j; -/** - * Inventory. - */ +/** Inventory. */ @Slf4j public class Inventory { @@ -40,18 +38,14 @@ public class Inventory { private final List items; private final Lock lock; - /** - * Constructor. - */ + /** Constructor. */ public Inventory(int inventorySize) { this.inventorySize = inventorySize; this.items = new ArrayList<>(inventorySize); this.lock = new ReentrantLock(); } - /** - * Add item. - */ + /** Add item. */ public boolean addItem(Item item) { if (items.size() < inventorySize) { lock.lock(); @@ -77,5 +71,4 @@ public boolean addItem(Item item) { public final List getItems() { return List.copyOf(items); } - } diff --git a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java index 1e25d77c425a..8f16ea69f3af 100644 --- a/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java +++ b/double-checked-locking/src/main/java/com/iluwatar/doublechecked/locking/Item.java @@ -24,8 +24,5 @@ */ package com.iluwatar.doublechecked.locking; -/** - * Item. - */ -public class Item { -} +/** Item. */ +public class Item {} diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java index 33a66d601a7f..9d9ae3496f7c 100644 --- a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/AppTest.java @@ -24,24 +24,19 @@ */ package com.iluwatar.doublechecked.locking; -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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java index 9067bf8e5e7d..d79f6b660a00 100644 --- a/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java +++ b/double-checked-locking/src/test/java/com/iluwatar/doublechecked/locking/InventoryTest.java @@ -43,10 +43,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * InventoryTest - * - */ +/** InventoryTest */ class InventoryTest { private InMemoryAppender appender; @@ -67,9 +64,7 @@ void tearDown() { */ private static final int THREAD_COUNT = 8; - /** - * The maximum number of {@link Item}s allowed in the {@link Inventory} - */ + /** The maximum number of {@link Item}s allowed in the {@link Inventory} */ private static final int INVENTORY_SIZE = 1000; /** @@ -80,34 +75,42 @@ void tearDown() { */ @Test void testAddItem() { - assertTimeout(ofMillis(10000), () -> { - // Create a new inventory with a limit of 1000 items and put some load on the add method - final var inventory = new Inventory(INVENTORY_SIZE); - final var executorService = Executors.newFixedThreadPool(THREAD_COUNT); - IntStream.range(0, THREAD_COUNT).mapToObj(i -> () -> { - while (inventory.addItem(new Item())) ; - }).forEach(executorService::execute); - - // Wait until all threads have finished - executorService.shutdown(); - executorService.awaitTermination(5, TimeUnit.SECONDS); - - // Check the number of items in the inventory. It should not have exceeded the allowed maximum - final var items = inventory.getItems(); - assertNotNull(items); - assertEquals(INVENTORY_SIZE, items.size()); - - assertEquals(INVENTORY_SIZE, appender.getLogSize()); - - // ... and check if the inventory size is increasing continuously - IntStream.range(0, items.size()) - .mapToObj(i -> appender.log.get(i).getFormattedMessage() - .contains("items.size()=" + (i + 1))) - .forEach(Assertions::assertTrue); - }); + assertTimeout( + ofMillis(10000), + () -> { + // Create a new inventory with a limit of 1000 items and put some load on the add method + final var inventory = new Inventory(INVENTORY_SIZE); + final var executorService = Executors.newFixedThreadPool(THREAD_COUNT); + IntStream.range(0, THREAD_COUNT) + .mapToObj( + i -> + () -> { + while (inventory.addItem(new Item())) + ; + }) + .forEach(executorService::execute); + + // Wait until all threads have finished + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + + // Check the number of items in the inventory. It should not have exceeded the allowed + // maximum + final var items = inventory.getItems(); + assertNotNull(items); + assertEquals(INVENTORY_SIZE, items.size()); + + assertEquals(INVENTORY_SIZE, appender.getLogSize()); + + // ... and check if the inventory size is increasing continuously + IntStream.range(0, items.size()) + .mapToObj( + i -> + appender.log.get(i).getFormattedMessage().contains("items.size()=" + (i + 1))) + .forEach(Assertions::assertTrue); + }); } - private static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); @@ -125,5 +128,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/double-dispatch/README.md b/double-dispatch/README.md index 0022467191ee..05c0cdea4581 100644 --- a/double-dispatch/README.md +++ b/double-dispatch/README.md @@ -33,6 +33,10 @@ Wikipedia says > In software engineering, double dispatch is a special form of multiple dispatch, and a mechanism that dispatches a function call to different concrete functions depending on the runtime types of two objects involved in the call. In most object-oriented systems, the concrete function that is called from a function call in the code depends on the dynamic type of a single object and therefore they are known as single dispatch calls, or simply virtual function calls. +Sequence diagram + +![Double Dispatch sequence diagram](./etc/double-dispatch-sequence-diagram.png) + ## Programmatic Example of Double Dispatch Pattern in Java The Double Dispatch pattern in Java is used to handle collisions between different types of game objects. Each game object is an instance of a class that extends the `GameObject` abstract class. The `GameObject` class has a `collision(GameObject)` method, which is overridden in each subclass to define the behavior when a collision occurs with another game object. Here is a simplified version of the `GameObject` class and its subclasses: @@ -113,10 +117,6 @@ Here is the program output: 15:47:23.773 [main] INFO com.iluwatar.doubledispatch.App -- SpaceStationIss at [12,12,14,14] damaged=true onFire=false ``` -## Detailed Explanation of Double Dispatch Pattern with Real-World Examples - -![Double Dispatch](./etc/double-dispatch.png "Double Dispatch") - ## When to Use the Double Dispatch Pattern in Java * When the behavior of a method needs to vary not just based on the object it is called on, but also based on the type of the argument. diff --git a/double-dispatch/etc/double-dispatch-sequence-diagram.png b/double-dispatch/etc/double-dispatch-sequence-diagram.png new file mode 100644 index 000000000000..71302caf6d04 Binary files /dev/null and b/double-dispatch/etc/double-dispatch-sequence-diagram.png differ diff --git a/double-dispatch/pom.xml b/double-dispatch/pom.xml index 297e93b9dd70..035732318ee0 100644 --- a/double-dispatch/pom.xml +++ b/double-dispatch/pom.xml @@ -34,6 +34,14 @@ double-dispatch + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java index 6fa4387609df..bc38d0ee689d 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/App.java @@ -37,9 +37,9 @@ * to change the method's implementation and add a new instanceof-check. This violates the single * responsibility principle - a class should have only one reason to change. * - *

    Instead of the instanceof-checks a better way is to make another virtual call on the - * parameter object. This way new functionality can be easily added without the need to modify - * existing implementation (open-closed principle). + *

    Instead of the instanceof-checks a better way is to make another virtual call on the parameter + * object. This way new functionality can be easily added without the need to modify existing + * implementation (open-closed principle). * *

    In this example we have hierarchy of objects ({@link GameObject}) that can collide to each * other. Each object has its own coordinates which are checked against the other objects' @@ -57,21 +57,24 @@ public class App { public static void main(String[] args) { // initialize game objects and print their status LOGGER.info("Init objects and print their status"); - var objects = List.of( - new FlamingAsteroid(0, 0, 5, 5), - new SpaceStationMir(1, 1, 2, 2), - new Meteoroid(10, 10, 15, 15), - new SpaceStationIss(12, 12, 14, 14) - ); + var objects = + List.of( + new FlamingAsteroid(0, 0, 5, 5), + new SpaceStationMir(1, 1, 2, 2), + new Meteoroid(10, 10, 15, 15), + new SpaceStationIss(12, 12, 14, 14)); objects.forEach(o -> LOGGER.info(o.toString())); // collision check LOGGER.info("Collision check"); - objects.forEach(o1 -> objects.forEach(o2 -> { - if (o1 != o2 && o1.intersectsWith(o2)) { - o1.collision(o2); - } - })); + objects.forEach( + o1 -> + objects.forEach( + o2 -> { + if (o1 != o2 && o1.intersectsWith(o2)) { + o1.collision(o2); + } + })); // output eventual object statuses LOGGER.info("Print object status after collision checks"); diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/FlamingAsteroid.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/FlamingAsteroid.java index 9789fc3bb567..98b4c6c7a932 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/FlamingAsteroid.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/FlamingAsteroid.java @@ -24,9 +24,7 @@ */ package com.iluwatar.doubledispatch; -/** - * Flaming asteroid game object. - */ +/** Flaming asteroid game object. */ public class FlamingAsteroid extends Meteoroid { public FlamingAsteroid(int left, int top, int right, int bottom) { diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/GameObject.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/GameObject.java index cd9a8b9eff53..85ef3aa85241 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/GameObject.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/GameObject.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.Setter; -/** - * Game objects have coordinates and some other status information. - */ +/** Game objects have coordinates and some other status information. */ @Getter @Setter public abstract class GameObject extends Rectangle { @@ -43,8 +41,9 @@ public GameObject(int left, int top, int right, int bottom) { @Override public String toString() { - return String.format("%s at %s damaged=%b onFire=%b", this.getClass().getSimpleName(), - super.toString(), isDamaged(), isOnFire()); + return String.format( + "%s at %s damaged=%b onFire=%b", + this.getClass().getSimpleName(), super.toString(), isDamaged(), isOnFire()); } public abstract void collision(GameObject gameObject); diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Meteoroid.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Meteoroid.java index 7d9d950a81c4..f2e0587590cb 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Meteoroid.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Meteoroid.java @@ -27,9 +27,7 @@ import com.iluwatar.doubledispatch.constants.AppConstants; import lombok.extern.slf4j.Slf4j; -/** - * Meteoroid game object. - */ +/** Meteoroid game object. */ @Slf4j public class Meteoroid extends GameObject { @@ -44,14 +42,14 @@ public void collision(GameObject gameObject) { @Override public void collisionResolve(FlamingAsteroid asteroid) { - LOGGER.info(AppConstants.HITS, asteroid.getClass().getSimpleName(), this.getClass() - .getSimpleName()); + LOGGER.info( + AppConstants.HITS, asteroid.getClass().getSimpleName(), this.getClass().getSimpleName()); } @Override public void collisionResolve(Meteoroid meteoroid) { - LOGGER.info(AppConstants.HITS, meteoroid.getClass().getSimpleName(), this.getClass() - .getSimpleName()); + LOGGER.info( + AppConstants.HITS, meteoroid.getClass().getSimpleName(), this.getClass().getSimpleName()); } @Override diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java index 7f5e88994d36..4dfe8f22a7f1 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/Rectangle.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Rectangle has coordinates and can be checked for overlap against other Rectangles. - */ +/** Rectangle has coordinates and can be checked for overlap against other Rectangles. */ @Getter @RequiredArgsConstructor public class Rectangle { @@ -40,8 +38,10 @@ public class Rectangle { private final int bottom; boolean intersectsWith(Rectangle r) { - return !(r.getLeft() > getRight() || r.getRight() < getLeft() || r.getTop() > getBottom() || r - .getBottom() < getTop()); + return !(r.getLeft() > getRight() + || r.getRight() < getLeft() + || r.getTop() > getBottom() + || r.getBottom() < getTop()); } @Override diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationIss.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationIss.java index 0ab506092d71..14c44c59602a 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationIss.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationIss.java @@ -24,9 +24,7 @@ */ package com.iluwatar.doubledispatch; -/** - * Space station ISS game object. - */ +/** Space station ISS game object. */ public class SpaceStationIss extends SpaceStationMir { public SpaceStationIss(int left, int top, int right, int bottom) { diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java index 3f718c8d97b4..045698256a2e 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/SpaceStationMir.java @@ -27,9 +27,7 @@ import com.iluwatar.doubledispatch.constants.AppConstants; import lombok.extern.slf4j.Slf4j; -/** - * Space station Mir game object. - */ +/** Space station Mir game object. */ @Slf4j public class SpaceStationMir extends GameObject { @@ -44,10 +42,12 @@ public void collision(GameObject gameObject) { @Override public void collisionResolve(FlamingAsteroid asteroid) { - LOGGER.info(AppConstants.HITS + " {} is damaged! {} is set on fire!", asteroid.getClass() - .getSimpleName(), - this.getClass().getSimpleName(), this.getClass().getSimpleName(), this.getClass() - .getSimpleName()); + LOGGER.info( + AppConstants.HITS + " {} is damaged! {} is set on fire!", + asteroid.getClass().getSimpleName(), + this.getClass().getSimpleName(), + this.getClass().getSimpleName(), + this.getClass().getSimpleName()); setDamaged(true); setOnFire(true); } @@ -71,7 +71,11 @@ public void collisionResolve(SpaceStationIss iss) { } private void logHits(GameObject gameObject) { - LOGGER.info(AppConstants.HITS, " {} is damaged!", gameObject.getClass().getSimpleName(), - this.getClass().getSimpleName(), this.getClass().getSimpleName()); + LOGGER.info( + AppConstants.HITS, + " {} is damaged!", + gameObject.getClass().getSimpleName(), + this.getClass().getSimpleName(), + this.getClass().getSimpleName()); } -} \ No newline at end of file +} diff --git a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/constants/AppConstants.java b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/constants/AppConstants.java index 0f4a51da1b56..f664092fa944 100644 --- a/double-dispatch/src/main/java/com/iluwatar/doubledispatch/constants/AppConstants.java +++ b/double-dispatch/src/main/java/com/iluwatar/doubledispatch/constants/AppConstants.java @@ -24,9 +24,7 @@ */ package com.iluwatar.doubledispatch.constants; -/** - * Constants class to define all constants. - */ +/** Constants class to define all constants. */ public class AppConstants { public static final String HITS = "{} hits {}."; diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java index 24b695244b5d..1abff6a45c73 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/AppTest.java @@ -24,24 +24,19 @@ */ package com.iluwatar.doubledispatch; -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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java index 709b8517a3ad..7d11fa8502fc 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/CollisionTest.java @@ -46,14 +46,18 @@ public abstract class CollisionTest { * Collide the tested item with the other given item and verify if the damage and fire state is as * expected * - * @param other The other object we have to collide with + * @param other The other object we have to collide with * @param otherDamaged Indicates if the other object should be damaged after the collision - * @param otherOnFire Indicates if the other object should be burning after the collision - * @param thisDamaged Indicates if the test object should be damaged after the collision - * @param thisOnFire Indicates if the other object should be burning after the collision + * @param otherOnFire Indicates if the other object should be burning after the collision + * @param thisDamaged Indicates if the test object should be damaged after the collision + * @param thisOnFire Indicates if the other object should be burning after the collision */ - void testCollision(final GameObject other, final boolean otherDamaged, final boolean otherOnFire, - final boolean thisDamaged, final boolean thisOnFire) { + void testCollision( + final GameObject other, + final boolean otherDamaged, + final boolean otherOnFire, + final boolean thisDamaged, + final boolean thisOnFire) { Objects.requireNonNull(other); Objects.requireNonNull(getTestedObject()); @@ -67,24 +71,33 @@ void testCollision(final GameObject other, final boolean otherDamaged, final boo testOnFire(tested, other, thisOnFire); testDamaged(tested, other, thisDamaged); - } /** * Test if the fire state of the target matches the expected state after colliding with the given * object * - * @param target The target object - * @param other The other object + * @param target The target object + * @param other The other object * @param expectTargetOnFire The expected state of fire on the target object */ - private void testOnFire(final GameObject target, final GameObject other, final boolean expectTargetOnFire) { + private void testOnFire( + final GameObject target, final GameObject other, final boolean expectTargetOnFire) { final var targetName = target.getClass().getSimpleName(); final var otherName = other.getClass().getSimpleName(); - final var errorMessage = expectTargetOnFire - ? "Expected [" + targetName + "] to be on fire after colliding with [" + otherName + "] but it was not!" - : "Expected [" + targetName + "] not to be on fire after colliding with [" + otherName + "] but it was!"; + final var errorMessage = + expectTargetOnFire + ? "Expected [" + + targetName + + "] to be on fire after colliding with [" + + otherName + + "] but it was not!" + : "Expected [" + + targetName + + "] not to be on fire after colliding with [" + + otherName + + "] but it was!"; assertEquals(expectTargetOnFire, target.isOnFire(), errorMessage); } @@ -93,19 +106,28 @@ private void testOnFire(final GameObject target, final GameObject other, final b * Test if the damage state of the target matches the expected state after colliding with the * given object * - * @param target The target object - * @param other The other object + * @param target The target object + * @param other The other object * @param expectedDamage The expected state of damage on the target object */ - private void testDamaged(final GameObject target, final GameObject other, final boolean expectedDamage) { + private void testDamaged( + final GameObject target, final GameObject other, final boolean expectedDamage) { final var targetName = target.getClass().getSimpleName(); final var otherName = other.getClass().getSimpleName(); - final var errorMessage = expectedDamage - ? "Expected [" + targetName + "] to be damaged after colliding with [" + otherName + "] but it was not!" - : "Expected [" + targetName + "] not to be damaged after colliding with [" + otherName + "] but it was!"; + final var errorMessage = + expectedDamage + ? "Expected [" + + targetName + + "] to be damaged after colliding with [" + + otherName + + "] but it was not!" + : "Expected [" + + targetName + + "] not to be damaged after colliding with [" + + otherName + + "] but it was!"; assertEquals(expectedDamage, target.isDamaged(), errorMessage); } - } diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java index 78401b86a034..65cd7cbd16b9 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/FlamingAsteroidTest.java @@ -30,10 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * FlamingAsteroidTest - * - */ +/** FlamingAsteroidTest */ class FlamingAsteroidTest extends CollisionTest { @Override @@ -41,9 +38,7 @@ final FlamingAsteroid getTestedObject() { return new FlamingAsteroid(1, 2, 3, 4); } - /** - * Test the constructor parameters - */ + /** Test the constructor parameters */ @Test void testConstructor() { final var asteroid = new FlamingAsteroid(1, 2, 3, 4); @@ -56,52 +51,27 @@ void testConstructor() { assertEquals("FlamingAsteroid at [1,2,3,4] damaged=false onFire=true", asteroid.toString()); } - /** - * Test what happens we collide with an asteroid - */ + /** Test what happens we collide with an asteroid */ @Test void testCollideFlamingAsteroid() { - testCollision( - new FlamingAsteroid(1, 2, 3, 4), - false, true, - false, true - ); + testCollision(new FlamingAsteroid(1, 2, 3, 4), false, true, false, true); } - /** - * Test what happens we collide with an meteoroid - */ + /** Test what happens we collide with an meteoroid */ @Test void testCollideMeteoroid() { - testCollision( - new Meteoroid(1, 1, 3, 4), - false, false, - false, true - ); + testCollision(new Meteoroid(1, 1, 3, 4), false, false, false, true); } - /** - * Test what happens we collide with ISS - */ + /** Test what happens we collide with ISS */ @Test void testCollideSpaceStationIss() { - testCollision( - new SpaceStationIss(1, 1, 3, 4), - true, true, - false, true - ); + testCollision(new SpaceStationIss(1, 1, 3, 4), true, true, false, true); } - /** - * Test what happens we collide with MIR - */ + /** Test what happens we collide with MIR */ @Test void testCollideSpaceStationMir() { - testCollision( - new SpaceStationMir(1, 1, 3, 4), - true, true, - false, true - ); + testCollision(new SpaceStationMir(1, 1, 3, 4), true, true, false, true); } - -} \ No newline at end of file +} diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java index aa02f1940bc8..17d529523dfa 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/MeteoroidTest.java @@ -29,10 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * MeteoroidTest - * - */ +/** MeteoroidTest */ class MeteoroidTest extends CollisionTest { @Override @@ -40,9 +37,7 @@ final Meteoroid getTestedObject() { return new Meteoroid(1, 2, 3, 4); } - /** - * Test the constructor parameters - */ + /** Test the constructor parameters */ @Test void testConstructor() { final var meteoroid = new Meteoroid(1, 2, 3, 4); @@ -55,52 +50,27 @@ void testConstructor() { assertEquals("Meteoroid at [1,2,3,4] damaged=false onFire=false", meteoroid.toString()); } - /** - * Test what happens we collide with an asteroid - */ + /** Test what happens we collide with an asteroid */ @Test void testCollideFlamingAsteroid() { - testCollision( - new FlamingAsteroid(1, 1, 3, 4), - false, true, - false, false - ); + testCollision(new FlamingAsteroid(1, 1, 3, 4), false, true, false, false); } - /** - * Test what happens we collide with an meteoroid - */ + /** Test what happens we collide with an meteoroid */ @Test void testCollideMeteoroid() { - testCollision( - new Meteoroid(1, 1, 3, 4), - false, false, - false, false - ); + testCollision(new Meteoroid(1, 1, 3, 4), false, false, false, false); } - /** - * Test what happens we collide with ISS - */ + /** Test what happens we collide with ISS */ @Test void testCollideSpaceStationIss() { - testCollision( - new SpaceStationIss(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationIss(1, 1, 3, 4), true, false, false, false); } - /** - * Test what happens we collide with MIR - */ + /** Test what happens we collide with MIR */ @Test void testCollideSpaceStationMir() { - testCollision( - new SpaceStationMir(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationMir(1, 1, 3, 4), true, false, false, false); } - -} \ No newline at end of file +} diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java index 8a042f09dea6..6b143643a805 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/RectangleTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * Unit test for Rectangle - */ +/** Unit test for Rectangle */ class RectangleTest { /** @@ -48,8 +46,7 @@ void testConstructor() { } /** - * Test if the values passed through the constructor matches the values in the {@link - * #toString()} + * Test if the values passed through the constructor matches the values in the {@link #toString()} */ @Test void testToString() { @@ -57,9 +54,7 @@ void testToString() { assertEquals("[1,2,3,4]", rectangle.toString()); } - /** - * Test if the {@link Rectangle} class can detect if it intersects with another rectangle. - */ + /** Test if the {@link Rectangle} class can detect if it intersects with another rectangle. */ @Test void testIntersection() { assertTrue(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(0, 0, 1, 1))); @@ -67,5 +62,4 @@ void testIntersection() { assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(2, 2, 3, 3))); assertFalse(new Rectangle(0, 0, 1, 1).intersectsWith(new Rectangle(-2, -2, -1, -1))); } - } diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java index b4587568684a..593caf3d64fb 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationIssTest.java @@ -29,10 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * SpaceStationIssTest - * - */ +/** SpaceStationIssTest */ class SpaceStationIssTest extends CollisionTest { @Override @@ -40,9 +37,7 @@ final SpaceStationIss getTestedObject() { return new SpaceStationIss(1, 2, 3, 4); } - /** - * Test the constructor parameters - */ + /** Test the constructor parameters */ @Test void testConstructor() { final var iss = new SpaceStationIss(1, 2, 3, 4); @@ -55,52 +50,27 @@ void testConstructor() { assertEquals("SpaceStationIss at [1,2,3,4] damaged=false onFire=false", iss.toString()); } - /** - * Test what happens we collide with an asteroid - */ + /** Test what happens we collide with an asteroid */ @Test void testCollideFlamingAsteroid() { - testCollision( - new FlamingAsteroid(1, 1, 3, 4), - false, true, - false, false - ); + testCollision(new FlamingAsteroid(1, 1, 3, 4), false, true, false, false); } - /** - * Test what happens we collide with an meteoroid - */ + /** Test what happens we collide with an meteoroid */ @Test void testCollideMeteoroid() { - testCollision( - new Meteoroid(1, 1, 3, 4), - false, false, - false, false - ); + testCollision(new Meteoroid(1, 1, 3, 4), false, false, false, false); } - /** - * Test what happens we collide with ISS - */ + /** Test what happens we collide with ISS */ @Test void testCollideSpaceStationIss() { - testCollision( - new SpaceStationIss(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationIss(1, 1, 3, 4), true, false, false, false); } - /** - * Test what happens we collide with MIR - */ + /** Test what happens we collide with MIR */ @Test void testCollideSpaceStationMir() { - testCollision( - new SpaceStationMir(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationMir(1, 1, 3, 4), true, false, false, false); } - -} \ No newline at end of file +} diff --git a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java index 540d148f1761..155595253ebc 100644 --- a/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java +++ b/double-dispatch/src/test/java/com/iluwatar/doubledispatch/SpaceStationMirTest.java @@ -29,10 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * SpaceStationMirTest - * - */ +/** SpaceStationMirTest */ class SpaceStationMirTest extends CollisionTest { @Override @@ -40,9 +37,7 @@ final SpaceStationMir getTestedObject() { return new SpaceStationMir(1, 2, 3, 4); } - /** - * Test the constructor parameters - */ + /** Test the constructor parameters */ @Test void testConstructor() { final var mir = new SpaceStationMir(1, 2, 3, 4); @@ -55,52 +50,27 @@ void testConstructor() { assertEquals("SpaceStationMir at [1,2,3,4] damaged=false onFire=false", mir.toString()); } - /** - * Test what happens we collide with an asteroid - */ + /** Test what happens we collide with an asteroid */ @Test void testCollideFlamingAsteroid() { - testCollision( - new FlamingAsteroid(1, 1, 3, 4), - false, true, - false, false - ); + testCollision(new FlamingAsteroid(1, 1, 3, 4), false, true, false, false); } - /** - * Test what happens we collide with an meteoroid - */ + /** Test what happens we collide with an meteoroid */ @Test void testCollideMeteoroid() { - testCollision( - new Meteoroid(1, 1, 3, 4), - false, false, - false, false - ); + testCollision(new Meteoroid(1, 1, 3, 4), false, false, false, false); } - /** - * Test what happens we collide with ISS - */ + /** Test what happens we collide with ISS */ @Test void testCollideSpaceStationIss() { - testCollision( - new SpaceStationIss(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationIss(1, 1, 3, 4), true, false, false, false); } - /** - * Test what happens we collide with MIR - */ + /** Test what happens we collide with MIR */ @Test void testCollideSpaceStationMir() { - testCollision( - new SpaceStationMir(1, 1, 3, 4), - true, false, - false, false - ); + testCollision(new SpaceStationMir(1, 1, 3, 4), true, false, false, false); } - -} \ No newline at end of file +} diff --git a/dynamic-proxy/README.md b/dynamic-proxy/README.md index 9dedbedd0ce5..ec0821260b1c 100644 --- a/dynamic-proxy/README.md +++ b/dynamic-proxy/README.md @@ -35,6 +35,10 @@ Wikipedia says > A dynamic proxy class is a class that implements a list of interfaces specified at runtime such that a method invocation through one of the interfaces on an instance of the class will be encoded and dispatched to another object through a uniform interface. Thus, a dynamic proxy class can be used to create a type-safe proxy object for a list of interfaces without requiring pre-generation of the proxy class, such as with compile-time tools. Method invocations on an instance of a dynamic proxy class are dispatched to a single method in the instance's invocation handler, and they are encoded with a _java.lang.reflect.Method_ object identifying the method that was invoked and an array of type _Object_ containing the arguments. +Sequence diagram + +![Dynamic Proxy sequence diagram](./etc/dynamic-proxy-sequence-diagram.png) + ## Programmatic Example of Dynamic Proxy Pattern in Java This example demonstrates using the Dynamic Proxy pattern in Java to hit the public fake API [JSONPlaceholder](https://jsonplaceholder.typicode.com) for the resource `Album` through an interface. diff --git a/dynamic-proxy/etc/dynamic-proxy-sequence-diagram.png b/dynamic-proxy/etc/dynamic-proxy-sequence-diagram.png new file mode 100644 index 000000000000..cdffa1b36d62 Binary files /dev/null and b/dynamic-proxy/etc/dynamic-proxy-sequence-diagram.png differ diff --git a/dynamic-proxy/pom.xml b/dynamic-proxy/pom.xml index c6e9f55cf461..decbb24fd02d 100644 --- a/dynamic-proxy/pom.xml +++ b/dynamic-proxy/pom.xml @@ -35,19 +35,28 @@ dynamic-proxy + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + com.fasterxml.jackson.core jackson-core - 2.17.2 + 2.19.0 com.fasterxml.jackson.core jackson-databind - 2.17.2 + 2.18.3 org.springframework spring-web + 7.0.0-M4 org.junit.jupiter diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java index 1a4efc606c96..c61223008e19 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/Album.java @@ -30,8 +30,8 @@ import lombok.NoArgsConstructor; /** - * This class represents an endpoint resource that - * we are going to interchange with a Rest API server. + * This class represents an endpoint resource that we are going to interchange with a Rest API + * server. */ @Data @AllArgsConstructor @@ -42,5 +42,4 @@ public class Album { private Integer id; private String title; private Integer userId; - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java index e53485ae06f1..84a0b22d041f 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumInvocationHandler.java @@ -31,8 +31,8 @@ import lombok.extern.slf4j.Slf4j; /** - * Class whose method 'invoke' will be called every time that an interface's method is called. - * That interface is linked to this class by the Proxy class. + * Class whose method 'invoke' will be called every time that an interface's method is called. That + * interface is linked to this class by the Proxy class. */ @Slf4j public class AlbumInvocationHandler implements InvocationHandler { @@ -42,7 +42,7 @@ public class AlbumInvocationHandler implements InvocationHandler { /** * Class constructor. It instantiates a TinyRestClient object. * - * @param baseUrl Root url for endpoints. + * @param baseUrl Root url for endpoints. * @param httpClient Handle the http communication. */ public AlbumInvocationHandler(String baseUrl, HttpClient httpClient) { @@ -52,10 +52,11 @@ public AlbumInvocationHandler(String baseUrl, HttpClient httpClient) { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - LOGGER.info("===== Calling the method {}.{}()", - method.getDeclaringClass().getSimpleName(), method.getName()); + LOGGER.info( + "===== Calling the method {}.{}()", + method.getDeclaringClass().getSimpleName(), + method.getName()); return restClient.send(method, args); } - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java index c0975b6e6555..8b4c0d02d692 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/AlbumService.java @@ -34,8 +34,8 @@ /** * Every method in this interface is annotated with the necessary metadata to represents an endpoint - * that we can call to communicate with a host server which is serving a resource by Rest API. - * This interface is focused in the resource Album. + * that we can call to communicate with a host server which is serving a resource by Rest API. This + * interface is focused in the resource Album. */ public interface AlbumService { @@ -69,7 +69,7 @@ public interface AlbumService { * Updates an existing album. * * @param albumId Album's id to be modified. - * @param album New album's data. + * @param album New album's data. * @return Updated album's data. */ @Put("/albums/{albumId}") @@ -83,5 +83,4 @@ public interface AlbumService { */ @Delete("/albums/{albumId}") Album deleteAlbum(@Path("albumId") Integer albumId); - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java index 61118694d652..d5a19184bf91 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/App.java @@ -30,14 +30,14 @@ /** * Application to demonstrate the Dynamic Proxy pattern. This application allow us to hit the public - * fake API https://jsonplaceholder.typicode.com for the resource Album through an interface. - * The call to Proxy.newProxyInstance creates a new dynamic proxy for the AlbumService interface and + * fake API https://jsonplaceholder.typicode.com for the resource Album through an interface. The + * call to Proxy.newProxyInstance creates a new dynamic proxy for the AlbumService interface and * sets the AlbumInvocationHandler class as the handler to intercept all the interface's methods. * Everytime that we call an AlbumService's method, the handler's method "invoke" will be call * automatically, and it will pass all the method's metadata and arguments to other specialized - * class - TinyRestClient - to prepare the Rest API call accordingly. - * In this demo, the Dynamic Proxy pattern help us to run business logic through interfaces without - * an explicit implementation of the interfaces and supported on the Java Reflection approach. + * class - TinyRestClient - to prepare the Rest API call accordingly. In this demo, the Dynamic + * Proxy pattern help us to run business logic through interfaces without an explicit implementation + * of the interfaces and supported on the Java Reflection approach. */ @Slf4j public class App { @@ -51,7 +51,7 @@ public class App { /** * Class constructor. * - * @param baseUrl Root url for endpoints. + * @param baseUrl Root url for endpoints. * @param httpClient Handle the http communication. */ public App(String baseUrl, HttpClient httpClient) { @@ -71,18 +71,23 @@ public static void main(String[] args) { } /** - * Create the Dynamic Proxy linked to the AlbumService interface and to the AlbumInvocationHandler. + * Create the Dynamic Proxy linked to the AlbumService interface and to the + * AlbumInvocationHandler. */ public void createDynamicProxy() { AlbumInvocationHandler albumInvocationHandler = new AlbumInvocationHandler(baseUrl, httpClient); - albumServiceProxy = (AlbumService) Proxy.newProxyInstance( - App.class.getClassLoader(), new Class[]{AlbumService.class}, albumInvocationHandler); + albumServiceProxy = + (AlbumService) + Proxy.newProxyInstance( + App.class.getClassLoader(), + new Class[] {AlbumService.class}, + albumInvocationHandler); } /** - * Call the methods of the Dynamic Proxy, in other words, the AlbumService interface's methods - * and receive the responses from the Rest API. + * Call the methods of the Dynamic Proxy, in other words, the AlbumService interface's methods and + * receive the responses from the Rest API. */ public void callMethods() { int albumId = 17; @@ -94,16 +99,16 @@ public void callMethods() { var album = albumServiceProxy.readAlbum(albumId); LOGGER.info("{}", album); - var newAlbum = albumServiceProxy.createAlbum(Album.builder() - .title("Big World").userId(userId).build()); + var newAlbum = + albumServiceProxy.createAlbum(Album.builder().title("Big World").userId(userId).build()); LOGGER.info("{}", newAlbum); - var editAlbum = albumServiceProxy.updateAlbum(albumId, Album.builder() - .title("Green Valley").userId(userId).build()); + var editAlbum = + albumServiceProxy.updateAlbum( + albumId, Album.builder().title("Green Valley").userId(userId).build()); LOGGER.info("{}", editAlbum); var removedAlbum = albumServiceProxy.deleteAlbum(albumId); LOGGER.info("{}", removedAlbum); } - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java index f76fe88ec76f..d474c59da20f 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/JsonUtil.java @@ -32,22 +32,19 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; -/** - * Utility class to handle Json operations. - */ +/** Utility class to handle Json operations. */ @Slf4j public class JsonUtil { private static ObjectMapper objectMapper = new ObjectMapper(); - private JsonUtil() { - } + private JsonUtil() {} /** * Convert an object to a Json string representation. * * @param object Object to convert. - * @param Object's class. + * @param Object's class. * @return Json string. */ public static String objectToJson(T object) { @@ -62,9 +59,9 @@ public static String objectToJson(T object) { /** * Convert a Json string to an object of a class. * - * @param json Json string to convert. + * @param json Json string to convert. * @param clazz Object's class. - * @param Object's generic class. + * @param Object's generic class. * @return Object. */ public static T jsonToObject(String json, Class clazz) { @@ -79,20 +76,19 @@ public static T jsonToObject(String json, Class clazz) { /** * Convert a Json string to a List of objects of a class. * - * @param json Json string to convert. + * @param json Json string to convert. * @param clazz Object's class. - * @param Object's generic class. + * @param Object's generic class. * @return List of objects. */ public static List jsonToList(String json, Class clazz) { try { - CollectionType listType = objectMapper.getTypeFactory() - .constructCollectionType(ArrayList.class, clazz); + CollectionType listType = + objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, clazz); return objectMapper.reader().forType(listType).readValue(json); } catch (JsonProcessingException e) { LOGGER.error("Cannot convert the Json " + json + " to List of " + clazz.getName() + ".", e); return List.of(); } } - } diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java index fe777beca34f..dd952f479fa0 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/TinyRestClient.java @@ -45,8 +45,8 @@ import org.springframework.web.util.UriUtils; /** - * Class to handle all the http communication with a Rest API. - * It is supported by the HttpClient Java library. + * Class to handle all the http communication with a Rest API. It is supported by the HttpClient + * Java library. */ @Slf4j public class TinyRestClient { @@ -59,7 +59,7 @@ public class TinyRestClient { /** * Class constructor. * - * @param baseUrl Root url for endpoints. + * @param baseUrl Root url for endpoints. * @param httpClient Handle the http communication. */ public TinyRestClient(String baseUrl, HttpClient httpClient) { @@ -71,9 +71,9 @@ public TinyRestClient(String baseUrl, HttpClient httpClient) { * Creates a http communication to request and receive data from an endpoint. * * @param method Interface's method which is annotated with a http method. - * @param args Method's arguments passed in the call. + * @param args Method's arguments passed in the call. * @return Response from the endpoint. - * @throws IOException Exception thrown when any fail happens in the call. + * @throws IOException Exception thrown when any fail happens in the call. * @throws InterruptedException Exception thrown when call is interrupted. */ public Object send(Method method, Object[] args) throws IOException, InterruptedException { @@ -84,11 +84,12 @@ public Object send(Method method, Object[] args) throws IOException, Interrupted var httpAnnotationName = httpAnnotation.annotationType().getSimpleName().toUpperCase(); var url = baseUrl + buildUrl(method, args, httpAnnotation); var bodyPublisher = buildBodyPublisher(method, args); - var httpRequest = HttpRequest.newBuilder() - .uri(URI.create(url)) - .header("Content-Type", "application/json") - .method(httpAnnotationName, bodyPublisher) - .build(); + var httpRequest = + HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Content-Type", "application/json") + .method(httpAnnotationName, bodyPublisher) + .build(); var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); var statusCode = httpResponse.statusCode(); if (statusCode >= HttpURLConnection.HTTP_BAD_REQUEST) { @@ -140,8 +141,8 @@ private Object getResponse(Method method, HttpResponse httpResponse) { return null; } if (returnType instanceof ParameterizedType) { - Class responseClass = (Class) (((ParameterizedType) returnType) - .getActualTypeArguments()[0]); + Class responseClass = + (Class) (((ParameterizedType) returnType).getActualTypeArguments()[0]); return JsonUtil.jsonToList(rawData, responseClass); } else { Class responseClass = method.getReturnType(); @@ -150,22 +151,28 @@ private Object getResponse(Method method, HttpResponse httpResponse) { } private Annotation getHttpAnnotation(Method method) { - return httpAnnotationByMethod.computeIfAbsent(method, m -> - Arrays.stream(m.getDeclaredAnnotations()) - .filter(annot -> annot.annotationType().isAnnotationPresent(Http.class)) - .findFirst().orElse(null)); + return httpAnnotationByMethod.computeIfAbsent( + method, + m -> + Arrays.stream(m.getDeclaredAnnotations()) + .filter(annot -> annot.annotationType().isAnnotationPresent(Http.class)) + .findFirst() + .orElse(null)); } private Annotation getAnnotationOf(Annotation[] annotations, Class clazz) { return Arrays.stream(annotations) .filter(annot -> annot.annotationType().equals(clazz)) - .findFirst().orElse(null); + .findFirst() + .orElse(null); } private String annotationValue(Annotation annotation) { - var valueMethod = Arrays.stream(annotation.annotationType().getDeclaredMethods()) - .filter(methodAnnot -> methodAnnot.getName().equals("value")) - .findFirst().orElse(null); + var valueMethod = + Arrays.stream(annotation.annotationType().getDeclaredMethods()) + .filter(methodAnnot -> methodAnnot.getName().equals("value")) + .findFirst() + .orElse(null); if (valueMethod == null) { return null; } @@ -173,8 +180,13 @@ private String annotationValue(Annotation annotation) { try { result = valueMethod.invoke(annotation, (Object[]) null); } catch (Exception e) { - LOGGER.error("Cannot read the value " + annotation.annotationType().getSimpleName() - + "." + valueMethod.getName() + "()", e); + LOGGER.error( + "Cannot read the value " + + annotation.annotationType().getSimpleName() + + "." + + valueMethod.getName() + + "()", + e); result = null; } return (result instanceof String strResult ? strResult : null); diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java index 1d91c0af1acf..df89731936ba 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Body.java @@ -30,10 +30,9 @@ import java.lang.annotation.Target; /** - * Annotation to mark a method's parameter as a Body parameter. - * It is typically used on Post and Put http methods. + * Annotation to mark a method's parameter as a Body parameter. It is typically used on Post and Put + * http methods. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) -public @interface Body { -} +public @interface Body {} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java index 5f21457abc36..a43df905798f 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Delete.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark an interface's method as a DELETE http method. - */ +/** Annotation to mark an interface's method as a DELETE http method. */ @Http @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java index 163a65ad9a65..74f96ac062ec 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Get.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark an interface's method as a GET http method. - */ +/** Annotation to mark an interface's method as a GET http method. */ @Http @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java index beabcdb69aa7..f12878de2d90 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Http.java @@ -29,10 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark other annotations to be recognized as http methods. - */ +/** Annotation to mark other annotations to be recognized as http methods. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) -public @interface Http { -} +public @interface Http {} diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java index 0f2bcd007b94..4ff1faea4844 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Path.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark a method's parameter as a Path parameter. - */ +/** Annotation to mark a method's parameter as a Path parameter. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Path { diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java index 9885389c4c75..f10f38c83984 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Post.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark an interface's method as a POST http method. - */ +/** Annotation to mark an interface's method as a POST http method. */ @Http @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java index 851303ed8182..9af9b27c4dde 100644 --- a/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java +++ b/dynamic-proxy/src/main/java/com/iluwatar/dynamicproxy/tinyrestclient/annotation/Put.java @@ -29,9 +29,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Annotation to mark an interface's method as a PUT http method. - */ +/** Annotation to mark an interface's method as a PUT http method. */ @Http @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) diff --git a/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java b/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java index 72412ca5b976..107255695344 100644 --- a/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java +++ b/dynamic-proxy/src/test/java/com/iluwatar/dynamicproxy/AppTest.java @@ -24,14 +24,14 @@ */ package com.iluwatar.dynamicproxy; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + class AppTest { @Test void shouldRunAppWithoutExceptions() { - assertDoesNotThrow(() -> App.main(null)); + assertDoesNotThrow(() -> App.main(null)); } } diff --git a/event-aggregator/README.md b/event-aggregator/README.md index a052e87c462f..063aac502640 100644 --- a/event-aggregator/README.md +++ b/event-aggregator/README.md @@ -36,6 +36,10 @@ In Plain Words > Event Aggregator is a design pattern that allows multiple event sources to communicate with event handlers through a central point, rather than having each event source communicate directly with each handler. +Sequence diagram + +![Event Aggregator sequence diagram](./etc/event-aggregator-sequence-diagram.png) + ## Programmatic Example of Event Aggregator Pattern in Java Consider the following example where we use the Event Aggregator to handle multiple events. @@ -161,10 +165,6 @@ The console output after running the example. 21:37:38.739 [main] INFO com.iluwatar.event.aggregator.KingJoffrey -- Received event from the King's Hand: Traitor detected ``` -## Detailed Explanation of Event Aggregator Pattern with Real-World Examples - -![Event Aggregator](./etc/classes.png "Event Aggregator") - ## When to Use the Event Aggregator Pattern in Java Use the Event Aggregator pattern when diff --git a/event-aggregator/etc/event-aggregator-sequence-diagram.png b/event-aggregator/etc/event-aggregator-sequence-diagram.png new file mode 100644 index 000000000000..f96fbc427a86 Binary files /dev/null and b/event-aggregator/etc/event-aggregator-sequence-diagram.png differ diff --git a/event-aggregator/pom.xml b/event-aggregator/pom.xml index 2bf8797f4855..3e7e58120155 100644 --- a/event-aggregator/pom.xml +++ b/event-aggregator/pom.xml @@ -34,6 +34,14 @@ event-aggregator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java index 60a6381c9e12..4b5e9a615cbf 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/App.java @@ -67,12 +67,7 @@ public static void main(String[] args) { var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED); - var emitters = List.of( - kingsHand, - baelish, - varys, - scout - ); + var emitters = List.of(kingsHand, baelish, varys, scout); Arrays.stream(Weekday.values()) .>map(day -> emitter -> emitter.timePasses(day)) diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java index dd839410cdc8..87b1f5eafa22 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Event.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Event enumeration. - */ +/** Event enumeration. */ @RequiredArgsConstructor public enum Event { - WHITE_WALKERS_SIGHTED("White walkers sighted"), STARK_SIGHTED("Stark sighted"), WARSHIPS_APPROACHING("Warships approaching"), diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java index 60bc056393d6..ccff62740db7 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventEmitter.java @@ -29,9 +29,7 @@ import java.util.List; import java.util.Map; -/** - * EventEmitter is the base class for event producers that can be observed. - */ +/** EventEmitter is the base class for event producers that can be observed. */ public abstract class EventEmitter { private final Map> observerLists; @@ -46,11 +44,11 @@ public EventEmitter(EventObserver obs, Event e) { } /** - * Registers observer for specific event in the related list. - * - * @param obs the observer that observers this emitter - * @param e the specific event for that observation occurs - * */ + * Registers observer for specific event in the related list. + * + * @param obs the observer that observers this emitter + * @param e the specific event for that observation occurs + */ public final void registerObserver(EventObserver obs, Event e) { if (!observerLists.containsKey(e)) { observerLists.put(e, new LinkedList<>()); @@ -62,9 +60,7 @@ public final void registerObserver(EventObserver obs, Event e) { protected void notifyObservers(Event e) { if (observerLists.containsKey(e)) { - observerLists - .get(e) - .forEach(observer -> observer.onEvent(e)); + observerLists.get(e).forEach(observer -> observer.onEvent(e)); } } diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventObserver.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventObserver.java index 631e24cb8c1f..2db98f93982b 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventObserver.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/EventObserver.java @@ -24,11 +24,8 @@ */ package com.iluwatar.event.aggregator; -/** - * Observers of events implement this interface. - */ +/** Observers of events implement this interface. */ public interface EventObserver { void onEvent(Event e); - } diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingJoffrey.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingJoffrey.java index 6273fdd599eb..90036f664ab7 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingJoffrey.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingJoffrey.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * KingJoffrey observes events from {@link KingsHand}. - */ +/** KingJoffrey observes events from {@link KingsHand}. */ @Slf4j public class KingJoffrey implements EventObserver { diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java index 38bb04ea45cb..c9089a68fdda 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/KingsHand.java @@ -24,13 +24,10 @@ */ package com.iluwatar.event.aggregator; -/** - * KingsHand observes events from multiple sources and delivers them to listeners. - */ +/** KingsHand observes events from multiple sources and delivers them to listeners. */ public class KingsHand extends EventEmitter implements EventObserver { - public KingsHand() { - } + public KingsHand() {} public KingsHand(EventObserver obs, Event e) { super(obs, e); @@ -43,5 +40,8 @@ public void onEvent(Event e) { @Override public void timePasses(Weekday day) { + // This method is intentionally left empty because KingsHand does not handle time-based events + // directly. + // It serves as a placeholder to fulfill the EventObserver interface contract. } } diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java index 00033805ff0b..f133a774d89a 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordBaelish.java @@ -24,13 +24,10 @@ */ package com.iluwatar.event.aggregator; -/** - * LordBaelish produces events. - */ +/** LordBaelish produces events. */ public class LordBaelish extends EventEmitter { - public LordBaelish() { - } + public LordBaelish() {} public LordBaelish(EventObserver obs, Event e) { super(obs, e); diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java index 883560bc7170..5472e2288496 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/LordVarys.java @@ -26,14 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * LordVarys produces events. - */ +/** LordVarys produces events. */ @Slf4j public class LordVarys extends EventEmitter implements EventObserver { - public LordVarys() { - } + public LordVarys() {} public LordVarys(EventObserver obs, Event e) { super(obs, e); @@ -46,7 +43,6 @@ public void timePasses(Weekday day) { } } - @Override public void onEvent(Event e) { notifyObservers(e); diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java index ef983585e59b..d1566571a465 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Scout.java @@ -24,13 +24,10 @@ */ package com.iluwatar.event.aggregator; -/** - * Scout produces events. - */ +/** Scout produces events. */ public class Scout extends EventEmitter { - public Scout() { - } + public Scout() {} public Scout(EventObserver obs, Event e) { super(obs, e); diff --git a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java index 6dbbd75c95fb..b232ad54b563 100644 --- a/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java +++ b/event-aggregator/src/main/java/com/iluwatar/event/aggregator/Weekday.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Weekday enumeration. - */ +/** Weekday enumeration. */ @RequiredArgsConstructor public enum Weekday { - MONDAY("Monday"), TUESDAY("Tuesday"), WEDNESDAY("Wednesday"), diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java index 3b0a66027abe..2ccd559a832e 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/AppTest.java @@ -24,23 +24,19 @@ */ package com.iluwatar.event.aggregator; -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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java index 2dba768f323b..bfc00e6cd88a 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventEmitterTest.java @@ -42,32 +42,26 @@ */ abstract class EventEmitterTest { - /** - * Factory used to create a new instance of the test object with a default observer - */ + /** Factory used to create a new instance of the test object with a default observer */ private final BiFunction factoryWithDefaultObserver; - /** - * Factory used to create a new instance of the test object without passing a default observer - */ + /** Factory used to create a new instance of the test object without passing a default observer */ private final Supplier factoryWithoutDefaultObserver; - /** - * The day of the week an event is expected - */ + /** The day of the week an event is expected */ private final Weekday specialDay; - /** - * The expected event, emitted on the special day - */ + /** The expected event, emitted on the special day */ private final Event event; /** * Create a new event emitter test, using the given test object factories, special day and event */ - EventEmitterTest(final Weekday specialDay, final Event event, - final BiFunction factoryWithDefaultObserver, - final Supplier factoryWithoutDefaultObserver) { + EventEmitterTest( + final Weekday specialDay, + final Event event, + final BiFunction factoryWithDefaultObserver, + final Supplier factoryWithoutDefaultObserver) { this.specialDay = specialDay; this.event = event; @@ -90,12 +84,15 @@ void testAllDays() { * received the correct event on the special day. * * @param specialDay The special day on which an event is emitted - * @param event The expected event emitted by the test object - * @param emitter The event emitter - * @param observers The registered observer mocks + * @param event The expected event emitted by the test object + * @param emitter The event emitter + * @param observers The registered observer mocks */ - private void testAllDays(final Weekday specialDay, final Event event, final E emitter, - final EventObserver... observers) { + private void testAllDays( + final Weekday specialDay, + final Event event, + final E emitter, + final EventObserver... observers) { for (final var weekday : Weekday.values()) { // Pass each week of the day, day by day to the event emitter @@ -121,7 +118,7 @@ private void testAllDays(final Weekday specialDay, final Event event, final E em * event emitter without a default observer * * @param specialDay The special day on which an event is emitted - * @param event The expected event emitted by the test object + * @param event The expected event emitted by the test object */ private void testAllDaysWithoutDefaultObserver(final Weekday specialDay, final Event event) { final var observer1 = mock(EventObserver.class); @@ -138,7 +135,7 @@ private void testAllDaysWithoutDefaultObserver(final Weekday specialDay, final E * Go over every day of the month, and check if the event is emitted on the given day. * * @param specialDay The special day on which an event is emitted - * @param event The expected event emitted by the test object + * @param event The expected event emitted by the test object */ private void testAllDaysWithDefaultObserver(final Weekday specialDay, final Event event) { final var defaultObserver = mock(EventObserver.class); @@ -151,5 +148,4 @@ private void testAllDaysWithDefaultObserver(final Weekday specialDay, final Even testAllDays(specialDay, event, emitter, defaultObserver, observer1, observer2); } - } diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java index b5f65c1bb3eb..20c3c489925f 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/EventTest.java @@ -30,21 +30,18 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * EventTest - * - */ +/** EventTest */ class EventTest { - /** - * Verify if every event has a non-null, non-empty description - */ + /** Verify if every event has a non-null, non-empty description */ @Test void testToString() { - Arrays.stream(Event.values()).map(Event::toString).forEach(toString -> { - assertNotNull(toString); - assertFalse(toString.trim().isEmpty()); - }); + Arrays.stream(Event.values()) + .map(Event::toString) + .forEach( + toString -> { + assertNotNull(toString); + assertFalse(toString.trim().isEmpty()); + }); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java index 70d731d20686..aa689a34e9c5 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingJoffreyTest.java @@ -37,10 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * KingJoffreyTest - * - */ +/** KingJoffreyTest */ class KingJoffreyTest { private InMemoryAppender appender; @@ -55,22 +52,21 @@ void tearDown() { appender.stop(); } - /** - * Test if {@link KingJoffrey} tells us what event he received - */ + /** Test if {@link KingJoffrey} tells us what event he received */ @Test void testOnEvent() { final var kingJoffrey = new KingJoffrey(); - IntStream.range(0, Event.values().length).forEach(i -> { - assertEquals(i, appender.getLogSize()); - var event = Event.values()[i]; - kingJoffrey.onEvent(event); - final var expectedMessage = "Received event from the King's Hand: " + event; - assertEquals(expectedMessage, appender.getLastMessage()); - assertEquals(i + 1, appender.getLogSize()); - }); - + IntStream.range(0, Event.values().length) + .forEach( + i -> { + assertEquals(i, appender.getLogSize()); + var event = Event.values()[i]; + kingJoffrey.onEvent(event); + final var expectedMessage = "Received event from the King's Hand: " + event; + assertEquals(expectedMessage, appender.getLastMessage()); + assertEquals(i + 1, appender.getLogSize()); + }); } private static class InMemoryAppender extends AppenderBase { @@ -94,5 +90,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java index 533252a2d601..e06a50fc80b9 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/KingsHandTest.java @@ -33,15 +33,10 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * KingsHandTest - * - */ +/** KingsHandTest */ class KingsHandTest extends EventEmitterTest { - /** - * Create a new test instance, using the correct object factory - */ + /** Create a new test instance, using the correct object factory */ public KingsHandTest() { super(null, null, KingsHand::new, KingsHand::new); } @@ -64,12 +59,12 @@ void testPassThrough() { verifyNoMoreInteractions(observer); // Verify if each event is passed on to the observer, nothing less, nothing more. - Arrays.stream(Event.values()).forEach(event -> { - kingsHand.onEvent(event); - verify(observer, times(1)).onEvent(eq(event)); - verifyNoMoreInteractions(observer); - }); - + Arrays.stream(Event.values()) + .forEach( + event -> { + kingsHand.onEvent(event); + verify(observer, times(1)).onEvent(eq(event)); + verifyNoMoreInteractions(observer); + }); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java index 75481c2afaca..c90ccd993317 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordBaelishTest.java @@ -24,17 +24,11 @@ */ package com.iluwatar.event.aggregator; -/** - * LordBaelishTest - * - */ +/** LordBaelishTest */ class LordBaelishTest extends EventEmitterTest { - /** - * Create a new test instance, using the correct object factory - */ + /** Create a new test instance, using the correct object factory */ public LordBaelishTest() { super(Weekday.FRIDAY, Event.STARK_SIGHTED, LordBaelish::new, LordBaelish::new); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java index 68aaff394876..5ddd0a65e26f 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/LordVarysTest.java @@ -24,17 +24,11 @@ */ package com.iluwatar.event.aggregator; -/** - * LordVarysTest - * - */ +/** LordVarysTest */ class LordVarysTest extends EventEmitterTest { - /** - * Create a new test instance, using the correct object factory - */ + /** Create a new test instance, using the correct object factory */ public LordVarysTest() { super(Weekday.SATURDAY, Event.TRAITOR_DETECTED, LordVarys::new, LordVarys::new); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java index 0b10c86c1e1a..b6f86123e45c 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/ScoutTest.java @@ -24,19 +24,12 @@ */ package com.iluwatar.event.aggregator; -/** - * ScoutTest - * - */ +/** ScoutTest */ class ScoutTest extends EventEmitterTest { - /** - * Create a new test instance, using the correct object factory - */ + /** Create a new test instance, using the correct object factory */ public ScoutTest() { - super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new); - + super(Weekday.TUESDAY, Event.WARSHIPS_APPROACHING, Scout::new, Scout::new); } - -} \ No newline at end of file +} diff --git a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java index 9726050092fb..e078951e4eb4 100644 --- a/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java +++ b/event-aggregator/src/test/java/com/iluwatar/event/aggregator/WeekdayTest.java @@ -30,19 +30,17 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * WeekdayTest - * - */ +/** WeekdayTest */ class WeekdayTest { @Test void testToString() { - Arrays.stream(Weekday.values()).forEach(weekday -> { - final String toString = weekday.toString(); - assertNotNull(toString); - assertEquals(weekday.name(), toString.toUpperCase()); - }); + Arrays.stream(Weekday.values()) + .forEach( + weekday -> { + final String toString = weekday.toString(); + assertNotNull(toString); + assertEquals(weekday.name(), toString.toUpperCase()); + }); } - -} \ No newline at end of file +} diff --git a/event-based-asynchronous/README.md b/event-based-asynchronous/README.md index eee9758dd638..6eb885c715e9 100644 --- a/event-based-asynchronous/README.md +++ b/event-based-asynchronous/README.md @@ -32,6 +32,10 @@ In Plain Words > The Event-Based Asynchronous design pattern allows tasks to be executed in the background, notifying the main program via events when completed, thereby enhancing system efficiency and responsiveness without blocking ongoing operations. +Sequence diagram + +![Event-Based Asynchronous sequence diagram](./etc/event-based-asynchronous-sequence-diagram.png) + ## Programmatic Example of Event-Based Asynchronous Pattern in Java Event-Based Asynchronous design pattern allows tasks to be executed in the background, notifying the main program via events when completed, thereby enhancing system efficiency and responsiveness without blocking ongoing operations. diff --git a/event-based-asynchronous/etc/event-based-asynchronous-sequence-diagram.png b/event-based-asynchronous/etc/event-based-asynchronous-sequence-diagram.png new file mode 100644 index 000000000000..882975df5c0a Binary files /dev/null and b/event-based-asynchronous/etc/event-based-asynchronous-sequence-diagram.png differ diff --git a/event-based-asynchronous/pom.xml b/event-based-asynchronous/pom.xml index 1c1a9346c43a..3a66d321c02f 100644 --- a/event-based-asynchronous/pom.xml +++ b/event-based-asynchronous/pom.xml @@ -34,6 +34,14 @@ event-based-asynchronous + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -42,7 +50,7 @@ org.awaitility awaitility - 4.2.2 + 4.3.0 test diff --git a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java index fd94357d0739..731cd169157e 100644 --- a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/App.java @@ -97,9 +97,7 @@ public void setUp() { } } - /** - * Run program in either interactive mode or not. - */ + /** Run program in either interactive mode or not. */ public void run() { if (interactiveMode) { runInteractiveMode(); @@ -108,9 +106,7 @@ public void run() { } } - /** - * Run program in non-interactive mode. - */ + /** Run program in non-interactive mode. */ public void quickRun() { var eventManager = new EventManager(); @@ -135,15 +131,15 @@ public void quickRun() { eventManager.cancel(syncEventId); LOGGER.info("Sync Event [{}] has been stopped.", syncEventId); - } catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException + } catch (MaxNumOfEventsAllowedException + | LongRunningEventException + | EventDoesNotExistException | InvalidOperationException e) { LOGGER.error(e.getMessage()); } } - /** - * Run program in interactive mode. - */ + /** Run program in interactive mode. */ public void runInteractiveMode() { var eventManager = new EventManager(); @@ -151,7 +147,8 @@ public void runInteractiveMode() { var option = -1; while (option != 4) { LOGGER.info("Hello. Would you like to boil some eggs?"); - LOGGER.info(""" + LOGGER.info( + """ (1) BOIL AN EGG (2) STOP BOILING THIS EGG (3) HOW ARE MY EGGS? @@ -214,7 +211,8 @@ private void processOption1(EventManager eventManager, Scanner s) { var eventId = eventManager.createAsync(eventTime); eventManager.start(eventId); LOGGER.info("Egg [{}] is being boiled.", eventId); - } catch (MaxNumOfEventsAllowedException | LongRunningEventException + } catch (MaxNumOfEventsAllowedException + | LongRunningEventException | EventDoesNotExistException e) { LOGGER.error(e.getMessage()); } @@ -223,13 +221,14 @@ private void processOption1(EventManager eventManager, Scanner s) { var eventId = eventManager.create(eventTime); eventManager.start(eventId); LOGGER.info("Egg [{}] is being boiled.", eventId); - } catch (MaxNumOfEventsAllowedException | InvalidOperationException - | LongRunningEventException | EventDoesNotExistException e) { + } catch (MaxNumOfEventsAllowedException + | InvalidOperationException + | LongRunningEventException + | EventDoesNotExistException e) { LOGGER.error(e.getMessage()); } } else { LOGGER.info("Unknown event type."); } } - -} \ No newline at end of file +} diff --git a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java index 7537afd447c2..b58848f0a702 100644 --- a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/AsyncEvent.java @@ -32,17 +32,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Each Event runs as a separate/individual thread. - */ +/** Each Event runs as a separate/individual thread. */ @Slf4j @RequiredArgsConstructor public class AsyncEvent implements Event, Runnable { private final int eventId; private final Duration eventTime; - @Getter - private final boolean synchronous; + @Getter private final boolean synchronous; private Thread thread; private final AtomicBoolean isComplete = new AtomicBoolean(false); private ThreadCompleteListener eventListener; @@ -97,4 +94,4 @@ private void completed() { eventListener.completedEventHandler(eventId); } } -} \ No newline at end of file +} diff --git a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java index 663ba5a7ef02..6c51f8584ec6 100644 --- a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/Event.java @@ -34,5 +34,4 @@ public interface Event { void stop(); void status(); - } diff --git a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java index 8069060fe5e7..ad6a649c058f 100644 --- a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventDoesNotExistException.java @@ -26,13 +26,10 @@ import java.io.Serial; -/** - * Custom Exception Class for Non-Existent Event. - */ +/** Custom Exception Class for Non-Existent Event. */ public class EventDoesNotExistException extends Exception { - @Serial - private static final long serialVersionUID = -3398463738273811509L; + @Serial private static final long serialVersionUID = -3398463738273811509L; public EventDoesNotExistException(String message) { super(message); diff --git a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java index 44561d6e2ca1..cef3697ffb55 100644 --- a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/EventManager.java @@ -31,9 +31,9 @@ import lombok.Getter; /** - * EventManager handles and maintains a pool of event threads. {@link AsyncEvent} threads are created - * upon user request. Thre are two types of events; Asynchronous and Synchronous. There can be - * multiple Asynchronous events running at once but only one Synchronous event running at a time. + * EventManager handles and maintains a pool of event threads. {@link AsyncEvent} threads are + * created upon user request. Thre are two types of events; Asynchronous and Synchronous. There can + * be multiple Asynchronous events running at once but only one Synchronous event running at a time. * Currently supported event operations are: start, stop, and getStatus. Once an event is complete, * it then notifies EventManager through a listener. The EventManager then takes the event out of * the pool. @@ -48,18 +48,14 @@ public class EventManager implements ThreadCompleteListener { private int currentlyRunningSyncEvent = -1; private final SecureRandom rand; - @Getter - private final Map eventPool; + @Getter private final Map eventPool; private static final String DOES_NOT_EXIST = " does not exist."; - /** - * EventManager constructor. - */ + /** EventManager constructor. */ public EventManager() { rand = new SecureRandom(); eventPool = new ConcurrentHashMap<>(MAX_RUNNING_EVENTS); - } /** @@ -68,15 +64,18 @@ public EventManager() { * @param eventTime Time an event should run for. * @return eventId * @throws MaxNumOfEventsAllowedException When too many events are running at a time. - * @throws InvalidOperationException No new synchronous events can be created when one is - * already running. - * @throws LongRunningEventException Long-running events are not allowed in the app. + * @throws InvalidOperationException No new synchronous events can be created when one is already + * running. + * @throws LongRunningEventException Long-running events are not allowed in the app. */ public int create(Duration eventTime) throws MaxNumOfEventsAllowedException, InvalidOperationException, LongRunningEventException { if (currentlyRunningSyncEvent != -1) { - throw new InvalidOperationException("Event [" + currentlyRunningSyncEvent + "] is still" - + " running. Please wait until it finishes and try again."); + throw new InvalidOperationException( + "Event [" + + currentlyRunningSyncEvent + + "] is still" + + " running. Please wait until it finishes and try again."); } var eventId = createEvent(eventTime, true); @@ -91,10 +90,10 @@ public int create(Duration eventTime) * @param eventTime Time an event should run for. * @return eventId * @throws MaxNumOfEventsAllowedException When too many events are running at a time. - * @throws LongRunningEventException Long-running events are not allowed in the app. + * @throws LongRunningEventException Long-running events are not allowed in the app. */ - public int createAsync(Duration eventTime) throws MaxNumOfEventsAllowedException, - LongRunningEventException { + public int createAsync(Duration eventTime) + throws MaxNumOfEventsAllowedException, LongRunningEventException { return createEvent(eventTime, false); } @@ -105,8 +104,8 @@ private int createEvent(Duration eventTime, boolean isSynchronous) } if (eventPool.size() == MAX_RUNNING_EVENTS) { - throw new MaxNumOfEventsAllowedException("Too many events are running at the moment." - + " Please try again later."); + throw new MaxNumOfEventsAllowedException( + "Too many events are running at the moment." + " Please try again later."); } if (eventTime.getSeconds() > MAX_EVENT_TIME.getSeconds()) { @@ -170,17 +169,13 @@ public void status(int eventId) throws EventDoesNotExistException { eventPool.get(eventId).status(); } - /** - * Gets status of all running events. - */ + /** Gets status of all running events. */ @SuppressWarnings("rawtypes") public void statusOfAllEvents() { eventPool.entrySet().forEach(entry -> ((AsyncEvent) ((Map.Entry) entry).getValue()).status()); } - /** - * Stop all running events. - */ + /** Stop all running events. */ @SuppressWarnings("rawtypes") public void shutdown() { eventPool.entrySet().forEach(entry -> ((AsyncEvent) ((Map.Entry) entry).getValue()).stop()); @@ -188,8 +183,7 @@ public void shutdown() { /** * Returns a pseudo-random number between min and max, inclusive. The difference between min and - * max can be at most - * Integer.MAX_VALUE - 1. + * max can be at most Integer.MAX_VALUE - 1. */ private int generateId() { // nextInt is normally exclusive of the top value, @@ -203,7 +197,8 @@ private int generateId() { } /** - * Callback from an {@link AsyncEvent} (once it is complete). The Event is then removed from the pool. + * Callback from an {@link AsyncEvent} (once it is complete). The Event is then removed from the + * pool. */ @Override public void completedEventHandler(int eventId) { @@ -214,10 +209,8 @@ public void completedEventHandler(int eventId) { eventPool.remove(eventId); } - /** - * Get number of currently running Synchronous events. - */ + /** Get number of currently running Synchronous events. */ public int numOfCurrentlyRunningSyncEvent() { return currentlyRunningSyncEvent; } -} \ No newline at end of file +} diff --git a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java index cffe3a3cc029..f8a5914602de 100644 --- a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/InvalidOperationException.java @@ -26,16 +26,12 @@ import java.io.Serial; -/** - * Type of Exception raised when the Operation being invoked is Invalid. - */ +/** Type of Exception raised when the Operation being invoked is Invalid. */ public class InvalidOperationException extends Exception { - @Serial - private static final long serialVersionUID = -6191545255213410803L; + @Serial private static final long serialVersionUID = -6191545255213410803L; public InvalidOperationException(String message) { super(message); } - } diff --git a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java index 54e2717c8c19..9045b6dcf9b1 100644 --- a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/LongRunningEventException.java @@ -26,13 +26,10 @@ import java.io.Serial; -/** - * Type of Exception raised when the Operation being invoked is Long Running. - */ +/** Type of Exception raised when the Operation being invoked is Long Running. */ public class LongRunningEventException extends Exception { - @Serial - private static final long serialVersionUID = -483423544320148809L; + @Serial private static final long serialVersionUID = -483423544320148809L; public LongRunningEventException(String message) { super(message); diff --git a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java index af4da61f0584..16fa5502d1f9 100644 --- a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/MaxNumOfEventsAllowedException.java @@ -26,13 +26,10 @@ import java.io.Serial; -/** - * Type of Exception raised when the max number of allowed events is exceeded. - */ +/** Type of Exception raised when the max number of allowed events is exceeded. */ public class MaxNumOfEventsAllowedException extends Exception { - @Serial - private static final long serialVersionUID = -8430876973516292695L; + @Serial private static final long serialVersionUID = -8430876973516292695L; public MaxNumOfEventsAllowedException(String message) { super(message); diff --git a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java index 334cada2cb4e..f30eb51351a5 100644 --- a/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java +++ b/event-based-asynchronous/src/main/java/com/iluwatar/event/asynchronous/ThreadCompleteListener.java @@ -24,9 +24,7 @@ */ package com.iluwatar.event.asynchronous; -/** - * Interface with listener behaviour related to Thread Completion. - */ +/** Interface with listener behaviour related to Thread Completion. */ public interface ThreadCompleteListener { void completedEventHandler(final int eventId); } diff --git a/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java index 4e4e33ac6cf6..4b5094033b13 100644 --- a/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java +++ b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/AppTest.java @@ -24,23 +24,19 @@ */ package com.iluwatar.event.asynchronous; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that EventAsynchronous example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that EventAsynchronous 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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java index 5de00634133f..ab9dd70bbf3d 100644 --- a/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java +++ b/event-based-asynchronous/src/test/java/com/iluwatar/event/asynchronous/EventAsynchronousTest.java @@ -31,13 +31,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.time.Duration; import lombok.SneakyThrows; import org.junit.jupiter.api.Test; -import java.time.Duration; -/** - * Application test - */ +/** Application test */ class EventAsynchronousTest { @Test @@ -46,7 +44,7 @@ void testAsynchronousEvent() { var eventManager = new EventManager(); var aEventId = eventManager.createAsync(Duration.ofSeconds(60)); - assertDoesNotThrow(() ->eventManager.start(aEventId)); + assertDoesNotThrow(() -> eventManager.start(aEventId)); assertEquals(1, eventManager.getEventPool().size()); assertTrue(eventManager.getEventPool().size() < EventManager.MAX_RUNNING_EVENTS); @@ -54,7 +52,6 @@ void testAsynchronousEvent() { assertDoesNotThrow(() -> eventManager.cancel(aEventId)); assertTrue(eventManager.getEventPool().isEmpty()); - } @Test @@ -70,7 +67,6 @@ void testSynchronousEvent() { assertDoesNotThrow(() -> eventManager.cancel(sEventId)); assertTrue(eventManager.getEventPool().isEmpty()); - } @Test @@ -86,21 +82,21 @@ void testFullSynchronousEvent() { eventManager.start(sEventId); await().until(() -> eventManager.getEventPool().isEmpty()); - } @Test @SneakyThrows void testUnsuccessfulSynchronousEvent() { - assertThrows(InvalidOperationException.class, () -> { - var eventManager = new EventManager(); - - var sEventId = assertDoesNotThrow(() -> eventManager.create(Duration.ofSeconds(60))); - eventManager.start(sEventId); - sEventId = eventManager.create(Duration.ofSeconds(60)); - eventManager.start(sEventId); - - }); + assertThrows( + InvalidOperationException.class, + () -> { + var eventManager = new EventManager(); + + var sEventId = assertDoesNotThrow(() -> eventManager.create(Duration.ofSeconds(60))); + eventManager.start(sEventId); + sEventId = eventManager.create(Duration.ofSeconds(60)); + eventManager.start(sEventId); + }); } @Test @@ -119,28 +115,27 @@ void testFullAsynchronousEvent() { eventManager.start(aEventId3); await().until(() -> eventManager.getEventPool().isEmpty()); - } @Test - void testLongRunningEventException(){ - assertThrows(LongRunningEventException.class, () -> { - var eventManager = new EventManager(); - eventManager.createAsync(Duration.ofMinutes(31)); - }); + void testLongRunningEventException() { + assertThrows( + LongRunningEventException.class, + () -> { + var eventManager = new EventManager(); + eventManager.createAsync(Duration.ofMinutes(31)); + }); } - @Test - void testMaxNumOfEventsAllowedException(){ - assertThrows(MaxNumOfEventsAllowedException.class, () -> { - final var eventManager = new EventManager(); - for(int i=0;i<1100;i++){ - eventManager.createAsync(Duration.ofSeconds(i)); - } - }); + void testMaxNumOfEventsAllowedException() { + assertThrows( + MaxNumOfEventsAllowedException.class, + () -> { + final var eventManager = new EventManager(); + for (int i = 0; i < 1100; i++) { + eventManager.createAsync(Duration.ofSeconds(i)); + } + }); } - - - -} \ No newline at end of file +} diff --git a/event-driven-architecture/README.md b/event-driven-architecture/README.md index 435637a95ada..c868c174e078 100644 --- a/event-driven-architecture/README.md +++ b/event-driven-architecture/README.md @@ -38,6 +38,10 @@ Wikipedia says > Event-driven architecture (EDA) is a software architecture paradigm concerning the production and detection of events. +Architecture diagram + +![EDA Architecture Diagram](./etc/eda-architecture-diagram.png) + ## Programmatic Example of Event-Driven Architecture in Java The Event-Driven Architecture (EDA) pattern in this module is implemented using several key classes and concepts: @@ -153,10 +157,6 @@ Running the example produces the following console output: This example demonstrates the Event-Driven Architecture pattern, where the occurrence of events drives the flow of the program. The system is designed to respond to events as they occur, which allows for a high degree of flexibility and decoupling between components. -## Detailed Explanation of Event-Driven Architecture Pattern with Real-World Examples - -![Event-Driven Architecture](./etc/eda.png "Event-Driven Architecture") - ## When to Use the Event-Driven Architecture Pattern in Java Use an Event-driven architecture when diff --git a/event-driven-architecture/etc/eda-architecture-diagram.png b/event-driven-architecture/etc/eda-architecture-diagram.png new file mode 100644 index 000000000000..4e74bcf2e89a Binary files /dev/null and b/event-driven-architecture/etc/eda-architecture-diagram.png differ diff --git a/event-driven-architecture/pom.xml b/event-driven-architecture/pom.xml index eac1961709c1..8a9fc27876eb 100644 --- a/event-driven-architecture/pom.xml +++ b/event-driven-architecture/pom.xml @@ -34,6 +34,14 @@ event-driven-architecture + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java index c4aaa6bc3611..3b2ea6ae24b3 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/App.java @@ -61,5 +61,4 @@ public static void main(String[] args) { dispatcher.dispatch(new UserCreatedEvent(user)); dispatcher.dispatch(new UserUpdatedEvent(user)); } - } diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/AbstractEvent.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/AbstractEvent.java index 9a8834ff91ff..0add9446244e 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/AbstractEvent.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/AbstractEvent.java @@ -30,10 +30,12 @@ /** * The {@link AbstractEvent} class serves as a base class for defining custom events happening with * your system. In this example we have two types of events defined. + * *

      - *
    • {@link UserCreatedEvent} - used when a user is created
    • - *
    • {@link UserUpdatedEvent} - used when a user is updated
    • + *
    • {@link UserCreatedEvent} - used when a user is created + *
    • {@link UserUpdatedEvent} - used when a user is updated *
    + * * Events can be distinguished using the {@link #getType() getType} method. */ public abstract class AbstractEvent implements Event { @@ -47,4 +49,4 @@ public abstract class AbstractEvent implements Event { public Class getType() { return getClass(); } -} \ No newline at end of file +} diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java index 245f79b4cbca..06cbe3122858 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserCreatedEvent.java @@ -29,9 +29,9 @@ import lombok.RequiredArgsConstructor; /** - * The {@link UserCreatedEvent} should be dispatched whenever a user has been created. - * This class can be extended to contain details about the user has been created. - * In this example, the entire {@link User} object is passed on as data with the event. + * The {@link UserCreatedEvent} should be dispatched whenever a user has been created. This class + * can be extended to contain details about the user has been created. In this example, the entire + * {@link User} object is passed on as data with the event. */ @RequiredArgsConstructor @Getter diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java index 6fa1832cb4f9..5fd0e4a7d2ee 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/event/UserUpdatedEvent.java @@ -29,9 +29,9 @@ import lombok.RequiredArgsConstructor; /** - * The {@link UserUpdatedEvent} should be dispatched whenever a user has been updated. - * This class can be extended to contain details about the user has been updated. - * In this example, the entire {@link User} object is passed on as data with the event. + * The {@link UserUpdatedEvent} should be dispatched whenever a user has been updated. This class + * can be extended to contain details about the user has been updated. In this example, the entire + * {@link User} object is passed on as data with the event. */ @RequiredArgsConstructor @Getter diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java index 1d0fab9d5da7..da29f770eeea 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/framework/EventDispatcher.java @@ -43,12 +43,9 @@ public EventDispatcher() { * Links an {@link Event} to a specific {@link Handler}. * * @param eventType The {@link Event} to be registered - * @param handler The {@link Handler} that will be handling the {@link Event} + * @param handler The {@link Handler} that will be handling the {@link Event} */ - public void registerHandler( - Class eventType, - Handler handler - ) { + public void registerHandler(Class eventType, Handler handler) { handlers.put(eventType, handler); } @@ -64,5 +61,4 @@ public void dispatch(E event) { handler.onEvent(event); } } - } diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java index 22d5fdaa4a3f..25f2354b5dd2 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserCreatedEventHandler.java @@ -28,9 +28,7 @@ import com.iluwatar.eda.framework.Handler; import lombok.extern.slf4j.Slf4j; -/** - * Handles the {@link UserCreatedEvent} message. - */ +/** Handles the {@link UserCreatedEvent} message. */ @Slf4j public class UserCreatedEventHandler implements Handler { @@ -38,5 +36,4 @@ public class UserCreatedEventHandler implements Handler { public void onEvent(UserCreatedEvent event) { LOGGER.info("User '{}' has been Created!", event.getUser().username()); } - } diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java index 016b525707b4..9947af48403c 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/handler/UserUpdatedEventHandler.java @@ -28,9 +28,7 @@ import com.iluwatar.eda.framework.Handler; import lombok.extern.slf4j.Slf4j; -/** - * Handles the {@link UserUpdatedEvent} message. - */ +/** Handles the {@link UserUpdatedEvent} message. */ @Slf4j public class UserUpdatedEventHandler implements Handler { diff --git a/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java b/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java index b255cfa98356..2b9e17693f14 100644 --- a/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java +++ b/event-driven-architecture/src/main/java/com/iluwatar/eda/model/User.java @@ -26,8 +26,6 @@ import com.iluwatar.eda.event.UserCreatedEvent; import com.iluwatar.eda.event.UserUpdatedEvent; -import lombok.Getter; -import lombok.RequiredArgsConstructor; /** * This {@link User} class is a basic pojo used to demonstrate user data sent along with the {@link diff --git a/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java b/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java index 17f3bddaa4a2..52e08eb9a76a 100644 --- a/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java +++ b/event-driven-architecture/src/test/java/com/iluwatar/eda/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.eda; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Event Driven Architecture example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Event Driven Architecture 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#main(String[])} - * throws an exception. + * + *

    Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java b/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java index 88ba805a4a20..d3254255e714 100644 --- a/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java +++ b/event-driven-architecture/src/test/java/com/iluwatar/eda/event/UserCreatedEventTest.java @@ -29,9 +29,7 @@ import com.iluwatar.eda.model.User; import org.junit.jupiter.api.Test; -/** - * {@link UserCreatedEventTest} tests and verifies {@link AbstractEvent} behaviour. - */ +/** {@link UserCreatedEventTest} tests and verifies {@link AbstractEvent} behaviour. */ class UserCreatedEventTest { /** diff --git a/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java b/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java index b4cae33ab1a9..f92fde927109 100644 --- a/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java +++ b/event-driven-architecture/src/test/java/com/iluwatar/eda/framework/EventDispatcherTest.java @@ -34,9 +34,7 @@ import com.iluwatar.eda.model.User; import org.junit.jupiter.api.Test; -/** - * Event Dispatcher unit tests to assert and verify correct event dispatcher behaviour - */ +/** Event Dispatcher unit tests to assert and verify correct event dispatcher behaviour */ class EventDispatcherTest { /** @@ -57,15 +55,14 @@ void testEventDriverPattern() { var userCreatedEvent = new UserCreatedEvent(user); var userUpdatedEvent = new UserUpdatedEvent(user); - //fire a userCreatedEvent and verify that userCreatedEventHandler has been invoked. + // fire a userCreatedEvent and verify that userCreatedEventHandler has been invoked. dispatcher.dispatch(userCreatedEvent); verify(userCreatedEventHandler).onEvent(userCreatedEvent); verify(dispatcher).dispatch(userCreatedEvent); - //fire a userCreatedEvent and verify that userUpdatedEventHandler has been invoked. + // fire a userCreatedEvent and verify that userUpdatedEventHandler has been invoked. dispatcher.dispatch(userUpdatedEvent); verify(userUpdatedEventHandler).onEvent(userUpdatedEvent); verify(dispatcher).dispatch(userUpdatedEvent); } - } diff --git a/event-queue/README.md b/event-queue/README.md index 8b2991b1daf0..08d807937ead 100644 --- a/event-queue/README.md +++ b/event-queue/README.md @@ -35,6 +35,10 @@ Wikipedia says > Message queues (also known as event queues) implement an asynchronous communication pattern between two or more processes/threads whereby the sending and receiving party do not need to interact with the queue at the same time. +Sequence diagram + +![Event Queue sequence diagram](./etc/event-queue-sequence-diagram.png) + ## Programmatic Example of Event Queue Pattern in Java This example demonstrates an application using an event queue system to handle audio playback asynchronously. diff --git a/event-queue/etc/event-queue-sequence-diagram.png b/event-queue/etc/event-queue-sequence-diagram.png new file mode 100644 index 000000000000..26e01a79e18e Binary files /dev/null and b/event-queue/etc/event-queue-sequence-diagram.png differ diff --git a/event-queue/pom.xml b/event-queue/pom.xml index 3f2d1de623d6..4b7566df445b 100644 --- a/event-queue/pom.xml +++ b/event-queue/pom.xml @@ -34,6 +34,14 @@ event-queue + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/App.java b/event-queue/src/main/java/com/iluwatar/event/queue/App.java index 283884fea741..3ea957a5a9be 100644 --- a/event-queue/src/main/java/com/iluwatar/event/queue/App.java +++ b/event-queue/src/main/java/com/iluwatar/event/queue/App.java @@ -47,11 +47,11 @@ public class App { * Program entry point. * * @param args command line args - * @throws IOException when there is a problem with the audio file loading + * @throws IOException when there is a problem with the audio file loading * @throws UnsupportedAudioFileException when the loaded audio file is unsupported */ - public static void main(String[] args) throws UnsupportedAudioFileException, IOException, - InterruptedException { + public static void main(String[] args) + throws UnsupportedAudioFileException, IOException, InterruptedException { var audio = Audio.getInstance(); audio.playSound(audio.getAudioStream("./etc/Bass-Drum-1.wav"), -10.0f); audio.playSound(audio.getAudioStream("./etc/Closed-Hi-Hat-1.wav"), -8.0f); diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java b/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java index e183229477fc..02bee71b86e2 100644 --- a/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java +++ b/event-queue/src/main/java/com/iluwatar/event/queue/Audio.java @@ -33,10 +33,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * This class implements the Event Queue pattern. - * - */ +/** This class implements the Event Queue pattern. */ @Slf4j public class Audio { private static final Audio INSTANCE = new Audio(); @@ -49,21 +46,16 @@ public class Audio { private volatile Thread updateThread = null; - @Getter - private final PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING]; + @Getter private final PlayMessage[] pendingAudio = new PlayMessage[MAX_PENDING]; // Visible only for testing purposes - Audio() { - - } + Audio() {} public static Audio getInstance() { return INSTANCE; } - /** - * This method stops the Update Method's thread and waits till service stops. - */ + /** This method stops the Update Method's thread and waits till service stops. */ public synchronized void stopService() throws InterruptedException { if (updateThread != null) { updateThread.interrupt(); @@ -82,23 +74,23 @@ public synchronized boolean isServiceRunning() { } /** - * Starts the thread for the Update Method pattern if it was not started previously. Also, when the - * thread is ready initializes the indexes of the queue + * Starts the thread for the Update Method pattern if it was not started previously. Also, when + * the thread is ready initializes the indexes of the queue */ public void init() { if (updateThread == null) { - updateThread = new Thread(() -> { - while (!Thread.currentThread().isInterrupted()) { - update(); - } - }); + updateThread = + new Thread( + () -> { + while (!Thread.currentThread().isInterrupted()) { + update(); + } + }); } startThread(); } - /** - * This is a synchronized thread starter. - */ + /** This is a synchronized thread starter. */ private synchronized void startThread() { if (!updateThread.isAlive()) { updateThread.start(); @@ -130,9 +122,7 @@ public void playSound(AudioInputStream stream, float volume) { tailIndex = (tailIndex + 1) % MAX_PENDING; } - /** - * This method uses the Update Method pattern. It takes the audio from the queue and plays it - */ + /** This method uses the Update Method pattern. It takes the audio from the queue and plays it */ private void update() { // If there are no pending requests, do nothing. if (headIndex == tailIndex) { @@ -159,7 +149,7 @@ private void update() { * @param filePath is the path of the audio file * @return AudioInputStream * @throws UnsupportedAudioFileException when the audio file is not supported - * @throws IOException when the file is not readable + * @throws IOException when the file is not readable */ public AudioInputStream getAudioStream(String filePath) throws UnsupportedAudioFileException, IOException { diff --git a/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java b/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java index 0769ee36db8a..60f328d79bd0 100644 --- a/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java +++ b/event-queue/src/main/java/com/iluwatar/event/queue/PlayMessage.java @@ -29,17 +29,12 @@ import lombok.Getter; import lombok.Setter; -/** - * The Event Queue's queue will store the instances of this class. - * - */ +/** The Event Queue's queue will store the instances of this class. */ @Getter @AllArgsConstructor public class PlayMessage { private final AudioInputStream stream; - @Setter - private float volume; - + @Setter private float volume; } diff --git a/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java b/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java index 5112be9da49e..4f2abbdac895 100644 --- a/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java +++ b/event-queue/src/test/java/com/iluwatar/event/queue/AudioTest.java @@ -32,11 +32,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -/** - * Testing the Audio service of the Queue - * - */ +/** Testing the Audio service of the Queue */ class AudioTest { private Audio audio; @@ -48,7 +44,8 @@ void createAudioInstance() { /** * Test here that the playSound method works correctly - * @throws UnsupportedAudioFileException when the audio file is not supported + * + * @throws UnsupportedAudioFileException when the audio file is not supported * @throws IOException when the file is not readable * @throws InterruptedException when the test is interrupted externally */ @@ -67,7 +64,8 @@ void testPlaySound() throws UnsupportedAudioFileException, IOException, Interrup /** * Test here that the Queue - * @throws UnsupportedAudioFileException when the audio file is not supported + * + * @throws UnsupportedAudioFileException when the audio file is not supported * @throws IOException when the file is not readable * @throws InterruptedException when the test is interrupted externally */ @@ -86,5 +84,4 @@ void testQueue() throws UnsupportedAudioFileException, IOException, InterruptedE // test that service is finished assertFalse(audio.isServiceRunning()); } - } diff --git a/event-sourcing/README.md b/event-sourcing/README.md index 17dc447734af..4de1c2dd5025 100644 --- a/event-sourcing/README.md +++ b/event-sourcing/README.md @@ -41,6 +41,10 @@ In plain words > The Event Sourcing pattern defines an approach to handling operations on data that's driven by a sequence of events, each of which is recorded in an append-only store. Application code sends a series of events that imperatively describe each action that has occurred on the data to the event store, where they're persisted. Each event represents a set of changes to the data (such as AddedItemToOrder). +Architecture diagram + +![Event Sourcing Architecture Diagram](./etc/event-sourcing-architecture-diagram.png) + ## Programmatic Example of Event Sourcing Pattern in Java In the programmatic example we transfer some money between bank accounts. diff --git a/event-sourcing/etc/event-sourcing-architecture-diagram.png b/event-sourcing/etc/event-sourcing-architecture-diagram.png new file mode 100644 index 000000000000..2ad8b3531e56 Binary files /dev/null and b/event-sourcing/etc/event-sourcing-architecture-diagram.png differ diff --git a/event-sourcing/pom.xml b/event-sourcing/pom.xml index d3dd2c747477..5660054d8195 100644 --- a/event-sourcing/pom.xml +++ b/event-sourcing/pom.xml @@ -34,6 +34,14 @@ event-sourcing + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -42,12 +50,12 @@ com.fasterxml.jackson.core jackson-core - 2.17.2 + 2.19.0 com.fasterxml.jackson.core jackson-databind - 2.17.2 + 2.18.3 diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java index 20c4151b5821..a625e9ace9d3 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/app/App.java @@ -53,13 +53,10 @@ @Slf4j public class App { - /** - * The constant ACCOUNT OF DAENERYS. - */ + /** The constant ACCOUNT OF DAENERYS. */ public static final int ACCOUNT_OF_DAENERYS = 1; - /** - * The constant ACCOUNT OF JON. - */ + + /** The constant ACCOUNT OF JON. */ public static final int ACCOUNT_OF_JON = 2; /** @@ -76,23 +73,24 @@ public static void main(String[] args) { LOGGER.info("Creating the accounts............"); - eventProcessor.process(new AccountCreateEvent( - 0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); + eventProcessor.process( + new AccountCreateEvent(0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); - eventProcessor.process(new AccountCreateEvent( - 1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); + eventProcessor.process( + new AccountCreateEvent(1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); LOGGER.info("Do some money operations............"); - eventProcessor.process(new MoneyDepositEvent( - 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); + eventProcessor.process( + new MoneyDepositEvent( + 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); - eventProcessor.process(new MoneyDepositEvent( - 3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); + eventProcessor.process( + new MoneyDepositEvent(3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); - eventProcessor.process(new MoneyTransferEvent( - 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, - ACCOUNT_OF_JON)); + eventProcessor.process( + new MoneyTransferEvent( + 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, ACCOUNT_OF_JON)); LOGGER.info("...............State:............"); LOGGER.info(AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS).toString()); diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java index 177348be86a3..89f4219d740b 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/domain/Account.java @@ -68,9 +68,13 @@ public Account copy() { @Override public String toString() { return "Account{" - + "accountNo=" + accountNo - + ", owner='" + owner + '\'' - + ", money=" + money + + "accountNo=" + + accountNo + + ", owner='" + + owner + + '\'' + + ", money=" + + money + '}'; } @@ -111,7 +115,6 @@ public void handleEvent(MoneyDepositEvent moneyDepositEvent) { handleDeposit(moneyDepositEvent.getMoney(), moneyDepositEvent.isRealTime()); } - /** * Handles the AccountCreateEvent. * @@ -141,6 +144,4 @@ public void handleTransferFromEvent(MoneyTransferEvent moneyTransferEvent) { public void handleTransferToEvent(MoneyTransferEvent moneyTransferEvent) { handleDeposit(moneyTransferEvent.getMoney(), moneyTransferEvent.isRealTime()); } - - } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java index f3f4e78e6ab3..087752cbf9c6 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/AccountCreateEvent.java @@ -46,15 +46,17 @@ public class AccountCreateEvent extends DomainEvent { /** * Instantiates a new Account created event. * - * @param sequenceId the sequence id + * @param sequenceId the sequence id * @param createdTime the created time - * @param accountNo the account no - * @param owner the owner + * @param accountNo the account no + * @param owner the owner */ @JsonCreator - public AccountCreateEvent(@JsonProperty("sequenceId") long sequenceId, + public AccountCreateEvent( + @JsonProperty("sequenceId") long sequenceId, @JsonProperty("createdTime") long createdTime, - @JsonProperty("accountNo") int accountNo, @JsonProperty("owner") String owner) { + @JsonProperty("accountNo") int accountNo, + @JsonProperty("owner") String owner) { super(sequenceId, createdTime, "AccountCreateEvent"); this.accountNo = accountNo; this.owner = owner; diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java index 139ddd576598..f39ebda43eb0 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/DomainEvent.java @@ -44,9 +44,6 @@ public abstract class DomainEvent implements Serializable { private final String eventClassName; private boolean realTime = true; - /** - * Process. - */ + /** Process. */ public abstract void process(); - } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java index 07e88bc0ea28..4f80ec6b6dfc 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyDepositEvent.java @@ -47,15 +47,17 @@ public class MoneyDepositEvent extends DomainEvent { /** * Instantiates a new Money deposit event. * - * @param sequenceId the sequence id + * @param sequenceId the sequence id * @param createdTime the created time - * @param accountNo the account no - * @param money the money + * @param accountNo the account no + * @param money the money */ @JsonCreator - public MoneyDepositEvent(@JsonProperty("sequenceId") long sequenceId, + public MoneyDepositEvent( + @JsonProperty("sequenceId") long sequenceId, @JsonProperty("createdTime") long createdTime, - @JsonProperty("accountNo") int accountNo, @JsonProperty("money") BigDecimal money) { + @JsonProperty("accountNo") int accountNo, + @JsonProperty("money") BigDecimal money) { super(sequenceId, createdTime, "MoneyDepositEvent"); this.money = money; this.accountNo = accountNo; @@ -63,8 +65,9 @@ public MoneyDepositEvent(@JsonProperty("sequenceId") long sequenceId, @Override public void process() { - var account = Optional.ofNullable(AccountAggregate.getAccount(accountNo)) - .orElseThrow(() -> new RuntimeException("Account not found")); + var account = + Optional.ofNullable(AccountAggregate.getAccount(accountNo)) + .orElseThrow(() -> new RuntimeException("Account not found")); account.handleEvent(this); } } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java index 3b4fa1ed1bb0..e6e257dded04 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/event/MoneyTransferEvent.java @@ -48,16 +48,18 @@ public class MoneyTransferEvent extends DomainEvent { /** * Instantiates a new Money transfer event. * - * @param sequenceId the sequence id - * @param createdTime the created time - * @param money the money + * @param sequenceId the sequence id + * @param createdTime the created time + * @param money the money * @param accountNoFrom the account no from - * @param accountNoTo the account no to + * @param accountNoTo the account no to */ @JsonCreator - public MoneyTransferEvent(@JsonProperty("sequenceId") long sequenceId, + public MoneyTransferEvent( + @JsonProperty("sequenceId") long sequenceId, @JsonProperty("createdTime") long createdTime, - @JsonProperty("money") BigDecimal money, @JsonProperty("accountNoFrom") int accountNoFrom, + @JsonProperty("money") BigDecimal money, + @JsonProperty("accountNoFrom") int accountNoFrom, @JsonProperty("accountNoTo") int accountNoTo) { super(sequenceId, createdTime, "MoneyTransferEvent"); this.money = money; @@ -67,10 +69,12 @@ public MoneyTransferEvent(@JsonProperty("sequenceId") long sequenceId, @Override public void process() { - var accountFrom = Optional.ofNullable(AccountAggregate.getAccount(accountNoFrom)) - .orElseThrow(() -> new RuntimeException("Account not found " + accountNoFrom)); - var accountTo = Optional.ofNullable(AccountAggregate.getAccount(accountNoTo)) - .orElseThrow(() -> new RuntimeException("Account not found " + accountNoTo)); + var accountFrom = + Optional.ofNullable(AccountAggregate.getAccount(accountNoFrom)) + .orElseThrow(() -> new RuntimeException("Account not found " + accountNoFrom)); + var accountTo = + Optional.ofNullable(AccountAggregate.getAccount(accountNoTo)) + .orElseThrow(() -> new RuntimeException("Account not found " + accountNoTo)); accountFrom.handleTransferFromEvent(this); accountTo.handleTransferToEvent(this); } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java index cdfe44e336b2..6b0658b40a43 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/DomainEventProcessor.java @@ -50,16 +50,12 @@ public void process(DomainEvent domainEvent) { eventJournal.write(domainEvent); } - /** - * Reset. - */ + /** Reset. */ public void reset() { eventJournal.reset(); } - /** - * Recover. - */ + /** Recover. */ public void recover() { DomainEvent domainEvent; while ((domainEvent = eventJournal.readNext()) != null) { diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java index 5c78da1d8125..3b4cdfd3ec3e 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/EventJournal.java @@ -28,9 +28,7 @@ import java.io.File; import lombok.extern.slf4j.Slf4j; -/** - * Base class for Journaling implementations. - */ +/** Base class for Journaling implementations. */ @Slf4j public abstract class EventJournal { @@ -43,9 +41,7 @@ public abstract class EventJournal { */ abstract void write(DomainEvent domainEvent); - /** - * Reset. - */ + /** Reset. */ void reset() { if (file.delete()) { LOGGER.info("File cleared successfully............"); diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java index cfde566dc2fb..106dbf95e93a 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/processor/JsonFileJournal.java @@ -53,14 +53,13 @@ public class JsonFileJournal extends EventJournal { private final List events = new ArrayList<>(); private int index = 0; - /** - * Instantiates a new Json file journal. - */ + /** Instantiates a new Json file journal. */ public JsonFileJournal() { file = new File("Journal.json"); if (file.exists()) { - try (var input = new BufferedReader( - new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { + try (var input = + new BufferedReader( + new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) { String line; while ((line = input.readLine()) != null) { events.add(line); @@ -73,7 +72,6 @@ public JsonFileJournal() { } } - /** * Write. * @@ -82,8 +80,9 @@ public JsonFileJournal() { @Override public void write(DomainEvent domainEvent) { var mapper = new ObjectMapper(); - try (var output = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8))) { + try (var output = + new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8))) { var eventString = mapper.writeValueAsString(domainEvent); output.write(eventString + "\r\n"); } catch (IOException e) { @@ -91,7 +90,6 @@ public void write(DomainEvent domainEvent) { } } - /** * Read the next domain event. * @@ -109,12 +107,13 @@ public DomainEvent readNext() { try { var jsonElement = mapper.readTree(event); var eventClassName = jsonElement.get("eventClassName").asText(); - domainEvent = switch (eventClassName) { - case "AccountCreateEvent" -> mapper.treeToValue(jsonElement, AccountCreateEvent.class); - case "MoneyDepositEvent" -> mapper.treeToValue(jsonElement, MoneyDepositEvent.class); - case "MoneyTransferEvent" -> mapper.treeToValue(jsonElement, MoneyTransferEvent.class); - default -> throw new RuntimeException("Journal Event not recognized"); - }; + domainEvent = + switch (eventClassName) { + case "AccountCreateEvent" -> mapper.treeToValue(jsonElement, AccountCreateEvent.class); + case "MoneyDepositEvent" -> mapper.treeToValue(jsonElement, MoneyDepositEvent.class); + case "MoneyTransferEvent" -> mapper.treeToValue(jsonElement, MoneyTransferEvent.class); + default -> throw new RuntimeException("Journal Event not recognized"); + }; } catch (JsonProcessingException jsonProcessingException) { throw new RuntimeException("Failed to convert JSON"); } diff --git a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java index 4948fcd30368..80253036f223 100644 --- a/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java +++ b/event-sourcing/src/main/java/com/iluwatar/event/sourcing/state/AccountAggregate.java @@ -38,8 +38,7 @@ public class AccountAggregate { private static Map accounts = new HashMap<>(); - private AccountAggregate() { - } + private AccountAggregate() {} /** * Put account. @@ -57,15 +56,10 @@ public static void putAccount(Account account) { * @return the copy of the account or null if not found */ public static Account getAccount(int accountNo) { - return Optional.of(accountNo) - .map(accounts::get) - .map(Account::copy) - .orElse(null); + return Optional.of(accountNo).map(accounts::get).map(Account::copy).orElse(null); } - /** - * Reset state. - */ + /** Reset state. */ public static void resetState() { accounts = new HashMap<>(); } diff --git a/event-sourcing/src/test/java/IntegrationTest.java b/event-sourcing/src/test/java/IntegrationTest.java index c737bd0da43d..89c7a77fe91e 100644 --- a/event-sourcing/src/test/java/IntegrationTest.java +++ b/event-sourcing/src/test/java/IntegrationTest.java @@ -40,46 +40,41 @@ /** * Integration Test for Event-Sourcing state recovery - *

    - * Created by Serdar Hamzaogullari on 19.08.2017. + * + *

    Created by Serdar Hamzaogullari on 19.08.2017. */ class IntegrationTest { - /** - * The Domain event processor. - */ + /** The Domain event processor. */ private DomainEventProcessor eventProcessor; - /** - * Initialize. - */ + /** Initialize. */ @BeforeEach void initialize() { eventProcessor = new DomainEventProcessor(new JsonFileJournal()); } - /** - * Test state recovery. - */ + /** Test state recovery. */ @Test void testStateRecovery() { eventProcessor.reset(); - eventProcessor.process(new AccountCreateEvent( - 0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); + eventProcessor.process( + new AccountCreateEvent(0, new Date().getTime(), ACCOUNT_OF_DAENERYS, "Daenerys Targaryen")); - eventProcessor.process(new AccountCreateEvent( - 1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); + eventProcessor.process( + new AccountCreateEvent(1, new Date().getTime(), ACCOUNT_OF_JON, "Jon Snow")); - eventProcessor.process(new MoneyDepositEvent( - 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); + eventProcessor.process( + new MoneyDepositEvent( + 2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000"))); - eventProcessor.process(new MoneyDepositEvent( - 3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); + eventProcessor.process( + new MoneyDepositEvent(3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100"))); - eventProcessor.process(new MoneyTransferEvent( - 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, - ACCOUNT_OF_JON)); + eventProcessor.process( + new MoneyTransferEvent( + 4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS, ACCOUNT_OF_JON)); var accountOfDaenerysBeforeShotDown = AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS); var accountOfJonBeforeShotDown = AccountAggregate.getAccount(ACCOUNT_OF_JON); @@ -92,9 +87,8 @@ void testStateRecovery() { var accountOfDaenerysAfterShotDown = AccountAggregate.getAccount(ACCOUNT_OF_DAENERYS); var accountOfJonAfterShotDown = AccountAggregate.getAccount(ACCOUNT_OF_JON); - assertEquals(accountOfDaenerysBeforeShotDown.getMoney(), - accountOfDaenerysAfterShotDown.getMoney()); + assertEquals( + accountOfDaenerysBeforeShotDown.getMoney(), accountOfDaenerysAfterShotDown.getMoney()); assertEquals(accountOfJonBeforeShotDown.getMoney(), accountOfJonAfterShotDown.getMoney()); } - } diff --git a/execute-around/README.md b/execute-around/README.md index aea3ae9e733f..9587674ded1e 100644 --- a/execute-around/README.md +++ b/execute-around/README.md @@ -39,6 +39,10 @@ In plain words > Basically it's the pattern where you write a method to do things which are always required, e.g. resource allocation and clean-up, and make the caller pass in "what we want to do with the resource". +Flowchart + +![Execute Around flowchart](./etc/execute-around-flowchart.png) + ## Programmatic Example of Execute Around Pattern in Java The Execute Around Pattern is a design pattern that is widely used in Java programming to manage resource allocation and deallocation. It ensures that important setup and cleanup operations are performed reliably around a core business operation. This pattern is particularly useful for resource management, such as handling files, databases, or network connections in Java applications. diff --git a/execute-around/etc/execute-around-flowchart.png b/execute-around/etc/execute-around-flowchart.png new file mode 100644 index 000000000000..3a9048585f62 Binary files /dev/null and b/execute-around/etc/execute-around-flowchart.png differ diff --git a/execute-around/pom.xml b/execute-around/pom.xml index d5e01d31cf0d..83a9372b4fe3 100644 --- a/execute-around/pom.xml +++ b/execute-around/pom.xml @@ -34,6 +34,14 @@ execute-around + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/App.java b/execute-around/src/main/java/com/iluwatar/execute/around/App.java index 9291fd0ea1d1..9dc88fbfdad9 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/App.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/App.java @@ -30,20 +30,18 @@ import lombok.extern.slf4j.Slf4j; /** - * The Execute Around idiom specifies executable code before and after a method. Typically, - * the idiom is used when the API has methods to be executed in pairs, such as resource + * The Execute Around idiom specifies executable code before and after a method. Typically, the + * idiom is used when the API has methods to be executed in pairs, such as resource * allocation/deallocation or lock acquisition/release. * - *

    In this example, we have {@link SimpleFileWriter} class that opens and closes the file for - * the user. The user specifies only what to do with the file by providing the {@link - * FileWriterAction} implementation. + *

    In this example, we have {@link SimpleFileWriter} class that opens and closes the file for the + * user. The user specifies only what to do with the file by providing the {@link FileWriterAction} + * implementation. */ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) throws IOException { // create the file writer and execute the custom action diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/FileWriterAction.java b/execute-around/src/main/java/com/iluwatar/execute/around/FileWriterAction.java index 5585bc4fac45..0627177a31f1 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/FileWriterAction.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/FileWriterAction.java @@ -27,12 +27,9 @@ import java.io.FileWriter; import java.io.IOException; -/** - * Interface for specifying what to do with the file resource. - */ +/** Interface for specifying what to do with the file resource. */ @FunctionalInterface public interface FileWriterAction { void writeFile(FileWriter writer) throws IOException; - } diff --git a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java index 66c844aa4b77..d3bd54d4ec6b 100644 --- a/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java +++ b/execute-around/src/main/java/com/iluwatar/execute/around/SimpleFileWriter.java @@ -35,9 +35,7 @@ @Slf4j public class SimpleFileWriter { - /** - * Constructor. - */ + /** Constructor. */ public SimpleFileWriter(String filename, FileWriterAction action) throws IOException { LOGGER.info("Opening the file"); try (var writer = new FileWriter(filename)) { diff --git a/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java b/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java index 8df0c9939c20..1e06beb12d3b 100644 --- a/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java +++ b/execute-around/src/test/java/com/iluwatar/execute/around/AppTest.java @@ -31,14 +31,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests execute-around example. - */ +/** Tests execute-around example. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } @BeforeEach diff --git a/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java b/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java index 6c0dfcd54ba0..60c4ecb6bdba 100644 --- a/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java +++ b/execute-around/src/test/java/com/iluwatar/execute/around/SimpleFileWriterTest.java @@ -38,15 +38,11 @@ import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport; import org.junit.rules.TemporaryFolder; -/** - * SimpleFileWriterTest - * - */ +/** SimpleFileWriterTest */ @EnableRuleMigrationSupport class SimpleFileWriterTest { - @Rule - public final TemporaryFolder testFolder = new TemporaryFolder(); + @Rule public final TemporaryFolder testFolder = new TemporaryFolder(); @Test void testWriterNotNull() throws Exception { @@ -74,12 +70,19 @@ void testContentsAreWrittenToFile() throws Exception { assertTrue(Files.lines(temporaryFile.toPath()).allMatch(testMessage::equals)); } - @Test @SneakyThrows void testRipplesIoExceptionOccurredWhileWriting() { var message = "Some error"; final var temporaryFile = this.testFolder.newFile(); - assertThrows(IOException.class, () -> new SimpleFileWriter(temporaryFile.getPath(), writer -> {throw new IOException("error");}), message); + assertThrows( + IOException.class, + () -> + new SimpleFileWriter( + temporaryFile.getPath(), + writer -> { + throw new IOException("error"); + }), + message); } -} \ No newline at end of file +} diff --git a/extension-objects/README.md b/extension-objects/README.md index 058fce65402c..82704008dca6 100644 --- a/extension-objects/README.md +++ b/extension-objects/README.md @@ -33,6 +33,10 @@ Wikipedia says > In object-oriented computer programming, an extension objects pattern is a design pattern added to an object after the original object was compiled. The modified object is often a class, a prototype or a type. Extension object patterns are features of some object-oriented programming languages. There is no syntactic difference between calling an extension method and calling a method declared in the type definition. +Sequence diagram + +![Extension Objects sequence diagram](./etc/extension-objects-sequence-diagram.png) + ## Programmatic Example of Extension Objects Pattern in Java The Extension Objects pattern allows for the flexible extension of an object's behavior without modifying its structure, by attaching additional objects that can dynamically add new functionality. @@ -143,10 +147,6 @@ This produces the following console output. This example demonstrates how the Extension Objects pattern allows for the flexible extension of an object's behavior without modifying its structure. -## Detailed Explanation of Extension Objects Pattern with Real-World Examples - -![Extension_objects](./etc/extension_obj.png "Extension objects") - ## When to Use the Extension Objects Pattern in Java This pattern is applicable in scenarios where an object's functionality needs to be extended at runtime, avoiding the complications of subclassing. It's particularly useful in systems where object capabilities need to be augmented post-deployment, or where the capabilities might vary significantly across instances. diff --git a/extension-objects/etc/extension-objects-sequence-diagram.png b/extension-objects/etc/extension-objects-sequence-diagram.png new file mode 100644 index 000000000000..46850163ab9b Binary files /dev/null and b/extension-objects/etc/extension-objects-sequence-diagram.png differ diff --git a/extension-objects/pom.xml b/extension-objects/pom.xml index 4e3d1e981d84..92297162fed4 100644 --- a/extension-objects/pom.xml +++ b/extension-objects/pom.xml @@ -34,6 +34,14 @@ 4.0.0 extension-objects + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/extension-objects/src/main/java/App.java b/extension-objects/src/main/java/App.java index dd67b2e1e4b1..db48032f1499 100644 --- a/extension-objects/src/main/java/App.java +++ b/extension-objects/src/main/java/App.java @@ -46,16 +46,15 @@ public class App { */ public static void main(String[] args) { - //Create 3 different units + // Create 3 different units var soldierUnit = new SoldierUnit("SoldierUnit1"); var sergeantUnit = new SergeantUnit("SergeantUnit1"); var commanderUnit = new CommanderUnit("CommanderUnit1"); - //check for each unit to have an extension + // check for each unit to have an extension checkExtensionsForUnit(soldierUnit); checkExtensionsForUnit(sergeantUnit); checkExtensionsForUnit(commanderUnit); - } private static void checkExtensionsForUnit(Unit unit) { diff --git a/extension-objects/src/main/java/abstractextensions/CommanderExtension.java b/extension-objects/src/main/java/abstractextensions/CommanderExtension.java index d366069b1939..bcad9db942c8 100644 --- a/extension-objects/src/main/java/abstractextensions/CommanderExtension.java +++ b/extension-objects/src/main/java/abstractextensions/CommanderExtension.java @@ -24,9 +24,7 @@ */ package abstractextensions; -/** - * Interface with their method. - */ +/** Interface with their method. */ public interface CommanderExtension extends UnitExtension { void commanderReady(); diff --git a/extension-objects/src/main/java/abstractextensions/SergeantExtension.java b/extension-objects/src/main/java/abstractextensions/SergeantExtension.java index 0cb04dc7034d..7eea21d0cb3f 100644 --- a/extension-objects/src/main/java/abstractextensions/SergeantExtension.java +++ b/extension-objects/src/main/java/abstractextensions/SergeantExtension.java @@ -24,9 +24,7 @@ */ package abstractextensions; -/** - * Interface with their method. - */ +/** Interface with their method. */ public interface SergeantExtension extends UnitExtension { void sergeantReady(); diff --git a/extension-objects/src/main/java/abstractextensions/SoldierExtension.java b/extension-objects/src/main/java/abstractextensions/SoldierExtension.java index 98e182de3ee7..579e3c1d0497 100644 --- a/extension-objects/src/main/java/abstractextensions/SoldierExtension.java +++ b/extension-objects/src/main/java/abstractextensions/SoldierExtension.java @@ -24,9 +24,7 @@ */ package abstractextensions; -/** - * Interface with their method. - */ +/** Interface with their method. */ public interface SoldierExtension extends UnitExtension { void soldierReady(); } diff --git a/extension-objects/src/main/java/abstractextensions/UnitExtension.java b/extension-objects/src/main/java/abstractextensions/UnitExtension.java index 8a82f0f0b916..d79868d547ba 100644 --- a/extension-objects/src/main/java/abstractextensions/UnitExtension.java +++ b/extension-objects/src/main/java/abstractextensions/UnitExtension.java @@ -24,8 +24,5 @@ */ package abstractextensions; -/** - * Other Extensions will extend this interface. - */ -public interface UnitExtension { -} +/** Other Extensions will extend this interface. */ +public interface UnitExtension {} diff --git a/extension-objects/src/main/java/concreteextensions/Commander.java b/extension-objects/src/main/java/concreteextensions/Commander.java index a2ffff94129a..0716d2e9bb25 100644 --- a/extension-objects/src/main/java/concreteextensions/Commander.java +++ b/extension-objects/src/main/java/concreteextensions/Commander.java @@ -25,14 +25,10 @@ package concreteextensions; import abstractextensions.CommanderExtension; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import units.CommanderUnit; -/** - * Class defining Commander. - */ +/** Class defining Commander. */ @Slf4j public record Commander(CommanderUnit unit) implements CommanderExtension { @@ -40,5 +36,4 @@ public record Commander(CommanderUnit unit) implements CommanderExtension { public void commanderReady() { LOGGER.info("[Commander] " + unit.getName() + " is ready!"); } - } diff --git a/extension-objects/src/main/java/concreteextensions/Sergeant.java b/extension-objects/src/main/java/concreteextensions/Sergeant.java index 716bd4a1a638..fb8f815c8c38 100644 --- a/extension-objects/src/main/java/concreteextensions/Sergeant.java +++ b/extension-objects/src/main/java/concreteextensions/Sergeant.java @@ -25,14 +25,10 @@ package concreteextensions; import abstractextensions.SergeantExtension; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import units.SergeantUnit; -/** - * Class defining Sergeant. - */ +/** Class defining Sergeant. */ @Slf4j public record Sergeant(SergeantUnit unit) implements SergeantExtension { @@ -40,5 +36,4 @@ public record Sergeant(SergeantUnit unit) implements SergeantExtension { public void sergeantReady() { LOGGER.info("[Sergeant] " + unit.getName() + " is ready!"); } - } diff --git a/extension-objects/src/main/java/concreteextensions/Soldier.java b/extension-objects/src/main/java/concreteextensions/Soldier.java index cbd5d2a1aec8..dd2338a5389f 100644 --- a/extension-objects/src/main/java/concreteextensions/Soldier.java +++ b/extension-objects/src/main/java/concreteextensions/Soldier.java @@ -25,14 +25,10 @@ package concreteextensions; import abstractextensions.SoldierExtension; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import units.SoldierUnit; -/** - * Class defining Soldier. - */ +/** Class defining Soldier. */ @Slf4j public record Soldier(SoldierUnit unit) implements SoldierExtension { @@ -40,5 +36,4 @@ public record Soldier(SoldierUnit unit) implements SoldierExtension { public void soldierReady() { LOGGER.info("[Soldier] " + unit.getName() + " is ready!"); } - } diff --git a/extension-objects/src/main/java/units/CommanderUnit.java b/extension-objects/src/main/java/units/CommanderUnit.java index cf62aaec84f1..94e92807f30d 100644 --- a/extension-objects/src/main/java/units/CommanderUnit.java +++ b/extension-objects/src/main/java/units/CommanderUnit.java @@ -28,9 +28,7 @@ import concreteextensions.Commander; import java.util.Optional; -/** - * Class defining CommanderUnit. - */ +/** Class defining CommanderUnit. */ public class CommanderUnit extends Unit { public CommanderUnit(String name) { diff --git a/extension-objects/src/main/java/units/SergeantUnit.java b/extension-objects/src/main/java/units/SergeantUnit.java index 7645a7495525..ae882d92943a 100644 --- a/extension-objects/src/main/java/units/SergeantUnit.java +++ b/extension-objects/src/main/java/units/SergeantUnit.java @@ -28,9 +28,7 @@ import concreteextensions.Sergeant; import java.util.Optional; -/** - * Class defining SergeantUnit. - */ +/** Class defining SergeantUnit. */ public class SergeantUnit extends Unit { public SergeantUnit(String name) { diff --git a/extension-objects/src/main/java/units/SoldierUnit.java b/extension-objects/src/main/java/units/SoldierUnit.java index e82e9fac421e..b1db8930e625 100644 --- a/extension-objects/src/main/java/units/SoldierUnit.java +++ b/extension-objects/src/main/java/units/SoldierUnit.java @@ -28,9 +28,7 @@ import concreteextensions.Soldier; import java.util.Optional; -/** - * Class defining SoldierUnit. - */ +/** Class defining SoldierUnit. */ public class SoldierUnit extends Unit { public SoldierUnit(String name) { diff --git a/extension-objects/src/main/java/units/Unit.java b/extension-objects/src/main/java/units/Unit.java index 1bb3a8dac7d7..1957771821b7 100644 --- a/extension-objects/src/main/java/units/Unit.java +++ b/extension-objects/src/main/java/units/Unit.java @@ -28,9 +28,7 @@ import lombok.Getter; import lombok.Setter; -/** - * Class defining Unit, other units will extend this class. - */ +/** Class defining Unit, other units will extend this class. */ @Setter @Getter public class Unit { diff --git a/extension-objects/src/test/java/AppTest.java b/extension-objects/src/test/java/AppTest.java index 92db1702a7d9..c80e472b4052 100644 --- a/extension-objects/src/test/java/AppTest.java +++ b/extension-objects/src/test/java/AppTest.java @@ -22,18 +22,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Created by Srdjan on 03-May-17. - */ +import org.junit.jupiter.api.Test; + +/** Created by Srdjan on 03-May-17. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/concreteextensions/CommanderTest.java b/extension-objects/src/test/java/concreteextensions/CommanderTest.java index 74b668d5edb3..5271d7ea7563 100644 --- a/extension-objects/src/test/java/concreteextensions/CommanderTest.java +++ b/extension-objects/src/test/java/concreteextensions/CommanderTest.java @@ -24,20 +24,18 @@ */ package concreteextensions; +import static org.junit.jupiter.api.Assertions.assertEquals; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; +import java.util.List; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; import units.CommanderUnit; -import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * CommanderTest - */ +/** CommanderTest */ class CommanderTest { @Test @@ -54,9 +52,8 @@ void shouldExecuteCommanderReady() { commander.commanderReady(); List logsList = listAppender.list; - assertEquals("[Commander] " + commander.unit().getName() + " is ready!", logsList.get(0) - .getMessage()); - assertEquals(Level.INFO, logsList.get(0) - .getLevel()); + assertEquals( + "[Commander] " + commander.unit().getName() + " is ready!", logsList.get(0).getMessage()); + assertEquals(Level.INFO, logsList.get(0).getLevel()); } } diff --git a/extension-objects/src/test/java/concreteextensions/SergeantTest.java b/extension-objects/src/test/java/concreteextensions/SergeantTest.java index 13f2f8812248..83b2cb46db5d 100644 --- a/extension-objects/src/test/java/concreteextensions/SergeantTest.java +++ b/extension-objects/src/test/java/concreteextensions/SergeantTest.java @@ -24,20 +24,18 @@ */ package concreteextensions; +import static org.junit.jupiter.api.Assertions.assertEquals; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; +import java.util.List; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; import units.SergeantUnit; -import java.util.List; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class SergeantTest { @Test @@ -54,10 +52,8 @@ void sergeantReady() { sergeant.sergeantReady(); List logsList = listAppender.list; - assertEquals("[Sergeant] " + sergeant.unit().getName() + " is ready!", logsList.get(0) - .getMessage()); - assertEquals(Level.INFO, logsList.get(0) - .getLevel()); + assertEquals( + "[Sergeant] " + sergeant.unit().getName() + " is ready!", logsList.get(0).getMessage()); + assertEquals(Level.INFO, logsList.get(0).getLevel()); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/concreteextensions/SoldierTest.java b/extension-objects/src/test/java/concreteextensions/SoldierTest.java index d5ebcca701d0..da3490c2bad9 100644 --- a/extension-objects/src/test/java/concreteextensions/SoldierTest.java +++ b/extension-objects/src/test/java/concreteextensions/SoldierTest.java @@ -24,21 +24,18 @@ */ package concreteextensions; +import static org.junit.jupiter.api.Assertions.assertEquals; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; +import java.util.List; import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; import units.SoldierUnit; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class SoldierTest { @Test @@ -55,10 +52,8 @@ void soldierReady() { soldier.soldierReady(); List logsList = listAppender.list; - assertEquals("[Soldier] " + soldier.unit().getName() + " is ready!", logsList.get(0) - .getMessage()); - assertEquals(Level.INFO, logsList.get(0) - .getLevel()); + assertEquals( + "[Soldier] " + soldier.unit().getName() + " is ready!", logsList.get(0).getMessage()); + assertEquals(Level.INFO, logsList.get(0).getLevel()); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/units/CommanderUnitTest.java b/extension-objects/src/test/java/units/CommanderUnitTest.java index 52e343d5c4ea..1b4793e66e71 100644 --- a/extension-objects/src/test/java/units/CommanderUnitTest.java +++ b/extension-objects/src/test/java/units/CommanderUnitTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class CommanderUnitTest { @Test @@ -42,5 +40,4 @@ void getUnitExtension() { assertNull(unit.getUnitExtension("SergeantExtension")); assertNotNull(unit.getUnitExtension("CommanderExtension")); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/units/SergeantUnitTest.java b/extension-objects/src/test/java/units/SergeantUnitTest.java index 59255f3b870b..b211483b15e9 100644 --- a/extension-objects/src/test/java/units/SergeantUnitTest.java +++ b/extension-objects/src/test/java/units/SergeantUnitTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class SergeantUnitTest { @Test @@ -42,5 +40,4 @@ void getUnitExtension() { assertNotNull(unit.getUnitExtension("SergeantExtension")); assertNull(unit.getUnitExtension("CommanderExtension")); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/units/SoldierUnitTest.java b/extension-objects/src/test/java/units/SoldierUnitTest.java index c32e741171ba..4a3ff803e15a 100644 --- a/extension-objects/src/test/java/units/SoldierUnitTest.java +++ b/extension-objects/src/test/java/units/SoldierUnitTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class SoldierUnitTest { @Test @@ -42,5 +40,4 @@ void getUnitExtension() { assertNull(unit.getUnitExtension("SergeantExtension")); assertNull(unit.getUnitExtension("CommanderExtension")); } - -} \ No newline at end of file +} diff --git a/extension-objects/src/test/java/units/UnitTest.java b/extension-objects/src/test/java/units/UnitTest.java index 2ec0bcf52b2f..9cd0d29d71b7 100644 --- a/extension-objects/src/test/java/units/UnitTest.java +++ b/extension-objects/src/test/java/units/UnitTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Created by Srdjan on 03-May-17. - */ +/** Created by Srdjan on 03-May-17. */ class UnitTest { @Test @@ -44,11 +42,9 @@ void testConstGetSet() { unit.setName(newName); assertEquals(newName, unit.getName()); - assertNull(unit.getUnitExtension("")); assertNull(unit.getUnitExtension("SoldierExtension")); assertNull(unit.getUnitExtension("SergeantExtension")); assertNull(unit.getUnitExtension("CommanderExtension")); } - -} \ No newline at end of file +} diff --git a/facade/README.md b/facade/README.md index ea27e4afa31c..e9f3daf0cbd8 100644 --- a/facade/README.md +++ b/facade/README.md @@ -10,7 +10,7 @@ tag: - Code simplification - Decoupling - Encapsulation - - Gang Of Four + - Gang of Four - Interface - Object composition --- @@ -33,6 +33,10 @@ Wikipedia says > A facade is an object that provides a simplified interface to a larger body of code, such as a class library. +Sequence diagram + +![Facade sequence diagram](./etc/facade-sequence-diagram.png) + ## Programmatic Example of Facade Pattern in Java Here's an example of the Facade Design Pattern in a goldmine scenario, demonstrating how a Java facade can streamline complex operations. diff --git a/facade/etc/facade-sequence-diagram.png b/facade/etc/facade-sequence-diagram.png new file mode 100644 index 000000000000..d0172c01c5c8 Binary files /dev/null and b/facade/etc/facade-sequence-diagram.png differ diff --git a/facade/pom.xml b/facade/pom.xml index c61e03e169e3..916a3b0c45b8 100644 --- a/facade/pom.xml +++ b/facade/pom.xml @@ -34,6 +34,14 @@ facade + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenCartOperator.java b/facade/src/main/java/com/iluwatar/facade/DwarvenCartOperator.java index 82ec4ef3aca9..9b077b242973 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenCartOperator.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenCartOperator.java @@ -1,44 +1,42 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import lombok.extern.slf4j.Slf4j; - -/** - * DwarvenCartOperator is one of the goldmine subsystems. - */ -@Slf4j -public class DwarvenCartOperator extends DwarvenMineWorker { - - @Override - public void work() { - LOGGER.info("{} moves gold chunks out of the mine.", name()); - } - - @Override - public String name() { - return "Dwarf cart operator"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import lombok.extern.slf4j.Slf4j; + +/** DwarvenCartOperator is one of the goldmine subsystems. */ +@Slf4j +public class DwarvenCartOperator extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} moves gold chunks out of the mine.", name()); + } + + @Override + public String name() { + return "Dwarf cart operator"; + } +} diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldDigger.java b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldDigger.java index 2f95c015b50f..29cdc7424667 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldDigger.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldDigger.java @@ -1,44 +1,42 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import lombok.extern.slf4j.Slf4j; - -/** - * DwarvenGoldDigger is one of the goldmine subsystems. - */ -@Slf4j -public class DwarvenGoldDigger extends DwarvenMineWorker { - - @Override - public void work() { - LOGGER.info("{} digs for gold.", name()); - } - - @Override - public String name() { - return "Dwarf gold digger"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import lombok.extern.slf4j.Slf4j; + +/** DwarvenGoldDigger is one of the goldmine subsystems. */ +@Slf4j +public class DwarvenGoldDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} digs for gold.", name()); + } + + @Override + public String name() { + return "Dwarf gold digger"; + } +} diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java index 28c1a58a946b..3e4151674b51 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenGoldmineFacade.java @@ -1,69 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import java.util.Collection; -import java.util.List; - -/** - * DwarvenGoldmineFacade provides a single interface through which users can operate the - * subsystems. - * - *

    This makes the goldmine easier to operate and cuts the dependencies from the goldmine user to - * the subsystems. - */ -public class DwarvenGoldmineFacade { - - private final List workers; - - /** - * Constructor. - */ - public DwarvenGoldmineFacade() { - workers = List.of( - new DwarvenGoldDigger(), - new DwarvenCartOperator(), - new DwarvenTunnelDigger()); - } - - public void startNewDay() { - makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); - } - - public void digOutGold() { - makeActions(workers, DwarvenMineWorker.Action.WORK); - } - - public void endDay() { - makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); - } - - private static void makeActions( - Collection workers, - DwarvenMineWorker.Action... actions - ) { - workers.forEach(worker -> worker.action(actions)); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import java.util.Collection; +import java.util.List; + +/** + * DwarvenGoldmineFacade provides a single interface through which users can operate the subsystems. + * + *

    This makes the goldmine easier to operate and cuts the dependencies from the goldmine user to + * the subsystems. + */ +public class DwarvenGoldmineFacade { + + private final List workers; + + /** Constructor. */ + public DwarvenGoldmineFacade() { + workers = + List.of(new DwarvenGoldDigger(), new DwarvenCartOperator(), new DwarvenTunnelDigger()); + } + + public void startNewDay() { + makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); + } + + public void digOutGold() { + makeActions(workers, DwarvenMineWorker.Action.WORK); + } + + public void endDay() { + makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); + } + + private static void makeActions( + Collection workers, DwarvenMineWorker.Action... actions) { + workers.forEach(worker -> worker.action(actions)); + } +} diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java b/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java index e0c50a2064f2..08af7cc2c878 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenMineWorker.java @@ -1,77 +1,77 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import java.util.Arrays; -import lombok.extern.slf4j.Slf4j; - -/** - * DwarvenMineWorker is one of the goldmine subsystems. - */ -@Slf4j -public abstract class DwarvenMineWorker { - - public void goToSleep() { - LOGGER.info("{} goes to sleep.", name()); - } - - public void wakeUp() { - LOGGER.info("{} wakes up.", name()); - } - - public void goHome() { - LOGGER.info("{} goes home.", name()); - } - - public void goToMine() { - LOGGER.info("{} goes to the mine.", name()); - } - - private void action(Action action) { - switch (action) { - case GO_TO_SLEEP -> goToSleep(); - case WAKE_UP -> wakeUp(); - case GO_HOME -> goHome(); - case GO_TO_MINE -> goToMine(); - case WORK -> work(); - default -> LOGGER.info("Undefined action"); - } - } - - /** - * Perform actions. - */ - public void action(Action... actions) { - Arrays.stream(actions).forEach(this::action); - } - - public abstract void work(); - - public abstract String name(); - - enum Action { - GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import java.util.Arrays; +import lombok.extern.slf4j.Slf4j; + +/** DwarvenMineWorker is one of the goldmine subsystems. */ +@Slf4j +public abstract class DwarvenMineWorker { + + public void goToSleep() { + LOGGER.info("{} goes to sleep.", name()); + } + + public void wakeUp() { + LOGGER.info("{} wakes up.", name()); + } + + public void goHome() { + LOGGER.info("{} goes home.", name()); + } + + public void goToMine() { + LOGGER.info("{} goes to the mine.", name()); + } + + private void action(Action action) { + switch (action) { + case GO_TO_SLEEP -> goToSleep(); + case WAKE_UP -> wakeUp(); + case GO_HOME -> goHome(); + case GO_TO_MINE -> goToMine(); + case WORK -> work(); + default -> LOGGER.info("Undefined action"); + } + } + + /** Perform actions. */ + public void action(Action... actions) { + Arrays.stream(actions).forEach(this::action); + } + + public abstract void work(); + + public abstract String name(); + + enum Action { + GO_TO_SLEEP, + WAKE_UP, + GO_HOME, + GO_TO_MINE, + WORK + } +} diff --git a/facade/src/main/java/com/iluwatar/facade/DwarvenTunnelDigger.java b/facade/src/main/java/com/iluwatar/facade/DwarvenTunnelDigger.java index b496b0ebd0e0..4a8fa5a89959 100644 --- a/facade/src/main/java/com/iluwatar/facade/DwarvenTunnelDigger.java +++ b/facade/src/main/java/com/iluwatar/facade/DwarvenTunnelDigger.java @@ -1,44 +1,42 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.facade; - -import lombok.extern.slf4j.Slf4j; - -/** - * DwarvenTunnelDigger is one of the goldmine subsystems. - */ -@Slf4j -public class DwarvenTunnelDigger extends DwarvenMineWorker { - - @Override - public void work() { - LOGGER.info("{} creates another promising tunnel.", name()); - } - - @Override - public String name() { - return "Dwarven tunnel digger"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.facade; + +import lombok.extern.slf4j.Slf4j; + +/** DwarvenTunnelDigger is one of the goldmine subsystems. */ +@Slf4j +public class DwarvenTunnelDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} creates another promising tunnel.", name()); + } + + @Override + public String name() { + return "Dwarven tunnel digger"; + } +} diff --git a/facade/src/test/java/com/iluwatar/facade/AppTest.java b/facade/src/test/java/com/iluwatar/facade/AppTest.java index 38c4b9b7827c..41d6d2a942b7 100644 --- a/facade/src/test/java/com/iluwatar/facade/AppTest.java +++ b/facade/src/test/java/com/iluwatar/facade/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.facade; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java index 63b59fb1cc8b..55baf907da56 100644 --- a/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java +++ b/facade/src/test/java/com/iluwatar/facade/DwarvenGoldmineFacadeTest.java @@ -37,10 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * DwarvenGoldmineFacadeTest - * - */ +/** DwarvenGoldmineFacadeTest */ class DwarvenGoldmineFacadeTest { private InMemoryAppender appender; @@ -59,8 +56,8 @@ void tearDown() { * Test a complete day cycle in the gold mine by executing all three different steps: {@link * DwarvenGoldmineFacade#startNewDay()}, {@link DwarvenGoldmineFacade#digOutGold()} and {@link * DwarvenGoldmineFacade#endDay()}. - *

    - * See if the workers are doing what's expected from them on each step. + * + *

    See if the workers are doing what's expected from them on each step. */ @Test void testFullWorkDay() { @@ -127,11 +124,7 @@ public int getLogSize() { } public boolean logContains(String message) { - return log.stream() - .map(ILoggingEvent::getFormattedMessage) - .anyMatch(message::equals); + return log.stream().map(ILoggingEvent::getFormattedMessage).anyMatch(message::equals); } } - - } diff --git a/factory-kit/README.md b/factory-kit/README.md index d13379a4ec8a..cd97e53f915f 100644 --- a/factory-kit/README.md +++ b/factory-kit/README.md @@ -32,6 +32,10 @@ In plain words > Factory kit is a configurable object builder, a factory to create factories. +Sequence diagram + +![Factory Kit sequence diagram](./etc/factory-kit-sequence-diagram.png) + ## Programmatic Example of Factory Kit Pattern in Java Imagine a magical weapon factory in Java capable of creating any desired weapon using the Factory Kit Pattern. This pattern allows for configurable object builders, making it ideal for scenarios where the types of objects are not known upfront. diff --git a/factory-kit/etc/factory-kit-sequence-diagram.png b/factory-kit/etc/factory-kit-sequence-diagram.png new file mode 100644 index 000000000000..c2f07c2b9bf3 Binary files /dev/null and b/factory-kit/etc/factory-kit-sequence-diagram.png differ diff --git a/factory-kit/pom.xml b/factory-kit/pom.xml index 0edb889ea600..729ea8c6b12d 100644 --- a/factory-kit/pom.xml +++ b/factory-kit/pom.xml @@ -34,6 +34,14 @@ factory-kit + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/App.java b/factory-kit/src/main/java/com/iluwatar/factorykit/App.java index 416b3d39b16a..e545c313e564 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/App.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/App.java @@ -35,9 +35,9 @@ *

    In the given example {@link WeaponFactory} represents the factory kit, that contains four * {@link Builder}s for creating new objects of the classes implementing {@link Weapon} interface. * - *

    Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with - * an input representing an instance of {@link WeaponType} that needs to be mapped explicitly with - * desired class type in the factory instance. + *

    Each of them can be called with {@link WeaponFactory#create(WeaponType)} method, with an input + * representing an instance of {@link WeaponType} that needs to be mapped explicitly with desired + * class type in the factory instance. */ @Slf4j public class App { @@ -48,12 +48,14 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var factory = WeaponFactory.factory(builder -> { - builder.add(WeaponType.SWORD, Sword::new); - builder.add(WeaponType.AXE, Axe::new); - builder.add(WeaponType.SPEAR, Spear::new); - builder.add(WeaponType.BOW, Bow::new); - }); + var factory = + WeaponFactory.factory( + builder -> { + builder.add(WeaponType.SWORD, Sword::new); + builder.add(WeaponType.AXE, Axe::new); + builder.add(WeaponType.SPEAR, Spear::new); + builder.add(WeaponType.BOW, Bow::new); + }); var list = new ArrayList(); list.add(factory.create(WeaponType.AXE)); list.add(factory.create(WeaponType.SPEAR)); diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Axe.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Axe.java index d8de7ffe2279..583fa44a515b 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Axe.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Axe.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Class representing Axe. - */ +/** Class representing Axe. */ public class Axe implements Weapon { @Override public String toString() { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Bow.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Bow.java index bd1644b788c5..f052db8109a9 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Bow.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Bow.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Class representing Bows. - */ +/** Class representing Bows. */ public class Bow implements Weapon { @Override public String toString() { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Builder.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Builder.java index 927f2b63162b..541932e8c8f1 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Builder.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Builder.java @@ -26,9 +26,7 @@ import java.util.function.Supplier; -/** - * Functional interface that allows adding builder with name to the factory. - */ +/** Functional interface that allows adding builder with name to the factory. */ public interface Builder { void add(WeaponType name, Supplier supplier); } diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Spear.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Spear.java index ad9b682a5a4a..9dfd97a07573 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Spear.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Spear.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Class representing Spear. - */ +/** Class representing Spear. */ public class Spear implements Weapon { @Override public String toString() { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Sword.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Sword.java index 2aa49ce58a9e..7f0320a3bc06 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Sword.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Sword.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Class representing Swords. - */ +/** Class representing Swords. */ public class Sword implements Weapon { @Override public String toString() { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/Weapon.java b/factory-kit/src/main/java/com/iluwatar/factorykit/Weapon.java index e02da7484164..8501fd59f635 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/Weapon.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/Weapon.java @@ -24,8 +24,5 @@ */ package com.iluwatar.factorykit; -/** - * Interface representing weapon. - */ -public interface Weapon { -} +/** Interface representing weapon. */ +public interface Weapon {} diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponFactory.java b/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponFactory.java index 997ac8fadc68..74bf0623b1a9 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponFactory.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponFactory.java @@ -29,11 +29,11 @@ import java.util.function.Supplier; /** - * Functional interface, an example of the factory-kit design pattern. - *
    Instance created locally gives an opportunity to strictly define - * which objects types the instance of a factory will be able to create. - *
    Factory is a placeholder for {@link Builder}s - * with {@link WeaponFactory#create(WeaponType)} method to initialize new objects. + * Functional interface, an example of the factory-kit design pattern.
    + * Instance created locally gives an opportunity to strictly define which objects types the instance + * of a factory will be able to create.
    + * Factory is a placeholder for {@link Builder}s with {@link WeaponFactory#create(WeaponType)} + * method to initialize new objects. */ public interface WeaponFactory { diff --git a/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponType.java b/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponType.java index 4d292f46106a..eee7e1136b29 100644 --- a/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponType.java +++ b/factory-kit/src/main/java/com/iluwatar/factorykit/WeaponType.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factorykit; -/** - * Enumerates {@link Weapon} types. - */ +/** Enumerates {@link Weapon} types. */ public enum WeaponType { SWORD, AXE, diff --git a/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java b/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java index 781b3f4da390..afd4f6404711 100644 --- a/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java +++ b/factory-kit/src/test/java/com/iluwatar/factorykit/app/AppTest.java @@ -24,19 +24,16 @@ */ package com.iluwatar.factorykit.app; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.iluwatar.factorykit.App; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Application Test Entrypoint - */ +/** Application Test Entrypoint */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } - diff --git a/factory-kit/src/test/java/com/iluwatar/factorykit/factorykit/FactoryKitTest.java b/factory-kit/src/test/java/com/iluwatar/factorykit/factorykit/FactoryKitTest.java index 152ce2bd036c..02862a5d8cc3 100644 --- a/factory-kit/src/test/java/com/iluwatar/factorykit/factorykit/FactoryKitTest.java +++ b/factory-kit/src/test/java/com/iluwatar/factorykit/factorykit/FactoryKitTest.java @@ -35,20 +35,20 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Factory Kit Pattern - */ +/** Test Factory Kit Pattern */ class FactoryKitTest { private WeaponFactory factory; @BeforeEach void init() { - factory = WeaponFactory.factory(builder -> { - builder.add(WeaponType.SPEAR, Spear::new); - builder.add(WeaponType.AXE, Axe::new); - builder.add(WeaponType.SWORD, Sword::new); - }); + factory = + WeaponFactory.factory( + builder -> { + builder.add(WeaponType.SPEAR, Spear::new); + builder.add(WeaponType.AXE, Axe::new); + builder.add(WeaponType.SWORD, Sword::new); + }); } /** @@ -71,7 +71,6 @@ void testAxeWeapon() { verifyWeapon(weapon, Axe.class); } - /** * Testing {@link WeaponFactory} to produce a SWORD asserting that the Weapon is an instance of * {@link Sword} @@ -86,7 +85,7 @@ void testWeapon() { * This method asserts that the weapon object that is passed is an instance of the clazz * * @param weapon weapon object which is to be verified - * @param clazz expected class of the weapon + * @param clazz expected class of the weapon */ private void verifyWeapon(Weapon weapon, Class clazz) { assertTrue(clazz.isInstance(weapon), "Weapon must be an object of: " + clazz.getName()); diff --git a/factory-method/README.md b/factory-method/README.md index 6aad53596c93..4334b052353d 100644 --- a/factory-method/README.md +++ b/factory-method/README.md @@ -36,6 +36,10 @@ Wikipedia says > In class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method — either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor. +Sequence diagram + +![Factory Method sequence diagram](./etc/factory-method-sequence-diagram.png) + ## Programmatic Example of Factory Method Pattern in Java The Factory Method approach is pivotal in Java Design Patterns for achieving flexible and maintainable code as we see in the following example. diff --git a/factory-method/etc/factory-method-sequence-diagram.png b/factory-method/etc/factory-method-sequence-diagram.png new file mode 100644 index 000000000000..d055d9ca956b Binary files /dev/null and b/factory-method/etc/factory-method-sequence-diagram.png differ diff --git a/factory-method/pom.xml b/factory-method/pom.xml index ea8e842c59d9..bdcfcea3cfcb 100644 --- a/factory-method/pom.xml +++ b/factory-method/pom.xml @@ -34,6 +34,14 @@ factory-method + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/App.java b/factory-method/src/main/java/com/iluwatar/factory/method/App.java index 5ca71f45a44e..2094d2d6928a 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/App.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/App.java @@ -34,10 +34,9 @@ * derived classes—rather than by calling a constructor. * *

    In this Factory Method example we have an interface ({@link Blacksmith}) with a method for - * creating objects ({@link Blacksmith#manufactureWeapon}). The concrete subclasses ( - * {@link OrcBlacksmith}, {@link ElfBlacksmith}) then override the method to produce objects of - * their liking. - * + * creating objects ({@link Blacksmith#manufactureWeapon}). The concrete subclasses ( {@link + * OrcBlacksmith}, {@link ElfBlacksmith}) then override the method to produce objects of their + * liking. */ @Slf4j public class App { @@ -46,6 +45,7 @@ public class App { /** * Program entry point. + * * @param args command line args */ public static void main(String[] args) { diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/Blacksmith.java b/factory-method/src/main/java/com/iluwatar/factory/method/Blacksmith.java index 15fb24b6505e..b027763ed673 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/Blacksmith.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/Blacksmith.java @@ -24,11 +24,8 @@ */ package com.iluwatar.factory.method; -/** - * The interface containing method for producing objects. - */ +/** The interface containing method for producing objects. */ public interface Blacksmith { Weapon manufactureWeapon(WeaponType weaponType); - } diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java b/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java index 411663ad1259..881b82a6a3f4 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/ElfBlacksmith.java @@ -28,9 +28,7 @@ import java.util.EnumMap; import java.util.Map; -/** - * Concrete subclass for creating new objects. - */ +/** Concrete subclass for creating new objects. */ public class ElfBlacksmith implements Blacksmith { private static final Map ELFARSENAL; diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java b/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java index e055132e2733..58b188e2996c 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/ElfWeapon.java @@ -24,12 +24,7 @@ */ package com.iluwatar.factory.method; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -/** - * ElfWeapon. - */ +/** ElfWeapon. */ public record ElfWeapon(WeaponType weaponType) implements Weapon { @Override diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java b/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java index 4d856bba537b..6657b66a4974 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/OrcBlacksmith.java @@ -28,9 +28,7 @@ import java.util.EnumMap; import java.util.Map; -/** - * Concrete subclass for creating new objects. - */ +/** Concrete subclass for creating new objects. */ public class OrcBlacksmith implements Blacksmith { private static final Map ORCARSENAL; diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java b/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java index 8446b9099451..c7bf473ce275 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/OrcWeapon.java @@ -24,12 +24,7 @@ */ package com.iluwatar.factory.method; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -/** - * OrcWeapon. - */ +/** OrcWeapon. */ public record OrcWeapon(WeaponType weaponType) implements Weapon { @Override diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/Weapon.java b/factory-method/src/main/java/com/iluwatar/factory/method/Weapon.java index 97f8cb1d7aec..0d5f12e488e0 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/Weapon.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/Weapon.java @@ -24,11 +24,8 @@ */ package com.iluwatar.factory.method; -/** - * Weapon interface. - */ +/** Weapon interface. */ public interface Weapon { WeaponType weaponType(); - } diff --git a/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java b/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java index 156afae5523d..e1ab11a1f22a 100644 --- a/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java +++ b/factory-method/src/main/java/com/iluwatar/factory/method/WeaponType.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * WeaponType enumeration. - */ +/** WeaponType enumeration. */ @RequiredArgsConstructor public enum WeaponType { - SHORT_SWORD("short sword"), SPEAR("spear"), AXE("axe"), diff --git a/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java b/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java index cd597444b7b2..88fe6f6d7aa2 100644 --- a/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java +++ b/factory-method/src/test/java/com/iluwatar/factory/method/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.factory.method; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Factory Method example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Factory Method example runs without errors. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/factory-method/src/test/java/com/iluwatar/factory/method/FactoryMethodTest.java b/factory-method/src/test/java/com/iluwatar/factory/method/FactoryMethodTest.java index 25b15836eaa2..7cd4dbeb234d 100644 --- a/factory-method/src/test/java/com/iluwatar/factory/method/FactoryMethodTest.java +++ b/factory-method/src/test/java/com/iluwatar/factory/method/FactoryMethodTest.java @@ -36,10 +36,8 @@ * and implemented by child classes, or implemented in a base class and optionally overridden by * derived classes—rather than by calling a constructor. * - *

    Factory produces the object of its liking. - * The weapon {@link Weapon} manufactured by the blacksmith depends on the kind of factory - * implementation it is referring to. - *

    + *

    Factory produces the object of its liking. The weapon {@link Weapon} manufactured by the + * blacksmith depends on the kind of factory implementation it is referring to. */ class FactoryMethodTest { @@ -91,13 +89,15 @@ void testElfBlacksmithWithSpear() { * This method asserts that the weapon object that is passed is an instance of the clazz and the * weapon is of type expectedWeaponType. * - * @param weapon weapon object which is to be verified + * @param weapon weapon object which is to be verified * @param expectedWeaponType expected WeaponType of the weapon - * @param clazz expected class of the weapon + * @param clazz expected class of the weapon */ private void verifyWeapon(Weapon weapon, WeaponType expectedWeaponType, Class clazz) { assertTrue(clazz.isInstance(weapon), "Weapon must be an object of: " + clazz.getName()); - assertEquals(expectedWeaponType, weapon - .weaponType(), "Weapon must be of weaponType: " + expectedWeaponType); + assertEquals( + expectedWeaponType, + weapon.weaponType(), + "Weapon must be of weaponType: " + expectedWeaponType); } } diff --git a/factory/README.md b/factory/README.md index da5b220e914e..e048b4b82e20 100644 --- a/factory/README.md +++ b/factory/README.md @@ -26,6 +26,10 @@ Wikipedia says > Factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class. +Sequence diagram + +![Factory sequence diagram](./etc/factory-sequence-diagram.png) + ## Programmatic Example of Factory Pattern in Java Imagine an alchemist who is about to manufacture coins. The alchemist must be able to create both gold and copper coins and switching between them must be possible without modifying the existing source code. The factory pattern makes it possible by providing a static construction method which can be called with relevant parameters. diff --git a/factory/etc/factory-sequence-diagram.png b/factory/etc/factory-sequence-diagram.png new file mode 100644 index 000000000000..260bea92f247 Binary files /dev/null and b/factory/etc/factory-sequence-diagram.png differ diff --git a/factory/pom.xml b/factory/pom.xml index 5468af6cdebf..7280d8b6d64a 100644 --- a/factory/pom.xml +++ b/factory/pom.xml @@ -34,6 +34,14 @@ factory + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/factory/src/main/java/com/iluwatar/factory/App.java b/factory/src/main/java/com/iluwatar/factory/App.java index d1f3313b4b31..ce9f205b0f15 100644 --- a/factory/src/main/java/com/iluwatar/factory/App.java +++ b/factory/src/main/java/com/iluwatar/factory/App.java @@ -27,20 +27,17 @@ import lombok.extern.slf4j.Slf4j; /** - * Factory is an object for creating other objects. It provides a static method to - * create and return objects of varying classes, in order to hide the implementation logic - * and makes client code focus on usage rather than objects initialization and management. + * Factory is an object for creating other objects. It provides a static method to create and return + * objects of varying classes, in order to hide the implementation logic and makes client code focus + * on usage rather than objects initialization and management. * *

    In this example an alchemist manufactures coins. CoinFactory is the factory class, and it * provides a static method to create different types of coins. */ - @Slf4j public class App { - /** - * Program main entry point. - */ + /** Program main entry point. */ public static void main(String[] args) { LOGGER.info("The alchemist begins his work."); var coin1 = CoinFactory.getCoin(CoinType.COPPER); diff --git a/factory/src/main/java/com/iluwatar/factory/Coin.java b/factory/src/main/java/com/iluwatar/factory/Coin.java index 4adef8ea78e7..b8824222e5a8 100644 --- a/factory/src/main/java/com/iluwatar/factory/Coin.java +++ b/factory/src/main/java/com/iluwatar/factory/Coin.java @@ -24,11 +24,8 @@ */ package com.iluwatar.factory; -/** - * Coin interface. - */ +/** Coin interface. */ public interface Coin { String getDescription(); - } diff --git a/factory/src/main/java/com/iluwatar/factory/CoinFactory.java b/factory/src/main/java/com/iluwatar/factory/CoinFactory.java index 212bef4b947d..8940e3ade568 100644 --- a/factory/src/main/java/com/iluwatar/factory/CoinFactory.java +++ b/factory/src/main/java/com/iluwatar/factory/CoinFactory.java @@ -24,14 +24,10 @@ */ package com.iluwatar.factory; -/** - * Factory of coins. - */ +/** Factory of coins. */ public class CoinFactory { - /** - * Factory method takes as a parameter the coin type and calls the appropriate class. - */ + /** Factory method takes as a parameter the coin type and calls the appropriate class. */ public static Coin getCoin(CoinType type) { return type.getConstructor().get(); } diff --git a/factory/src/main/java/com/iluwatar/factory/CoinType.java b/factory/src/main/java/com/iluwatar/factory/CoinType.java index 069bd458644d..de1583bf9099 100644 --- a/factory/src/main/java/com/iluwatar/factory/CoinType.java +++ b/factory/src/main/java/com/iluwatar/factory/CoinType.java @@ -28,13 +28,10 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Enumeration for different types of coins. - */ +/** Enumeration for different types of coins. */ @RequiredArgsConstructor @Getter public enum CoinType { - COPPER(CopperCoin::new), GOLD(GoldCoin::new); diff --git a/factory/src/main/java/com/iluwatar/factory/CopperCoin.java b/factory/src/main/java/com/iluwatar/factory/CopperCoin.java index b4c586468a42..fa6f3f2d8ad3 100644 --- a/factory/src/main/java/com/iluwatar/factory/CopperCoin.java +++ b/factory/src/main/java/com/iluwatar/factory/CopperCoin.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factory; -/** - * CopperCoin implementation. - */ +/** CopperCoin implementation. */ public class CopperCoin implements Coin { static final String DESCRIPTION = "This is a copper coin."; diff --git a/factory/src/main/java/com/iluwatar/factory/GoldCoin.java b/factory/src/main/java/com/iluwatar/factory/GoldCoin.java index 4e693a4d350f..b5e1aef1a010 100644 --- a/factory/src/main/java/com/iluwatar/factory/GoldCoin.java +++ b/factory/src/main/java/com/iluwatar/factory/GoldCoin.java @@ -24,9 +24,7 @@ */ package com.iluwatar.factory; -/** - * GoldCoin implementation. - */ +/** GoldCoin implementation. */ public class GoldCoin implements Coin { static final String DESCRIPTION = "This is a gold coin."; diff --git a/factory/src/test/java/com/iluwatar/factory/AppTest.java b/factory/src/test/java/com/iluwatar/factory/AppTest.java index 544a9bc46da3..702481525d45 100644 --- a/factory/src/test/java/com/iluwatar/factory/AppTest.java +++ b/factory/src/test/java/com/iluwatar/factory/AppTest.java @@ -32,7 +32,6 @@ class AppTest { @Test void shouldExecuteWithoutExceptions() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/fanout-fanin/README.md b/fanout-fanin/README.md index d92373760d97..12ba7cae53f1 100644 --- a/fanout-fanin/README.md +++ b/fanout-fanin/README.md @@ -35,6 +35,10 @@ Wikipedia says > > The fan-in concept, on the other hand, typically refers to the aggregation of multiple inputs. In digital electronics, it describes the number of inputs a logic gate can handle. Combining these concepts, the Fan-Out/Fan-In pattern in software engineering involves distributing tasks (fan-out) and then aggregating the results (fan-in). +Sequence diagram + +![Fan-Out/Fan-In flowchart](./etc/fan-out-fan-in-flowchart.png) + ## Programmatic Example of Fan-Out/Fan-In Pattern in Java The provided implementation involves a list of numbers with the objective to square them and aggregate the results. The `FanOutFanIn` class receives the list of numbers as `SquareNumberRequest` objects and a `Consumer` instance that collects the squared results as the requests complete. Each `SquareNumberRequest` squares its number with a random delay, simulating a long-running process that finishes at unpredictable times. The `Consumer` instance gathers the results from the various `SquareNumberRequest` objects as they become available at different times. diff --git a/fanout-fanin/etc/fan-out-fan-in-flowchart.png b/fanout-fanin/etc/fan-out-fan-in-flowchart.png new file mode 100644 index 000000000000..cf726696cd76 Binary files /dev/null and b/fanout-fanin/etc/fan-out-fan-in-flowchart.png differ diff --git a/fanout-fanin/pom.xml b/fanout-fanin/pom.xml index 2cc2c117f39d..1052918ee83f 100644 --- a/fanout-fanin/pom.xml +++ b/fanout-fanin/pom.xml @@ -34,6 +34,14 @@ 4.0.0 fanout-fanin + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java index 83f51f647048..7a34c22149b5 100644 --- a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/App.java @@ -28,8 +28,6 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; - - /** * FanOut/FanIn pattern is a concurrency pattern that refers to executing multiple instances of the * activity function concurrently. The "fan out" part is essentially splitting the data into diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java index 90b7c63855e3..95b7c663abe2 100644 --- a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/Consumer.java @@ -27,8 +27,6 @@ import java.util.concurrent.atomic.AtomicLong; import lombok.Getter; - - /** * Consumer or callback class that will be called every time a request is complete This will * aggregate individual result to form a final result. diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java index e75f0d22bd44..9239e03f46ee 100644 --- a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/FanOutFanIn.java @@ -38,6 +38,7 @@ public class FanOutFanIn { /** * the main fanOutFanIn function or orchestrator function. + * * @param requests List of numbers that need to be squared and summed up * @param consumer Takes in the squared number from {@link SquareNumberRequest} and sums it up * @return Aggregated sum of all squared numbers. diff --git a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java index e41c33683cdb..7f12e842031e 100644 --- a/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java +++ b/fanout-fanin/src/main/java/com/iluwatar/fanout/fanin/SquareNumberRequest.java @@ -41,8 +41,9 @@ public class SquareNumberRequest { /** * Squares the number with a little timeout to give impression of long-running process that return * at different times. + * * @param consumer callback class that takes the result after the delay. - * */ + */ public void delayedSquaring(final Consumer consumer) { var minTimeOut = 5000L; diff --git a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java index 3f2bee396357..0fe64096fddb 100644 --- a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java +++ b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/AppTest.java @@ -24,14 +24,14 @@ */ package com.iluwatar.fanout.fanin; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + class AppTest { - @Test - void shouldLaunchApp() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + @Test + void shouldLaunchApp() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java index bd31108b702d..55260c36aa3c 100644 --- a/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java +++ b/fanout-fanin/src/test/java/com/iluwatar/fanout/fanin/FanOutFanInTest.java @@ -24,10 +24,10 @@ */ package com.iluwatar.fanout.fanin; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; import java.util.Arrays; import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; class FanOutFanInTest { diff --git a/feature-toggle/README.md b/feature-toggle/README.md index b010cba5977e..dc2d913a86ed 100644 --- a/feature-toggle/README.md +++ b/feature-toggle/README.md @@ -34,6 +34,10 @@ Wikipedia says > A feature toggle in software development provides an alternative to maintaining multiple feature branches in source code. A condition within the code enables or disables a feature during runtime. In agile settings the toggle is used in production, to switch on the feature on demand, for some or all the users. +Flowchart + +![Feature Toggle flowchart](./etc/feature-toggle-flowchart.png) + ## Programmatic Example of Feature Toggle Pattern in Java This Java code example demonstrates how to display a feature when it is enabled by the developer and the user is a Premium member of the application. This approach is useful for managing subscription-locked features. diff --git a/feature-toggle/etc/feature-toggle-flowchart.png b/feature-toggle/etc/feature-toggle-flowchart.png new file mode 100644 index 000000000000..8a62b1b59309 Binary files /dev/null and b/feature-toggle/etc/feature-toggle-flowchart.png differ diff --git a/feature-toggle/pom.xml b/feature-toggle/pom.xml index 8a60b4876175..c21080827a57 100644 --- a/feature-toggle/pom.xml +++ b/feature-toggle/pom.xml @@ -34,6 +34,14 @@ 4.0.0 feature-toggle + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java index 93a953005670..30c729dd8992 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/App.java @@ -94,7 +94,8 @@ public static void main(String[] args) { // Demonstrates the TieredFeatureToggleVersion setup with // two users: one on the free tier and the other on the paid tier. When the // Service#getWelcomeMessage(User) method is called with the paid user, the welcome - // message includes their username. In contrast, calling the same service with the free tier user results + // message includes their username. In contrast, calling the same service with the free tier + // user results // in a more generic welcome message without the username. var service2 = new TieredFeatureToggleVersion(); diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java index 4dcc45b1300c..7c8287622bda 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/Service.java @@ -28,8 +28,8 @@ /** * Simple interfaces to allow the calling of the method to generate the welcome message for a given - * user. While there is a helper method to gather the status of the feature toggle. In some - * cases there is no need for the {@link Service#isEnhanced()} in {@link + * user. While there is a helper method to gather the status of the feature toggle. In some cases + * there is no need for the {@link Service#isEnhanced()} in {@link * com.iluwatar.featuretoggle.pattern.tieredversion.TieredFeatureToggleVersion} where the toggle is * determined by the actual {@link User}. * @@ -53,5 +53,4 @@ public interface Service { * @return Boolean {@code true} if enhanced. */ boolean isEnhanced(); - } diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java index b3484cec2a9a..cfc2ab56b293 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersion.java @@ -46,8 +46,8 @@ public class PropertiesFeatureToggleVersion implements Service { /** - * True if the welcome message to be returned is the enhanced venison or not. For - * this service it will see the value of the boolean that was set in the constructor {@link + * True if the welcome message to be returned is the enhanced venison or not. For this service it + * will see the value of the boolean that was set in the constructor {@link * PropertiesFeatureToggleVersion#PropertiesFeatureToggleVersion(Properties)} */ private final boolean enhanced; @@ -80,9 +80,9 @@ public PropertiesFeatureToggleVersion(final Properties properties) { * passed {@link User}. However, if disabled then a generic version fo the message is returned. * * @param user the {@link User} to be displayed in the message if the enhanced version is enabled - * see {@link PropertiesFeatureToggleVersion#isEnhanced()}. If the enhanced version is - * enabled, then the message will be personalised with the name of the passed {@link - * User}. However, if disabled then a generic version fo the message is returned. + * see {@link PropertiesFeatureToggleVersion#isEnhanced()}. If the enhanced version is + * enabled, then the message will be personalised with the name of the passed {@link User}. + * However, if disabled then a generic version fo the message is returned. * @return Resulting welcome message. * @see User */ diff --git a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java index 420783485569..f5342da01a57 100644 --- a/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java +++ b/feature-toggle/src/main/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersion.java @@ -30,9 +30,9 @@ /** * This example of the Feature Toggle pattern shows how it could be implemented based on a {@link - * User}. Therefore, showing its use within a tiered application where the paying users get access to - * different content or better versions of features. So in this instance a {@link User} is passed in - * and if they are found to be on the {@link UserGroup#isPaid(User)} they are welcomed with a + * User}. Therefore, showing its use within a tiered application where the paying users get access + * to different content or better versions of features. So in this instance a {@link User} is passed + * in and if they are found to be on the {@link UserGroup#isPaid(User)} they are welcomed with a * personalised message. While the other is more generic. However, this pattern is limited to simple * examples such as the one below. * @@ -49,8 +49,8 @@ public class TieredFeatureToggleVersion implements Service { * the enhanced version of the welcome message will be returned where the username is displayed. * * @param user the {@link User} to generate the welcome message for, different messages are - * displayed if the user is in the {@link UserGroup#isPaid(User)} or {@link - * UserGroup#freeGroup} + * displayed if the user is in the {@link UserGroup#isPaid(User)} or {@link + * UserGroup#freeGroup} * @return Resulting welcome message. * @see User * @see UserGroup @@ -75,5 +75,4 @@ public String getWelcomeMessage(User user) { public boolean isEnhanced() { return true; } - } diff --git a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java index f659134fb1cc..52badacaba2b 100644 --- a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java +++ b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/propertiesversion/PropertiesFeatureToggleVersionTest.java @@ -33,9 +33,7 @@ import java.util.Properties; import org.junit.jupiter.api.Test; -/** - * Test Properties Toggle - */ +/** Test Properties Toggle */ class PropertiesFeatureToggleVersionTest { @Test @@ -45,11 +43,13 @@ void testNullPropertiesPassed() { @Test void testNonBooleanProperty() { - assertThrows(IllegalArgumentException.class, () -> { - final var properties = new Properties(); - properties.setProperty("enhancedWelcome", "Something"); - new PropertiesFeatureToggleVersion(properties); - }); + assertThrows( + IllegalArgumentException.class, + () -> { + final var properties = new Properties(); + properties.setProperty("enhancedWelcome", "Something"); + new PropertiesFeatureToggleVersion(properties); + }); } @Test @@ -59,7 +59,8 @@ void testFeatureTurnedOn() { var service = new PropertiesFeatureToggleVersion(properties); assertTrue(service.isEnhanced()); final var welcomeMessage = service.getWelcomeMessage(new User("Jamie No Code")); - assertEquals("Welcome Jamie No Code. You're using the enhanced welcome message.", welcomeMessage); + assertEquals( + "Welcome Jamie No Code. You're using the enhanced welcome message.", welcomeMessage); } @Test diff --git a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java index 7eb03b833260..67314f4041e3 100644 --- a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java +++ b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/pattern/tieredversion/TieredFeatureToggleVersionTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Tiered Feature Toggle - */ +/** Test Tiered Feature Toggle */ class TieredFeatureToggleVersionTest { final User paidUser = new User("Jamie Coder"); diff --git a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java index 07d0bc88fd07..b9bfb0befed4 100644 --- a/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java +++ b/feature-toggle/src/test/java/com/iluwatar/featuretoggle/user/UserGroupTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * Test User Group specific feature - */ +/** Test User Group specific feature */ class UserGroupTest { @Test diff --git a/filterer/README.md b/filterer/README.md index be895411ba25..bfb5640f8640 100644 --- a/filterer/README.md +++ b/filterer/README.md @@ -33,6 +33,10 @@ In plain words > Filterer pattern is a design pattern that helps container-like objects return filtered versions of themselves. +Flowchart + +![Filterer flowchart](./etc/filterer-flowchart.png) + ## Programmatic Example of Filterer Pattern in Java To illustrate, we use the Filterer design pattern for a malware detection system in Java. This system can filter threats based on various criteria, showcasing the pattern’s flexibility and dynamic nature. In the design we have to take into consideration that new Threat types can be added later. Additionally, there is a requirement that the threat detection system can filter the detected threats based on different criteria (the target system acts as container-like object for threats). diff --git a/filterer/etc/filterer-flowchart.png b/filterer/etc/filterer-flowchart.png new file mode 100644 index 000000000000..7870f2e57e18 Binary files /dev/null and b/filterer/etc/filterer-flowchart.png differ diff --git a/filterer/pom.xml b/filterer/pom.xml index 23f0093f9896..a0d438394cb5 100644 --- a/filterer/pom.xml +++ b/filterer/pom.xml @@ -34,9 +34,17 @@ 4.0.0 filterer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter - junit-jupiter-api + junit-jupiter-engine test diff --git a/filterer/src/main/java/com/iluwatar/filterer/App.java b/filterer/src/main/java/com/iluwatar/filterer/App.java index 30a04d46ed3e..c64dce891fb6 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/App.java +++ b/filterer/src/main/java/com/iluwatar/filterer/App.java @@ -39,10 +39,10 @@ /** * This demo class represent how {@link com.iluwatar.filterer.domain.Filterer} pattern is used to * filter container-like objects to return filtered versions of themselves. The container like - * objects are systems that are aware of threats that they can be vulnerable to. We would like - * to have a way to create copy of different system objects but with filtered threats. - * The thing is to keep it simple if we add new subtype of {@link Threat} - * (for example {@link ProbableThreat}) - we still need to be able to filter by its properties. + * objects are systems that are aware of threats that they can be vulnerable to. We would like to + * have a way to create copy of different system objects but with filtered threats. The thing is to + * keep it simple if we add new subtype of {@link Threat} (for example {@link ProbableThreat}) - we + * still need to be able to filter by its properties. */ @Slf4j public class App { @@ -55,8 +55,8 @@ public static void main(String[] args) { /** * Demonstrates how to filter {@link com.iluwatar.filterer.threat.ProbabilisticThreatAwareSystem} * based on probability property. The @{@link com.iluwatar.filterer.domain.Filterer#by(Predicate)} - * method is able to use {@link com.iluwatar.filterer.threat.ProbableThreat} - * as predicate argument. + * method is able to use {@link com.iluwatar.filterer.threat.ProbableThreat} as predicate + * argument. */ private static void filteringSimpleProbableThreats() { LOGGER.info("### Filtering ProbabilisticThreatAwareSystem by probability ###"); @@ -69,20 +69,22 @@ private static void filteringSimpleProbableThreats() { var probabilisticThreatAwareSystem = new SimpleProbabilisticThreatAwareSystem("Sys-1", probableThreats); - LOGGER.info("Filtering ProbabilisticThreatAwareSystem. Initial : " - + probabilisticThreatAwareSystem); + LOGGER.info( + "Filtering ProbabilisticThreatAwareSystem. Initial : " + probabilisticThreatAwareSystem); - //Filtering using filterer - var filteredThreatAwareSystem = probabilisticThreatAwareSystem.filtered() - .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); + // Filtering using filterer + var filteredThreatAwareSystem = + probabilisticThreatAwareSystem + .filtered() + .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); LOGGER.info("Filtered by probability = 0.99 : " + filteredThreatAwareSystem); } /** - * Demonstrates how to filter {@link ThreatAwareSystem} based on startingOffset property - * of {@link SimpleThreat}. The @{@link com.iluwatar.filterer.domain.Filterer#by(Predicate)} - * method is able to use {@link Threat} as predicate argument. + * Demonstrates how to filter {@link ThreatAwareSystem} based on startingOffset property of {@link + * SimpleThreat}. The @{@link com.iluwatar.filterer.domain.Filterer#by(Predicate)} method is able + * to use {@link Threat} as predicate argument. */ private static void filteringSimpleThreats() { LOGGER.info("### Filtering ThreatAwareSystem by ThreatType ###"); @@ -95,11 +97,10 @@ private static void filteringSimpleThreats() { LOGGER.info("Filtering ThreatAwareSystem. Initial : " + threatAwareSystem); - //Filtering using Filterer - var rootkitThreatAwareSystem = threatAwareSystem.filtered() - .by(threat -> threat.type() == ThreatType.ROOTKIT); + // Filtering using Filterer + var rootkitThreatAwareSystem = + threatAwareSystem.filtered().by(threat -> threat.type() == ThreatType.ROOTKIT); LOGGER.info("Filtered by threatType = ROOTKIT : " + rootkitThreatAwareSystem); } - } diff --git a/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java b/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java index 091db7071b43..58da1a7e563b 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java +++ b/filterer/src/main/java/com/iluwatar/filterer/domain/Filterer.java @@ -28,10 +28,11 @@ /** * Filterer helper interface. + * * @param type of the container-like object. * @param type of the elements contained within this container-like object. */ @FunctionalInterface public interface Filterer { G by(Predicate predicate); -} \ No newline at end of file +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java index 21609207b500..a63728d74360 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbabilisticThreatAwareSystem.java @@ -27,13 +27,12 @@ import com.iluwatar.filterer.domain.Filterer; import java.util.List; -/** - * Represents system that is aware of its threats with given probability of their occurrence. - */ +/** Represents system that is aware of its threats with given probability of their occurrence. */ public interface ProbabilisticThreatAwareSystem extends ThreatAwareSystem { /** * {@inheritDoc} + * * @return {@link ProbableThreat} */ @Override @@ -41,9 +40,9 @@ public interface ProbabilisticThreatAwareSystem extends ThreatAwareSystem { /** * {@inheritDoc} + * * @return {@link Filterer} */ @Override Filterer filtered(); } - diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java index fc4efce51a68..8562136b50dd 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ProbableThreat.java @@ -24,13 +24,12 @@ */ package com.iluwatar.filterer.threat; -/** - * Represents threat that might be a threat with given probability. - */ +/** Represents threat that might be a threat with given probability. */ public interface ProbableThreat extends Threat { /** * Returns probability of occurrence of given threat. + * * @return probability of occurrence of given threat. */ double probability(); -} \ No newline at end of file +} diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java index e9161b9f4c4f..0584a031be7f 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystem.java @@ -31,9 +31,7 @@ import lombok.RequiredArgsConstructor; import lombok.ToString; -/** - * {@inheritDoc} - */ +/** {@inheritDoc} */ @ToString @EqualsAndHashCode @RequiredArgsConstructor @@ -42,25 +40,19 @@ public class SimpleProbabilisticThreatAwareSystem implements ProbabilisticThreat private final String systemId; private final List threats; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public String systemId() { return systemId; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public List threats() { return threats; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Filterer filtered() { return this::filteredGroup; @@ -71,11 +63,7 @@ private ProbabilisticThreatAwareSystem filteredGroup( return new SimpleProbabilisticThreatAwareSystem(this.systemId, filteredItems(predicate)); } - private List filteredItems( - final Predicate predicate) { - return this.threats.stream() - .filter(predicate) - .toList(); + private List filteredItems(final Predicate predicate) { + return this.threats.stream().filter(predicate).toList(); } - } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java index bd8997de31a5..044e02471755 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleProbableThreat.java @@ -26,23 +26,19 @@ import lombok.EqualsAndHashCode; -/** - * {@inheritDoc} - */ +/** {@inheritDoc} */ @EqualsAndHashCode(callSuper = false) public class SimpleProbableThreat extends SimpleThreat implements ProbableThreat { private final double probability; - public SimpleProbableThreat(final String name, final int id, final ThreatType threatType, - final double probability) { + public SimpleProbableThreat( + final String name, final int id, final ThreatType threatType, final double probability) { super(threatType, id, name); this.probability = probability; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public double probability() { return probability; @@ -50,9 +46,6 @@ public double probability() { @Override public String toString() { - return "SimpleProbableThreat{" - + "probability=" + probability - + "} " - + super.toString(); + return "SimpleProbableThreat{" + "probability=" + probability + "} " + super.toString(); } } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java index 6285bddc99be..034c6970be48 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreat.java @@ -28,9 +28,7 @@ import lombok.RequiredArgsConstructor; import lombok.ToString; -/** - * Represents a simple threat. - */ +/** Represents a simple threat. */ @ToString @EqualsAndHashCode @RequiredArgsConstructor @@ -40,28 +38,21 @@ public class SimpleThreat implements Threat { private final int id; private final String name; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public String name() { return name; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public int id() { return id; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public ThreatType type() { return threatType; } - } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java index 93304b64770c..c547afe8f5df 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystem.java @@ -32,9 +32,7 @@ import lombok.RequiredArgsConstructor; import lombok.ToString; -/** - * {@inheritDoc} - */ +/** {@inheritDoc} */ @ToString @EqualsAndHashCode @RequiredArgsConstructor @@ -43,25 +41,19 @@ public class SimpleThreatAwareSystem implements ThreatAwareSystem { private final String systemId; private final List issues; - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public String systemId() { return systemId; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public List threats() { return new ArrayList<>(issues); } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public Filterer filtered() { return this::filteredGroup; @@ -72,8 +64,6 @@ private ThreatAwareSystem filteredGroup(Predicate predicate) { } private List filteredItems(Predicate predicate) { - return this.issues.stream() - .filter(predicate).toList(); + return this.issues.stream().filter(predicate).toList(); } - } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java b/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java index f8aff1c84dc2..1a138956d9cd 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/Threat.java @@ -24,9 +24,7 @@ */ package com.iluwatar.filterer.threat; -/** - * Represents a threat that can be detected in given system. - */ +/** Represents a threat that can be detected in given system. */ public interface Threat { /** * Returns name of the threat. @@ -44,6 +42,7 @@ public interface Threat { /** * Returns threat type. + * * @return {@link ThreatType} */ ThreatType type(); diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java index 6e9bf7af6df6..49ef8560fb68 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatAwareSystem.java @@ -27,10 +27,8 @@ import com.iluwatar.filterer.domain.Filterer; import java.util.List; -/** - * Represents system that is aware of threats that are present in it. - */ -public interface ThreatAwareSystem { +/** Represents system that is aware of threats that are present in it. */ +public interface ThreatAwareSystem { /** * Returns the system id. @@ -41,15 +39,16 @@ public interface ThreatAwareSystem { /** * Returns list of threats for this system. + * * @return list of threats for this system. */ - List threats(); + List threats(); /** - * Returns the instance of {@link Filterer} helper interface that allows to covariantly - * specify lower bound for predicate that we want to filter by. + * Returns the instance of {@link Filterer} helper interface that allows to covariantly specify + * lower bound for predicate that we want to filter by. + * * @return an instance of {@link Filterer} helper interface. */ - Filterer filtered(); - + Filterer, T> filtered(); } diff --git a/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java index 72293ce13ac9..30dcaefa18b1 100644 --- a/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java +++ b/filterer/src/main/java/com/iluwatar/filterer/threat/ThreatType.java @@ -24,9 +24,7 @@ */ package com.iluwatar.filterer.threat; -/** - * Enum class representing Threat types. - */ +/** Enum class representing Threat types. */ public enum ThreatType { TROJAN, WORM, diff --git a/filterer/src/test/java/com/iluwatar/filterer/AppTest.java b/filterer/src/test/java/com/iluwatar/filterer/AppTest.java index 7885feefd1b3..a35f03ea5cf0 100644 --- a/filterer/src/test/java/com/iluwatar/filterer/AppTest.java +++ b/filterer/src/test/java/com/iluwatar/filterer/AppTest.java @@ -32,6 +32,6 @@ class AppTest { @Test void shouldLaunchApp() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java index bf5a3ed3923b..3705009f170f 100644 --- a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java +++ b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleProbabilisticThreatAwareSystemTest.java @@ -33,7 +33,7 @@ class SimpleProbabilisticThreatAwareSystemTest { @Test void shouldFilterByProbability() { - //given + // given var trojan = new SimpleProbableThreat("Troyan-ArcBomb", 1, ThreatType.TROJAN, 0.99); var rootkit = new SimpleProbableThreat("Rootkit-System", 2, ThreatType.ROOTKIT, 0.8); List probableThreats = List.of(trojan, rootkit); @@ -41,12 +41,14 @@ void shouldFilterByProbability() { var simpleProbabilisticThreatAwareSystem = new SimpleProbabilisticThreatAwareSystem("System-1", probableThreats); - //when - var filtered = simpleProbabilisticThreatAwareSystem.filtered() - .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); + // when + var filtered = + simpleProbabilisticThreatAwareSystem + .filtered() + .by(probableThreat -> Double.compare(probableThreat.probability(), 0.99) == 0); - //then + // then assertEquals(filtered.threats().size(), 1); assertEquals(filtered.threats().get(0), trojan); } -} \ No newline at end of file +} diff --git a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java index cc0d696f1471..d763edbf3e1c 100644 --- a/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java +++ b/filterer/src/test/java/com/iluwatar/filterer/threat/SimpleThreatAwareSystemTest.java @@ -32,18 +32,18 @@ class SimpleThreatAwareSystemTest { @Test void shouldFilterByThreatType() { - //given + // given var rootkit = new SimpleThreat(ThreatType.ROOTKIT, 1, "Simple-Rootkit"); var trojan = new SimpleThreat(ThreatType.TROJAN, 2, "Simple-Trojan"); List threats = List.of(rootkit, trojan); var threatAwareSystem = new SimpleThreatAwareSystem("System-1", threats); - //when - var rootkitThreatAwareSystem = threatAwareSystem.filtered() - .by(threat -> threat.type() == ThreatType.ROOTKIT); + // when + var rootkitThreatAwareSystem = + threatAwareSystem.filtered().by(threat -> threat.type() == ThreatType.ROOTKIT); - //then + // then assertEquals(rootkitThreatAwareSystem.threats().size(), 1); assertEquals(rootkitThreatAwareSystem.threats().get(0), rootkit); } diff --git a/fluent-interface/README.md b/fluent-interface/README.md index 8b2a0442061e..3ae18f69ac13 100644 --- a/fluent-interface/README.md +++ b/fluent-interface/README.md @@ -35,6 +35,10 @@ Wikipedia says > In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). +Sequence diagram + +![Fluent Interface sequence diagram](./etc/fluent-interface-sequence-diagram.png) + ## Programmatic Example of Fluent Interface Pattern in Java We need to select numbers based on different criteria from the list. It's a great chance to utilize fluent interface pattern to provide readable easy-to-use developer experience. diff --git a/fluent-interface/etc/fluent-interface-sequence-diagram.png b/fluent-interface/etc/fluent-interface-sequence-diagram.png new file mode 100644 index 000000000000..2c72f5aa54b7 Binary files /dev/null and b/fluent-interface/etc/fluent-interface-sequence-diagram.png differ diff --git a/fluent-interface/etc/fluent-interface.urm.puml b/fluent-interface/etc/fluent-interface.urm.puml new file mode 100644 index 000000000000..d343a478bff0 --- /dev/null +++ b/fluent-interface/etc/fluent-interface.urm.puml @@ -0,0 +1,72 @@ +@startuml +package com.iluwatar.fluentinterface.fluentiterable.simple { + class SimpleFluentIterable { + - iterable : Iterable + + SimpleFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + forEach(action : Consumer) + + from(iterable : Iterable) : FluentIterable {static} + + fromCopyOf(iterable : Iterable) : FluentIterable {static} + + getRemainingElementsCount() : int + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + + spliterator() : Spliterator + + toList(iterator : Iterator) : List {static} + } +} +package com.iluwatar.fluentinterface.app { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + - negatives() : Predicate {static} + - positives() : Predicate {static} + - prettyPrint(delimiter : String, prefix : String, iterable : Iterable) {static} + - prettyPrint(prefix : String, iterable : Iterable) {static} + - transformToString() : Function {static} + } +} +package com.iluwatar.fluentinterface.fluentiterable.lazy { + abstract class DecoratingIterator { + # fromIterator : Iterator + - next : E + + DecoratingIterator(fromIterator : Iterator) + + computeNext() : E {abstract} + + hasNext() : boolean + + next() : E + } + class LazyFluentIterable { + - iterable : Iterable + # LazyFluentIterable() + + LazyFluentIterable(iterable : Iterable) + + asList() : List + + filter(predicate : Predicate) : FluentIterable + + first() : Optional + + first(count : int) : FluentIterable + + from(iterable : Iterable) : FluentIterable {static} + + iterator() : Iterator + + last() : Optional + + last(count : int) : FluentIterable + + map(function : Function) : FluentIterable + } +} +package com.iluwatar.fluentinterface.fluentiterable { + interface FluentIterable { + + asList() : List {abstract} + + copyToList(iterable : Iterable) : List {static} + + filter(Predicate) : FluentIterable {abstract} + + first() : Optional {abstract} + + first(int) : FluentIterable {abstract} + + last() : Optional {abstract} + + last(int) : FluentIterable {abstract} + + map(Function) : FluentIterable {abstract} + } +} +LazyFluentIterable ..|> FluentIterable +SimpleFluentIterable ..|> FluentIterable +@enduml \ No newline at end of file diff --git a/fluent-interface/pom.xml b/fluent-interface/pom.xml index 94aa25999cb9..005fa9f7b831 100644 --- a/fluent-interface/pom.xml +++ b/fluent-interface/pom.xml @@ -34,6 +34,14 @@ 4.0.0 fluent-interface + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/app/App.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/app/App.java index b51f5839af1d..a36bef68b645 100644 --- a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/app/App.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/app/App.java @@ -47,57 +47,46 @@ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var integerList = List.of(1, -61, 14, -22, 18, -87, 6, 64, -82, 26, -98, 97, 45, 23, 2, -68); prettyPrint("The initial list contains: ", integerList); - var firstFiveNegatives = SimpleFluentIterable - .fromCopyOf(integerList) - .filter(negatives()) - .first(3) - .asList(); + var firstFiveNegatives = + SimpleFluentIterable.fromCopyOf(integerList).filter(negatives()).first(3).asList(); prettyPrint("The first three negative values are: ", firstFiveNegatives); - - var lastTwoPositives = SimpleFluentIterable - .fromCopyOf(integerList) - .filter(positives()) - .last(2) - .asList(); + var lastTwoPositives = + SimpleFluentIterable.fromCopyOf(integerList).filter(positives()).last(2).asList(); prettyPrint("The last two positive values are: ", lastTwoPositives); - SimpleFluentIterable - .fromCopyOf(integerList) + SimpleFluentIterable.fromCopyOf(integerList) .filter(number -> number % 2 == 0) .first() .ifPresent(evenNumber -> LOGGER.info("The first even number is: {}", evenNumber)); - - var transformedList = SimpleFluentIterable - .fromCopyOf(integerList) - .filter(negatives()) - .map(transformToString()) - .asList(); + var transformedList = + SimpleFluentIterable.fromCopyOf(integerList) + .filter(negatives()) + .map(transformToString()) + .asList(); prettyPrint("A string-mapped list of negative numbers contains: ", transformedList); - - var lastTwoOfFirstFourStringMapped = LazyFluentIterable - .from(integerList) - .filter(positives()) - .first(4) - .last(2) - .map(number -> "String[" + number + "]") - .asList(); - prettyPrint("The lazy list contains the last two of the first four positive numbers " - + "mapped to Strings: ", lastTwoOfFirstFourStringMapped); - - LazyFluentIterable - .from(integerList) + var lastTwoOfFirstFourStringMapped = + LazyFluentIterable.from(integerList) + .filter(positives()) + .first(4) + .last(2) + .map(number -> "String[" + number + "]") + .asList(); + prettyPrint( + "The lazy list contains the last two of the first four positive numbers " + + "mapped to Strings: ", + lastTwoOfFirstFourStringMapped); + + LazyFluentIterable.from(integerList) .filter(negatives()) .first(2) .last() @@ -120,10 +109,7 @@ private static void prettyPrint(String prefix, Iterable iterable) { prettyPrint(", ", prefix, iterable); } - private static void prettyPrint( - String delimiter, String prefix, - Iterable iterable - ) { + private static void prettyPrint(String delimiter, String prefix, Iterable iterable) { var joiner = new StringJoiner(delimiter, prefix, "."); iterable.forEach(e -> joiner.add(e.toString())); LOGGER.info(joiner.toString()); diff --git a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java index d97289e789ef..2ab128afa633 100644 --- a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java @@ -44,7 +44,7 @@ public interface FluentIterable extends Iterable { * the predicate. * * @param predicate the condition to test with for the filtering. If the test is negative, the - * tested object is removed by the iterator. + * tested object is removed by the iterator. * @return a filtered FluentIterable */ FluentIterable filter(Predicate predicate); @@ -82,7 +82,7 @@ public interface FluentIterable extends Iterable { * Transforms this FluentIterable into a new one containing objects of the type T. * * @param function a function that transforms an instance of E into an instance of T - * @param the target type of the transformation + * @param the target type of the transformation * @return a new FluentIterable of the new type */ FluentIterable map(Function function); @@ -98,7 +98,7 @@ public interface FluentIterable extends Iterable { * Utility method that iterates over iterable and adds the contents to a list. * * @param iterable the iterable to collect - * @param the type of the objects to iterate + * @param the type of the objects to iterate * @return a list with all objects of the given iterator */ static List copyToList(Iterable iterable) { diff --git a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java index aaf00b935e3c..12a0304a8db2 100644 --- a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java @@ -38,9 +38,7 @@ public abstract class DecoratingIterator implements Iterator { private E next; - /** - * Creates an iterator that decorates the given iterator. - */ + /** Creates an iterator that decorates the given iterator. */ public DecoratingIterator(Iterator fromIterator) { this.fromIterator = fromIterator; } diff --git a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java index f624179e05a9..971ac9a02207 100644 --- a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java @@ -44,9 +44,7 @@ public class LazyFluentIterable implements FluentIterable { private final Iterable iterable; - /** - * This constructor can be used to implement anonymous subclasses of the LazyFluentIterable. - */ + /** This constructor can be used to implement anonymous subclasses of the LazyFluentIterable. */ protected LazyFluentIterable() { iterable = this; } @@ -56,7 +54,7 @@ protected LazyFluentIterable() { * the predicate. * * @param predicate the condition to test with for the filtering. If the test is negative, the - * tested object is removed by the iterator. + * tested object is removed by the iterator. * @return a new FluentIterable object that decorates the source iterable */ @Override @@ -183,7 +181,7 @@ private void initialize() { * Transforms this FluentIterable into a new one containing objects of the type T. * * @param function a function that transforms an instance of E into an instance of T - * @param the target type of the transformation + * @param the target type of the transformation * @return a new FluentIterable of the new type */ @Override @@ -236,5 +234,4 @@ public E computeNext() { public static FluentIterable from(Iterable iterable) { return new LazyFluentIterable<>(iterable); } - } diff --git a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java index b3c912f1f7fd..a44cfb44cbfe 100644 --- a/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java +++ b/fluent-interface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java @@ -51,7 +51,7 @@ public class SimpleFluentIterable implements FluentIterable { * the predicate. * * @param predicate the condition to test with for the filtering. If the test is negative, the - * tested object is removed by the iterator. + * tested object is removed by the iterator. * @return the same FluentIterable with a filtered collection */ @Override @@ -139,7 +139,7 @@ public final FluentIterable last(int count) { * Transforms this FluentIterable into a new one containing objects of the type T. * * @param function a function that transforms an instance of E into an instance of T - * @param the target type of the transformation + * @param the target type of the transformation * @return a new FluentIterable of the new type */ @Override @@ -183,7 +183,6 @@ public void forEach(Consumer action) { iterable.forEach(action); } - @Override public Spliterator spliterator() { return iterable.spliterator(); diff --git a/fluent-interface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java index d637923dc6b9..003e4a12c950 100644 --- a/fluent-interface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java +++ b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/app/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.fluentinterface.app; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application Test Entry - */ +import org.junit.jupiter.api.Test; + +/** Application Test Entry */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java index e1e0b6291454..d85b64109dd0 100644 --- a/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java +++ b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterableTest.java @@ -38,10 +38,7 @@ import java.util.function.Consumer; import org.junit.jupiter.api.Test; -/** - * FluentIterableTest - * - */ +/** FluentIterableTest */ public abstract class FluentIterableTest { /** @@ -72,9 +69,7 @@ void testFirstEmptyCollection() { @Test void testFirstCount() { final var integers = List.of(1, 2, 3, 10, 9, 8); - final var first4 = createFluentIterable(integers) - .first(4) - .asList(); + final var first4 = createFluentIterable(integers).first(4).asList(); assertNotNull(first4); assertEquals(4, first4.size()); @@ -88,9 +83,7 @@ void testFirstCount() { @Test void testFirstCountLessItems() { final var integers = List.of(1, 2, 3); - final var first4 = createFluentIterable(integers) - .first(4) - .asList(); + final var first4 = createFluentIterable(integers).first(4).asList(); assertNotNull(first4); assertEquals(3, first4.size()); @@ -120,9 +113,7 @@ void testLastEmptyCollection() { @Test void testLastCount() { final var integers = List.of(1, 2, 3, 10, 9, 8); - final var last4 = createFluentIterable(integers) - .last(4) - .asList(); + final var last4 = createFluentIterable(integers).last(4).asList(); assertNotNull(last4); assertEquals(4, last4.size()); @@ -135,9 +126,7 @@ void testLastCount() { @Test void testLastCountLessItems() { final var integers = List.of(1, 2, 3); - final var last4 = createFluentIterable(integers) - .last(4) - .asList(); + final var last4 = createFluentIterable(integers).last(4).asList(); assertNotNull(last4); assertEquals(3, last4.size()); @@ -150,9 +139,7 @@ void testLastCountLessItems() { @Test void testFilter() { final var integers = List.of(1, 2, 3, 10, 9, 8); - final var evenItems = createFluentIterable(integers) - .filter(i -> i % 2 == 0) - .asList(); + final var evenItems = createFluentIterable(integers).filter(i -> i % 2 == 0).asList(); assertNotNull(evenItems); assertEquals(3, evenItems.size()); @@ -164,9 +151,7 @@ void testFilter() { @Test void testMap() { final var integers = List.of(1, 2, 3); - final var longs = createFluentIterable(integers) - .map(Integer::longValue) - .asList(); + final var longs = createFluentIterable(integers).map(Integer::longValue).asList(); assertNotNull(longs); assertEquals(integers.size(), longs.size()); @@ -186,7 +171,6 @@ void testForEach() { verify(consumer, times(1)).accept(2); verify(consumer, times(1)).accept(3); verifyNoMoreInteractions(consumer); - } @Test @@ -195,5 +179,4 @@ void testSpliterator() { final var split = createFluentIterable(integers).spliterator(); assertNotNull(split); } - -} \ No newline at end of file +} diff --git a/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java index e0f3dcd4d1e6..2db7ef534d03 100644 --- a/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java +++ b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterableTest.java @@ -27,15 +27,11 @@ import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; import com.iluwatar.fluentinterface.fluentiterable.FluentIterableTest; -/** - * LazyFluentIterableTest - * - */ +/** LazyFluentIterableTest */ class LazyFluentIterableTest extends FluentIterableTest { @Override protected FluentIterable createFluentIterable(Iterable integers) { return LazyFluentIterable.from(integers); } - } diff --git a/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java index 5903d1af1a48..5b1cc374d619 100644 --- a/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java +++ b/fluent-interface/src/test/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterableTest.java @@ -27,15 +27,11 @@ import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; import com.iluwatar.fluentinterface.fluentiterable.FluentIterableTest; -/** - * SimpleFluentIterableTest - * - */ +/** SimpleFluentIterableTest */ class SimpleFluentIterableTest extends FluentIterableTest { @Override protected FluentIterable createFluentIterable(Iterable integers) { return SimpleFluentIterable.fromCopyOf(integers); } - } diff --git a/flux/README.md b/flux/README.md index 95bfa2870310..e7702c839784 100644 --- a/flux/README.md +++ b/flux/README.md @@ -30,6 +30,10 @@ Wikipedia says > To support React's concept of unidirectional data flow (which might be contrasted with AngularJS's bidirectional flow), the Flux architecture was developed as an alternative to the popular model–view–controller architecture. Flux features actions which are sent through a central dispatcher to a store, and changes to the store are propagated back to the view. +Architecture diagram + +![Flux Architecture Diagram](./etc/flux-architecture-diagram.png) + ## Programmatic Example of Flux Pattern in Java The Flux design pattern is used for building client-side web applications. It advocates for a unidirectional data flow. When a user interacts with a view, the view propagates an action through a central dispatcher, to the various stores that hold the application's data and business logic, which updates all the views that are affected. @@ -83,17 +87,13 @@ In this example, when a menu item is clicked, the `MenuView` triggers a `MENU_IT This is a basic example of the Flux pattern, where actions are dispatched from the views, handled by the stores, and cause the views to update. -## Detailed Explanation of Flux Pattern with Real-World Examples - -![Flux](./etc/flux.png "Flux") - ## When to Use the Flux Pattern in Java Flux is applicable in developing client-side Java applications, where maintaining consistent data across various components and managing complex state interactions are critical. It is especially suited for applications with dynamic user interfaces that react to frequent data updates. ## Real-World Applications of Flux Pattern in Java -* Facebook extensively uses the Flux design pattern in conjunction with React to build robust, scalable user interfaces that can handle complex data updates efficiently. Many modern web applications adopt Flux or its variations (like Redux) to manage state in environments that demand high responsiveness and predictability. +* Facebook extensively uses the Flux design pattern in conjunction with React to build robust, scalable user interfaces that can handle complex data updates efficiently. * Many modern web applications adopt Flux or its variations (like Redux) to manage state in environments that demand high responsiveness and predictability. ## Benefits and Trade-offs of Flux Pattern diff --git a/flux/etc/flux-architecture-diagram.png b/flux/etc/flux-architecture-diagram.png new file mode 100644 index 000000000000..a2493da64608 Binary files /dev/null and b/flux/etc/flux-architecture-diagram.png differ diff --git a/flux/pom.xml b/flux/pom.xml index 2b8588884aa5..f0259126dc2b 100644 --- a/flux/pom.xml +++ b/flux/pom.xml @@ -34,6 +34,14 @@ flux + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/flux/src/main/java/com/iluwatar/flux/action/Action.java b/flux/src/main/java/com/iluwatar/flux/action/Action.java index e493d04dfa82..cf59d5ad9446 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/Action.java +++ b/flux/src/main/java/com/iluwatar/flux/action/Action.java @@ -27,13 +27,10 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Action is the data payload dispatched to the stores when something happens. - */ +/** Action is the data payload dispatched to the stores when something happens. */ @RequiredArgsConstructor @Getter public abstract class Action { private final ActionType type; - } diff --git a/flux/src/main/java/com/iluwatar/flux/action/ActionType.java b/flux/src/main/java/com/iluwatar/flux/action/ActionType.java index a8206972e14f..9665959d030e 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/ActionType.java +++ b/flux/src/main/java/com/iluwatar/flux/action/ActionType.java @@ -24,12 +24,8 @@ */ package com.iluwatar.flux.action; -/** - * Types of actions. - */ +/** Types of actions. */ public enum ActionType { - MENU_ITEM_SELECTED, CONTENT_CHANGED - } diff --git a/flux/src/main/java/com/iluwatar/flux/action/Content.java b/flux/src/main/java/com/iluwatar/flux/action/Content.java index d17aed00a9cb..124e8f9eda99 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/Content.java +++ b/flux/src/main/java/com/iluwatar/flux/action/Content.java @@ -26,12 +26,9 @@ import lombok.RequiredArgsConstructor; -/** - * Content items. - */ +/** Content items. */ @RequiredArgsConstructor public enum Content { - PRODUCTS("Products - This page lists the company's products."), COMPANY("Company - This page displays information about the company."); diff --git a/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java b/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java index 67afc3f2f0b7..fb3020dfa506 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java +++ b/flux/src/main/java/com/iluwatar/flux/action/ContentAction.java @@ -26,13 +26,10 @@ import lombok.Getter; -/** - * ContentAction is a concrete action. - */ +/** ContentAction is a concrete action. */ public class ContentAction extends Action { - @Getter - private final Content content; + @Getter private final Content content; public ContentAction(Content content) { super(ActionType.CONTENT_CHANGED); diff --git a/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java b/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java index 5ad3ca43b5aa..81e52213dd7e 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java +++ b/flux/src/main/java/com/iluwatar/flux/action/MenuAction.java @@ -24,16 +24,12 @@ */ package com.iluwatar.flux.action; - import lombok.Getter; -/** - * MenuAction is a concrete action. - */ +/** MenuAction is a concrete action. */ public class MenuAction extends Action { - @Getter - private final MenuItem menuItem; + @Getter private final MenuItem menuItem; public MenuAction(MenuItem menuItem) { super(ActionType.MENU_ITEM_SELECTED); diff --git a/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java b/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java index 70aabe5667a7..cc8087077e6c 100644 --- a/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java +++ b/flux/src/main/java/com/iluwatar/flux/action/MenuItem.java @@ -24,12 +24,11 @@ */ package com.iluwatar.flux.action; -/** - * Menu items. - */ +/** Menu items. */ public enum MenuItem { - - HOME("Home"), PRODUCTS("Products"), COMPANY("Company"); + HOME("Home"), + PRODUCTS("Products"), + COMPANY("Company"); private final String title; diff --git a/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java b/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java index 47a656e8941b..ca087dd420a2 100644 --- a/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java +++ b/flux/src/main/java/com/iluwatar/flux/dispatcher/Dispatcher.java @@ -34,26 +34,20 @@ import java.util.List; import lombok.Getter; -/** - * Dispatcher sends Actions to registered Stores. - */ +/** Dispatcher sends Actions to registered Stores. */ public final class Dispatcher { - @Getter - private static Dispatcher instance = new Dispatcher(); + @Getter private static Dispatcher instance = new Dispatcher(); private final List stores = new LinkedList<>(); - private Dispatcher() { - } + private Dispatcher() {} public void registerStore(Store store) { stores.add(store); } - /** - * Menu item selected handler. - */ + /** Menu item selected handler. */ public void menuItemSelected(MenuItem menuItem) { dispatchAction(new MenuAction(menuItem)); if (menuItem == MenuItem.COMPANY) { diff --git a/flux/src/main/java/com/iluwatar/flux/store/ContentStore.java b/flux/src/main/java/com/iluwatar/flux/store/ContentStore.java index 9a2e7bd9bd2a..b26146d52d95 100644 --- a/flux/src/main/java/com/iluwatar/flux/store/ContentStore.java +++ b/flux/src/main/java/com/iluwatar/flux/store/ContentStore.java @@ -30,13 +30,10 @@ import com.iluwatar.flux.action.ContentAction; import lombok.Getter; -/** - * ContentStore is a concrete store. - */ +/** ContentStore is a concrete store. */ public class ContentStore extends Store { - @Getter - private Content content = Content.PRODUCTS; + @Getter private Content content = Content.PRODUCTS; @Override public void onAction(Action action) { diff --git a/flux/src/main/java/com/iluwatar/flux/store/MenuStore.java b/flux/src/main/java/com/iluwatar/flux/store/MenuStore.java index 9a0732f0e95d..c0d8d8255a16 100644 --- a/flux/src/main/java/com/iluwatar/flux/store/MenuStore.java +++ b/flux/src/main/java/com/iluwatar/flux/store/MenuStore.java @@ -30,13 +30,10 @@ import com.iluwatar.flux.action.MenuItem; import lombok.Getter; -/** - * MenuStore is a concrete store. - */ +/** MenuStore is a concrete store. */ public class MenuStore extends Store { - @Getter - private MenuItem selected = MenuItem.HOME; + @Getter private MenuItem selected = MenuItem.HOME; @Override public void onAction(Action action) { diff --git a/flux/src/main/java/com/iluwatar/flux/store/Store.java b/flux/src/main/java/com/iluwatar/flux/store/Store.java index 313635bd69ca..879ae65d26c0 100644 --- a/flux/src/main/java/com/iluwatar/flux/store/Store.java +++ b/flux/src/main/java/com/iluwatar/flux/store/Store.java @@ -29,9 +29,7 @@ import java.util.LinkedList; import java.util.List; -/** - * Store is a data model. - */ +/** Store is a data model. */ public abstract class Store { private final List views = new LinkedList<>(); diff --git a/flux/src/main/java/com/iluwatar/flux/view/ContentView.java b/flux/src/main/java/com/iluwatar/flux/view/ContentView.java index 7f01daadb80e..66befdf78062 100644 --- a/flux/src/main/java/com/iluwatar/flux/view/ContentView.java +++ b/flux/src/main/java/com/iluwatar/flux/view/ContentView.java @@ -29,9 +29,7 @@ import com.iluwatar.flux.store.Store; import lombok.extern.slf4j.Slf4j; -/** - * ContentView is a concrete view. - */ +/** ContentView is a concrete view. */ @Slf4j public class ContentView implements View { diff --git a/flux/src/main/java/com/iluwatar/flux/view/MenuView.java b/flux/src/main/java/com/iluwatar/flux/view/MenuView.java index 378c9ed50c1f..74ae5192b4f7 100644 --- a/flux/src/main/java/com/iluwatar/flux/view/MenuView.java +++ b/flux/src/main/java/com/iluwatar/flux/view/MenuView.java @@ -30,9 +30,7 @@ import com.iluwatar.flux.store.Store; import lombok.extern.slf4j.Slf4j; -/** - * MenuView is a concrete view. - */ +/** MenuView is a concrete view. */ @Slf4j public class MenuView implements View { diff --git a/flux/src/main/java/com/iluwatar/flux/view/View.java b/flux/src/main/java/com/iluwatar/flux/view/View.java index 6e6a7cd596be..b31b20c18ffd 100644 --- a/flux/src/main/java/com/iluwatar/flux/view/View.java +++ b/flux/src/main/java/com/iluwatar/flux/view/View.java @@ -26,9 +26,7 @@ import com.iluwatar.flux.store.Store; -/** - * Views define the representation of data. - */ +/** Views define the representation of data. */ public interface View { void storeChanged(Store store); diff --git a/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java b/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java index 5e58e166f8ae..8263468c9472 100644 --- a/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java +++ b/flux/src/test/java/com/iluwatar/flux/action/ContentTest.java @@ -29,10 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * ContentTest - * - */ +/** ContentTest */ class ContentTest { @Test @@ -43,5 +40,4 @@ void testToString() { assertFalse(toString.trim().isEmpty()); } } - } diff --git a/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java b/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java index f0e2d4690a51..bf012abee931 100644 --- a/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java +++ b/flux/src/test/java/com/iluwatar/flux/action/MenuItemTest.java @@ -29,10 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * MenuItemTest - * - */ +/** MenuItemTest */ class MenuItemTest { @Test @@ -43,5 +40,4 @@ void testToString() { assertFalse(toString.trim().isEmpty()); } } - } diff --git a/flux/src/test/java/com/iluwatar/flux/app/AppTest.java b/flux/src/test/java/com/iluwatar/flux/app/AppTest.java index 0fc0049fc9c2..c594a1efb840 100644 --- a/flux/src/test/java/com/iluwatar/flux/app/AppTest.java +++ b/flux/src/test/java/com/iluwatar/flux/app/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.flux.app; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java b/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java index 8cb3e92ef839..b45de5992aef 100644 --- a/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java +++ b/flux/src/test/java/com/iluwatar/flux/dispatcher/DispatcherTest.java @@ -43,10 +43,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -/** - * DispatcherTest - * - */ +/** DispatcherTest */ class DispatcherTest { /** @@ -85,28 +82,37 @@ void testMenuItemSelected() { verifyNoMoreInteractions(store); final var actions = actionCaptor.getAllValues(); - final var menuActions = actions.stream() - .filter(a -> a.getType().equals(ActionType.MENU_ITEM_SELECTED)) - .map(a -> (MenuAction) a) - .toList(); + final var menuActions = + actions.stream() + .filter(a -> a.getType().equals(ActionType.MENU_ITEM_SELECTED)) + .map(a -> (MenuAction) a) + .toList(); - final var contentActions = actions.stream() - .filter(a -> a.getType().equals(ActionType.CONTENT_CHANGED)) - .map(a -> (ContentAction) a) - .toList(); + final var contentActions = + actions.stream() + .filter(a -> a.getType().equals(ActionType.CONTENT_CHANGED)) + .map(a -> (ContentAction) a) + .toList(); assertEquals(2, menuActions.size()); - assertEquals(1, menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.HOME::equals) - .count()); - assertEquals(1, menuActions.stream().map(MenuAction::getMenuItem) - .filter(MenuItem.COMPANY::equals).count()); + assertEquals( + 1, menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.HOME::equals).count()); + assertEquals( + 1, + menuActions.stream().map(MenuAction::getMenuItem).filter(MenuItem.COMPANY::equals).count()); assertEquals(2, contentActions.size()); - assertEquals(1, contentActions.stream().map(ContentAction::getContent) - .filter(Content.PRODUCTS::equals).count()); - assertEquals(1, contentActions.stream().map(ContentAction::getContent) - .filter(Content.COMPANY::equals).count()); - + assertEquals( + 1, + contentActions.stream() + .map(ContentAction::getContent) + .filter(Content.PRODUCTS::equals) + .count()); + assertEquals( + 1, + contentActions.stream() + .map(ContentAction::getContent) + .filter(Content.COMPANY::equals) + .count()); } - } diff --git a/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java b/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java index 0297899f01a0..6d6270ddbd71 100644 --- a/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java +++ b/flux/src/test/java/com/iluwatar/flux/store/ContentStoreTest.java @@ -38,10 +38,7 @@ import com.iluwatar.flux.view.View; import org.junit.jupiter.api.Test; -/** - * ContentStoreTest - * - */ +/** ContentStoreTest */ class ContentStoreTest { @Test @@ -62,7 +59,5 @@ void testOnAction() { verify(view, times(1)).storeChanged(eq(contentStore)); verifyNoMoreInteractions(view); assertEquals(Content.COMPANY, contentStore.getContent()); - } - } diff --git a/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java b/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java index d39394e96efe..368307602596 100644 --- a/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java +++ b/flux/src/test/java/com/iluwatar/flux/store/MenuStoreTest.java @@ -38,10 +38,7 @@ import com.iluwatar.flux.view.View; import org.junit.jupiter.api.Test; -/** - * MenuStoreTest - * - */ +/** MenuStoreTest */ class MenuStoreTest { @Test @@ -62,7 +59,5 @@ void testOnAction() { verify(view, times(1)).storeChanged(eq(menuStore)); verifyNoMoreInteractions(view); assertEquals(MenuItem.PRODUCTS, menuStore.getSelected()); - } - } diff --git a/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java b/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java index 619b8d2027fc..a6b91a95b244 100644 --- a/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java +++ b/flux/src/test/java/com/iluwatar/flux/view/ContentViewTest.java @@ -34,10 +34,7 @@ import com.iluwatar.flux.store.ContentStore; import org.junit.jupiter.api.Test; -/** - * ContentViewTest - * - */ +/** ContentViewTest */ class ContentViewTest { @Test @@ -51,5 +48,4 @@ void testStoreChanged() { verify(store, times(1)).getContent(); verifyNoMoreInteractions(store); } - } diff --git a/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java b/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java index 8b3f6cc60805..8fda7e47a57d 100644 --- a/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java +++ b/flux/src/test/java/com/iluwatar/flux/view/MenuViewTest.java @@ -38,10 +38,7 @@ import com.iluwatar.flux.store.Store; import org.junit.jupiter.api.Test; -/** - * MenuViewTest - * - */ +/** MenuViewTest */ class MenuViewTest { @Test @@ -66,7 +63,5 @@ void testItemClicked() { // We should receive a menu click action and a content changed action verify(store, times(2)).onAction(any(Action.class)); - } - } diff --git a/flyweight/README.md b/flyweight/README.md index e8e95097aeea..04d29c712f7c 100644 --- a/flyweight/README.md +++ b/flyweight/README.md @@ -30,6 +30,10 @@ Wikipedia says > In computer programming, flyweight is a software design pattern. A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects; it is a way to use objects in large numbers when a simple repeated representation would use an unacceptable amount of memory. +Sequence diagram + +![Flyweight sequence diagram](./etc/flyweight-sequence-diagram.png) + ## Programmatic Example of Flyweight Pattern in Java Alchemist's shop has shelves full of magic potions. Many of the potions are the same so there is no need to create a new object for each of them. Instead, one object instance can represent multiple shelf items so the memory footprint remains small. diff --git a/flyweight/etc/flyweight-sequence-diagram.png b/flyweight/etc/flyweight-sequence-diagram.png new file mode 100644 index 000000000000..bcf1f3489715 Binary files /dev/null and b/flyweight/etc/flyweight-sequence-diagram.png differ diff --git a/flyweight/pom.xml b/flyweight/pom.xml index 9f3a9672c2b9..26bc01369fd9 100644 --- a/flyweight/pom.xml +++ b/flyweight/pom.xml @@ -34,6 +34,14 @@ flyweight + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java b/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java index 05e1a48b47f3..42ce63c8efe8 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/AlchemistShop.java @@ -27,37 +27,33 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; -/** - * AlchemistShop holds potions on its shelves. It uses PotionFactory to provide the potions. - */ +/** AlchemistShop holds potions on its shelves. It uses PotionFactory to provide the potions. */ @Slf4j public class AlchemistShop { private final List topShelf; private final List bottomShelf; - /** - * Constructor. - */ + /** Constructor. */ public AlchemistShop() { var factory = new PotionFactory(); - topShelf = List.of( - factory.createPotion(PotionType.INVISIBILITY), - factory.createPotion(PotionType.INVISIBILITY), - factory.createPotion(PotionType.STRENGTH), - factory.createPotion(PotionType.HEALING), - factory.createPotion(PotionType.INVISIBILITY), - factory.createPotion(PotionType.STRENGTH), - factory.createPotion(PotionType.HEALING), - factory.createPotion(PotionType.HEALING) - ); - bottomShelf = List.of( - factory.createPotion(PotionType.POISON), - factory.createPotion(PotionType.POISON), - factory.createPotion(PotionType.POISON), - factory.createPotion(PotionType.HOLY_WATER), - factory.createPotion(PotionType.HOLY_WATER) - ); + topShelf = + List.of( + factory.createPotion(PotionType.INVISIBILITY), + factory.createPotion(PotionType.INVISIBILITY), + factory.createPotion(PotionType.STRENGTH), + factory.createPotion(PotionType.HEALING), + factory.createPotion(PotionType.INVISIBILITY), + factory.createPotion(PotionType.STRENGTH), + factory.createPotion(PotionType.HEALING), + factory.createPotion(PotionType.HEALING)); + bottomShelf = + List.of( + factory.createPotion(PotionType.POISON), + factory.createPotion(PotionType.POISON), + factory.createPotion(PotionType.POISON), + factory.createPotion(PotionType.HOLY_WATER), + factory.createPotion(PotionType.HOLY_WATER)); } /** @@ -78,9 +74,7 @@ public final List getBottomShelf() { return List.copyOf(this.bottomShelf); } - /** - * Drink all the potions. - */ + /** Drink all the potions. */ public void drinkPotions() { LOGGER.info("Drinking top shelf potions"); topShelf.forEach(Potion::drink); diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/HealingPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/HealingPotion.java index 9c63e4a8d919..9fb66df96155 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/HealingPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/HealingPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * HealingPotion. - */ +/** HealingPotion. */ @Slf4j public class HealingPotion implements Potion { diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/HolyWaterPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/HolyWaterPotion.java index 47d1cd4af8e7..73822c8bff2e 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/HolyWaterPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/HolyWaterPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * HolyWaterPotion. - */ +/** HolyWaterPotion. */ @Slf4j public class HolyWaterPotion implements Potion { diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/InvisibilityPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/InvisibilityPotion.java index 09a3d83a237d..1e5ada3cc2f4 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/InvisibilityPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/InvisibilityPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * InvisibilityPotion. - */ +/** InvisibilityPotion. */ @Slf4j public class InvisibilityPotion implements Potion { diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/PoisonPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/PoisonPotion.java index 8123053f8f7f..a25bfc6fcee9 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/PoisonPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/PoisonPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * PoisonPotion. - */ +/** PoisonPotion. */ @Slf4j public class PoisonPotion implements Potion { diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/Potion.java b/flyweight/src/main/java/com/iluwatar/flyweight/Potion.java index edbf07789834..fa1af252b9af 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/Potion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/Potion.java @@ -1,33 +1,31 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.flyweight; - -/** - * Interface for Potions. - */ -public interface Potion { - - void drink(); -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.flyweight; + +/** Interface for Potions. */ +public interface Potion { + + void drink(); +} diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/PotionFactory.java b/flyweight/src/main/java/com/iluwatar/flyweight/PotionFactory.java index 582cf8b90b67..2ac4e58a1bee 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/PotionFactory.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/PotionFactory.java @@ -1,61 +1,60 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.flyweight; - -import java.util.EnumMap; -import java.util.Map; - -/** - * PotionFactory is the Flyweight in this example. It minimizes memory use by sharing object - * instances. It holds a map of potion instances and new potions are created only when none of the - * type already exists. - */ -public class PotionFactory { - - private final Map potions; - - public PotionFactory() { - potions = new EnumMap<>(PotionType.class); - } - - Potion createPotion(PotionType type) { - var potion = potions.get(type); - if (potion == null) { - switch (type) { - case HEALING -> potion = new HealingPotion(); - case HOLY_WATER -> potion = new HolyWaterPotion(); - case INVISIBILITY -> potion = new InvisibilityPotion(); - case POISON -> potion = new PoisonPotion(); - case STRENGTH -> potion = new StrengthPotion(); - default -> { - } - } - if (potion != null) { - potions.put(type, potion); - } - } - return potion; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.flyweight; + +import java.util.EnumMap; +import java.util.Map; + +/** + * PotionFactory is the Flyweight in this example. It minimizes memory use by sharing object + * instances. It holds a map of potion instances and new potions are created only when none of the + * type already exists. + */ +public class PotionFactory { + + private final Map potions; + + public PotionFactory() { + potions = new EnumMap<>(PotionType.class); + } + + Potion createPotion(PotionType type) { + var potion = potions.get(type); + if (potion == null) { + switch (type) { + case HEALING -> potion = new HealingPotion(); + case HOLY_WATER -> potion = new HolyWaterPotion(); + case INVISIBILITY -> potion = new InvisibilityPotion(); + case POISON -> potion = new PoisonPotion(); + case STRENGTH -> potion = new StrengthPotion(); + default -> {} + } + if (potion != null) { + potions.put(type, potion); + } + } + return potion; + } +} diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/PotionType.java b/flyweight/src/main/java/com/iluwatar/flyweight/PotionType.java index b45fd2268746..da73c1f1caa1 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/PotionType.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/PotionType.java @@ -1,33 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.flyweight; - -/** - * Enumeration for potion types. - */ -public enum PotionType { - - HEALING, INVISIBILITY, STRENGTH, HOLY_WATER, POISON -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.flyweight; + +/** Enumeration for potion types. */ +public enum PotionType { + HEALING, + INVISIBILITY, + STRENGTH, + HOLY_WATER, + POISON +} diff --git a/flyweight/src/main/java/com/iluwatar/flyweight/StrengthPotion.java b/flyweight/src/main/java/com/iluwatar/flyweight/StrengthPotion.java index 22dfa24ede15..57a880b9f0e9 100644 --- a/flyweight/src/main/java/com/iluwatar/flyweight/StrengthPotion.java +++ b/flyweight/src/main/java/com/iluwatar/flyweight/StrengthPotion.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * StrengthPotion. - */ +/** StrengthPotion. */ @Slf4j public class StrengthPotion implements Potion { diff --git a/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java b/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java index f5580184e733..b5aff254afb3 100644 --- a/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java +++ b/flyweight/src/test/java/com/iluwatar/flyweight/AlchemistShopTest.java @@ -31,10 +31,7 @@ import java.util.HashSet; import org.junit.jupiter.api.Test; -/** - * AlchemistShopTest - * - */ +/** AlchemistShopTest */ class AlchemistShopTest { @Test diff --git a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java index 59ef84b3d365..d2960fb15a79 100644 --- a/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java +++ b/flyweight/src/test/java/com/iluwatar/flyweight/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.flyweight; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/front-controller/README.md b/front-controller/README.md index b4576c7caf09..4bf3f2e4100b 100644 --- a/front-controller/README.md +++ b/front-controller/README.md @@ -34,6 +34,10 @@ Wikipedia says > The front controller software design pattern is listed in several pattern catalogs and is related to the design of web applications. It is "a controller that handles all requests for a website", which is a useful structure for web application developers to achieve flexibility and reuse without code redundancy. +Architecture diagram + +![Front Controller Architecture Diagram](./etc/front-controller-architecture-diagram.png) + ## Programmatic Example of Front Controller Pattern in Java The Front Controller design pattern is a pattern that provides a centralized entry point for handling all requests in a web application. It ensures that request handling is managed consistently and efficiently across an application. @@ -104,10 +108,6 @@ In this example, when a request is received, the `FrontController` delegates the This is a basic example of the Front Controller pattern, where all requests are handled by a single controller and dispatcher, ensuring consistent and efficient request handling. -## Detailed Explanation of Front Controller Pattern with Real-World Examples - -![Front Controller](./etc/front-controller.png "Front Controller") - ## When to Use the Front Controller Pattern in Java * The Front Controller design pattern is particularly useful for Java web applications that require a centralized mechanism for request handling. diff --git a/front-controller/etc/front-controller-architecture-diagram.png b/front-controller/etc/front-controller-architecture-diagram.png new file mode 100644 index 000000000000..1caa0dc3b45d Binary files /dev/null and b/front-controller/etc/front-controller-architecture-diagram.png differ diff --git a/front-controller/pom.xml b/front-controller/pom.xml index c9eac51babb2..ba29e314bcc6 100644 --- a/front-controller/pom.xml +++ b/front-controller/pom.xml @@ -34,6 +34,14 @@ front-controller + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java b/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java index 6ba13a46ff78..2016c9d4d593 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ApplicationException.java @@ -26,13 +26,10 @@ import java.io.Serial; -/** - * Custom exception type. - */ +/** Custom exception type. */ public class ApplicationException extends RuntimeException { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; public ApplicationException(Throwable cause) { super(cause); diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java index ec3a120c0e10..a062e340577a 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherCommand.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Command for archers. - */ +/** Command for archers. */ public class ArcherCommand implements Command { @Override diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java index 9b7d96d9d9b1..794ecccb3991 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ArcherView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * View for archers. - */ +/** View for archers. */ @Slf4j public class ArcherView implements View { diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java index 6d9c63dc367a..6d032f99929f 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultCommand.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Command for catapults. - */ +/** Command for catapults. */ public class CatapultCommand implements Command { @Override diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java index 59c56c5ddb0d..68a02460a6c1 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/CatapultView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * View for catapults. - */ +/** View for catapults. */ @Slf4j public class CatapultView implements View { diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/Command.java b/front-controller/src/main/java/com/iluwatar/front/controller/Command.java index 65e3359fe266..a55b978485ef 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/Command.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/Command.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Commands are the intermediary between requests and views. - */ +/** Commands are the intermediary between requests and views. */ public interface Command { void process(); diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/Dispatcher.java b/front-controller/src/main/java/com/iluwatar/front/controller/Dispatcher.java index 8b9644ab8b3e..5b9da51b0abe 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/Dispatcher.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/Dispatcher.java @@ -69,4 +69,4 @@ static Class getCommandClass(String request) { return UnknownCommand.class; } } -} \ No newline at end of file +} diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java b/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java index 54e7119058a7..3e1a1d1b88b1 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/ErrorView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * View for errors. - */ +/** View for errors. */ @Slf4j public class ErrorView implements View { diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java b/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java index bc8b4e344d4f..293cedba4e39 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/FrontController.java @@ -25,9 +25,9 @@ package com.iluwatar.front.controller; /** - * The FrontController is responsible for handling all incoming requests. It delegates - * the processing of requests to the Dispatcher, which then determines the appropriate - * command and view to render the correct response. + * The FrontController is responsible for handling all incoming requests. It delegates the + * processing of requests to the Dispatcher, which then determines the appropriate command and view + * to render the correct response. */ public class FrontController { diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java b/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java index 1cb7060b2831..56d2b60a16f9 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/UnknownCommand.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Default command in case the mapping is not successful. - */ +/** Default command in case the mapping is not successful. */ public class UnknownCommand implements Command { @Override diff --git a/front-controller/src/main/java/com/iluwatar/front/controller/View.java b/front-controller/src/main/java/com/iluwatar/front/controller/View.java index 187ceeb7c609..cc741b9e6db0 100644 --- a/front-controller/src/main/java/com/iluwatar/front/controller/View.java +++ b/front-controller/src/main/java/com/iluwatar/front/controller/View.java @@ -24,9 +24,7 @@ */ package com.iluwatar.front.controller; -/** - * Views are the representations rendered for the user. - */ +/** Views are the representations rendered for the user. */ public interface View { void display(); diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java index ad2abda468ef..287398cc5763 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.front.controller; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java index 38dea3316e39..91e071f8be4e 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/ApplicationExceptionTest.java @@ -28,10 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * ApplicationExceptionTest - * - */ +/** ApplicationExceptionTest */ class ApplicationExceptionTest { @Test @@ -39,5 +36,4 @@ void testCause() { final var cause = new Exception(); assertSame(cause, new ApplicationException(cause).getCause()); } - -} \ No newline at end of file +} diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java index 8232b2b76017..7f7a3d39062c 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/CommandTest.java @@ -33,10 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * CommandTest - * - */ +/** CommandTest */ class CommandTest { private InMemoryAppender appender; @@ -53,14 +50,13 @@ void tearDown() { static List dataProvider() { return List.of( - new Object[]{"Archer", "Displaying archers"}, - new Object[]{"Catapult", "Displaying catapults"}, - new Object[]{"NonExistentCommand", "Error 500"} - ); + new Object[] {"Archer", "Displaying archers"}, + new Object[] {"Catapult", "Displaying catapults"}, + new Object[] {"NonExistentCommand", "Error 500"}); } /** - * @param request The request that's been tested + * @param request The request that's been tested * @param displayMessage The expected display message */ @ParameterizedTest @@ -72,5 +68,4 @@ void testDisplay(String request, String displayMessage) { assertEquals(displayMessage, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/DispatcherTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/DispatcherTest.java index c60aa899d335..64c611c49442 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/DispatcherTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/DispatcherTest.java @@ -24,12 +24,12 @@ */ package com.iluwatar.front.controller; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class DispatcherTest { private Dispatcher dispatcher; diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java index 1b5519db28aa..b4163aef4c5a 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/FrontControllerTest.java @@ -33,10 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * FrontControllerTest - * - */ +/** FrontControllerTest */ class FrontControllerTest { private InMemoryAppender appender; @@ -53,14 +50,13 @@ void tearDown() { static List dataProvider() { return List.of( - new Object[]{new ArcherCommand(), "Displaying archers"}, - new Object[]{new CatapultCommand(), "Displaying catapults"}, - new Object[]{new UnknownCommand(), "Error 500"} - ); + new Object[] {new ArcherCommand(), "Displaying archers"}, + new Object[] {new CatapultCommand(), "Displaying catapults"}, + new Object[] {new UnknownCommand(), "Error 500"}); } /** - * @param command The command that's been tested + * @param command The command that's been tested * @param displayMessage The expected display message */ @ParameterizedTest @@ -71,5 +67,4 @@ void testDisplay(Command command, String displayMessage) { assertEquals(displayMessage, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java b/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java index d95c0bbffb79..09cd1e21e617 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/ViewTest.java @@ -33,10 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * ViewTest - * - */ +/** ViewTest */ class ViewTest { private InMemoryAppender appender; @@ -53,14 +50,13 @@ void tearDown() { static List dataProvider() { return List.of( - new Object[]{new ArcherView(), "Displaying archers"}, - new Object[]{new CatapultView(), "Displaying catapults"}, - new Object[]{new ErrorView(), "Error 500"} - ); + new Object[] {new ArcherView(), "Displaying archers"}, + new Object[] {new CatapultView(), "Displaying catapults"}, + new Object[] {new ErrorView(), "Error 500"}); } /** - * @param view The view that's been tested + * @param view The view that's been tested * @param displayMessage The expected display message */ @ParameterizedTest @@ -71,5 +67,4 @@ void testDisplay(View view, String displayMessage) { assertEquals(displayMessage, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java b/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java index ca0a5ccbfa29..86268d4c38c8 100644 --- a/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java +++ b/front-controller/src/test/java/com/iluwatar/front/controller/utils/InMemoryAppender.java @@ -27,13 +27,11 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; -import org.slf4j.LoggerFactory; import java.util.LinkedList; import java.util.List; +import org.slf4j.LoggerFactory; -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/function-composition/README.md b/function-composition/README.md index 3398f4e9f2ce..961aaa6504c1 100644 --- a/function-composition/README.md +++ b/function-composition/README.md @@ -7,21 +7,18 @@ language: en tag: - Code simplification - Composition - - Decoupling - Functional decomposition - - Lambda - Reusability --- ## Also known as * Function Chaining -* Function Pipelining -* Functional Composition +* Higher-Order Function Wrapping ## Intent of Function Composition Design Pattern -The Function Composition design pattern in Java enables the creation of complex functions by combining simpler ones. This enhances modular code and reusability, crucial for maintainable software development. +Combine multiple small functions into a single operation that executes them in a sequence, producing a new function as the result. ## Detailed Explanation of Function Composition Pattern with Real-World Examples @@ -39,26 +36,30 @@ Wikipedia says > Function composition is an act or mechanism to combine simple functions to build more complicated ones. Like the usual composition of functions in mathematics, the result of each function is passed as the argument of the next, and the result of the last one is the result of the whole. +Sequence diagram + +![Function Composition sequence diagram](./etc/function-composition-sequence-diagram.png) + ## Programmatic Example of Function Composition Pattern in Java -In the functional programming paradigm, function composition is a powerful technique. For instance, in Java, you can use higher-order functions to compose operations like multiplying and squaring numbers. +In the functional programming paradigm, function composition is a powerful technique. For instance, in Java, you can use higher-order functions to combine operations like multiplying and squaring numbers. Using Java's functional interfaces, we can define simple functions and compose them. Here's how function composition works in Java. -Let's start with defining two simple functions. In this case, we have a function `timesTwo` that multiplies its input by 2, and a function `square` that squares its input. +Let's start with defining two simple functions. In this case, we have a function `timesTwo` that multiplies its input by 2, and a function `square` that squares its input: ```java Function timesTwo = x -> x * 2; Function square = x -> x * x; ``` -Next, we use the `FunctionComposer` class to compose these two functions into a new function. The `composeFunctions` method takes two functions as arguments and returns a new function that is the composition of the input functions. +Next, we use the `FunctionComposer` class to compose these two functions into a new function. The `composeFunctions` method takes two functions as arguments and returns a new function that is the composition of the input functions: ```java Function composedFunction = FunctionComposer.composeFunctions(timesTwo, square); ``` -Finally, we apply the composed function to an input value. In this case, we apply it to the number 3. The result is the square of the number 3 multiplied by 2, which is 36. +Finally, we apply the composed function to an input value. In this case, we apply it to the number 3. The result is the square of the number 3 multiplied by 2, which is 36: ```java public static void main(String[] args) { @@ -81,18 +82,13 @@ Result of composing 'timesTwo' and 'square' functions applied to 3 is: 36 This example demonstrates how the Function Composition pattern can be used to create complex functions by composing simpler ones, enhancing modularity and reusability of function-based logic. -## Function Composition Pattern Sequence diagram - -![Functional Composition Diagram](./etc/function.composition.urm.png "Functional Composition") - ## When to Use the Function Composition Pattern in Java Use the Function Composition pattern when: -* You want to create a pipeline of operations in Java. This enhances code clarity and quality by structuring complex logic into simpler, reusable components. -* You are working in a functional programming environment or a language that supports higher-order functions. -* When you want to avoid deep nesting of function calls and instead build a pipeline of operations. -* When aiming to promote immutability and side-effect-free functions in your design. +* When you want to build complex transformations by chaining smaller, reusable functions in Java. +* When the logic is best expressed through a series of operations that naturally feed one into another. +* When you want to reduce code duplication and improve readability by isolating each operation in its own function. ## Function Composition Pattern Java Tutorials @@ -101,31 +97,28 @@ Use the Function Composition pattern when: ## Real-World Applications of Function Composition Pattern in Java -* Stream processing in Java 8 and above -* Query builders in ORM libraries -* Middleware composition in web frameworks +* Java’s Stream API, where map and filter are composed for data transformations. +* Google Guava’s Function utilities. +* Apache Commons libraries that provide utilities for chaining functions. ## Benefits and Trade-offs of Function Composition Pattern Benefits: -* High reusability of composed functions. -* Increased modularity, making complex functions easier to understand and maintain. -* Flexible and dynamic creation of function pipelines at runtime. -* Enhances readability by structuring code in a linear, declarative manner. -* Facilitates easier testing of individual functions. +* Encourages highly modular and reusable code. +* Simplifies complex logic by breaking it down into smaller, testable units. +* Makes the code more expressive and easier to maintain. Trade-offs: -* Potentially higher complexity when debugging composed functions. -* Overhead from creating and managing multiple function objects in memory-intensive scenarios. -* May require a paradigm shift for developers unfamiliar with functional programming concepts. +* Excessive chaining can reduce readability if taken too far. +* May introduce performance overhead due to multiple function calls. +* Errors can be harder to trace in a deeply composed function pipeline. ## Related Java Design Patterns * [Chain of Responsibility](https://java-design-patterns.com/patterns/chain-of-responsibility/) - Both patterns allow processing to be broken down into a series of steps, but Functional Composition focuses on function composition rather than responsibility delegation. -* [Decorator](https://java-design-patterns.com/patterns/decorator/) - Similar in combining behaviors, but Decorator applies additional behavior to objects, while Functional Composition builds new functions. -* [Strategy](https://java-design-patterns.com/patterns/strategy/) - Provides interchangeable functions (strategies), which can be composed in Functional Composition. +* [Composite](https://java-design-patterns.com/patterns/composite/): Also deals with combining smaller components, though it is typically about object structure rather than function operations. ## References and Credits diff --git a/function-composition/etc/function-composition-sequence-diagram.png b/function-composition/etc/function-composition-sequence-diagram.png new file mode 100644 index 000000000000..4d8410d2635d Binary files /dev/null and b/function-composition/etc/function-composition-sequence-diagram.png differ diff --git a/function-composition/etc/function-composition.urm.puml b/function-composition/etc/function-composition.urm.puml new file mode 100644 index 000000000000..79b2a898fd12 --- /dev/null +++ b/function-composition/etc/function-composition.urm.puml @@ -0,0 +1,12 @@ +@startuml +package com.iluwatar.function.composition { + class App { + + App() + + main(args : String[]) {static} + } + class FunctionComposer { + + FunctionComposer() + + composeFunctions(f1 : Function, f2 : Function) : Function {static} + } +} +@enduml \ No newline at end of file diff --git a/function-composition/pom.xml b/function-composition/pom.xml index da803b723898..abf504b2979d 100644 --- a/function-composition/pom.xml +++ b/function-composition/pom.xml @@ -35,13 +35,16 @@ function-composition - org.junit.jupiter - junit-jupiter-engine - test + org.slf4j + slf4j-api - junit - junit + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/function-composition/src/main/java/com/iluwatar/function/composition/App.java b/function-composition/src/main/java/com/iluwatar/function/composition/App.java index da55135bd247..c5551f4f9575 100644 --- a/function-composition/src/main/java/com/iluwatar/function/composition/App.java +++ b/function-composition/src/main/java/com/iluwatar/function/composition/App.java @@ -25,11 +25,10 @@ package com.iluwatar.function.composition; import java.util.function.Function; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; -/** - * Main application class to demonstrate the use of function composition. - */ +/** Main application class to demonstrate the use of function composition. */ +@Slf4j public class App { /** @@ -38,13 +37,13 @@ public class App { * @param args command line arguments (not used) */ public static void main(String[] args) { - final var logger = LoggerFactory.getLogger(App.class); Function timesTwo = x -> x * 2; Function square = x -> x * x; - Function composedFunction = FunctionComposer.composeFunctions(timesTwo, square); + Function composedFunction = + FunctionComposer.composeFunctions(timesTwo, square); int result = composedFunction.apply(3); - logger.info("Result of composing 'timesTwo' and 'square' functions applied to 3 is: " + result); + LOGGER.info("Result of composing 'timesTwo' and 'square' functions applied to 3 is: " + result); } } diff --git a/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java b/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java index 725903f9c6ea..c440ae759a20 100644 --- a/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java +++ b/function-composition/src/main/java/com/iluwatar/function/composition/FunctionComposer.java @@ -27,20 +27,23 @@ import java.util.function.Function; /** - * Class for composing functions using the Function Composition pattern. - * Provides a static method to compose two functions using the 'andThen' method. + * Class for composing functions using the Function Composition pattern. Provides a static method to + * compose two functions using the 'andThen' method. */ public class FunctionComposer { + private FunctionComposer() {} + /** - * Composes two functions where the output of the first function becomes - * the input of the second function. + * Composes two functions where the output of the first function becomes the input of the second + * function. * * @param f1 the first function to apply * @param f2 the second function to apply after the first * @return a composed function that applies f1 and then f2 */ - public static Function composeFunctions(Function f1, Function f2) { + public static Function composeFunctions( + Function f1, Function f2) { return f1.andThen(f2); } } diff --git a/function-composition/src/test/java/com/iluwatar/function/composition/AppTest.java b/function-composition/src/test/java/com/iluwatar/function/composition/AppTest.java index 82d5435680da..f1640675c761 100644 --- a/function-composition/src/test/java/com/iluwatar/function/composition/AppTest.java +++ b/function-composition/src/test/java/com/iluwatar/function/composition/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.function.composition; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java b/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java index af8950252341..dbc4f0903236 100644 --- a/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java +++ b/function-composition/src/test/java/com/iluwatar/function/composition/FunctionComposerTest.java @@ -24,79 +24,73 @@ */ package com.iluwatar.function.composition; -import static org.junit.Assert.assertEquals; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.util.function.Function; +import org.junit.jupiter.api.Test; -/** - * Test class for FunctionComposer. - */ -public class FunctionComposerTest { +/** Test class for FunctionComposer. */ +class FunctionComposerTest { - /** - * Tests the composition of two functions. - */ + /** Tests the composition of two functions. */ @Test - public void testComposeFunctions() { + void testComposeFunctions() { Function timesTwo = x -> x * 2; Function square = x -> x * x; Function composed = FunctionComposer.composeFunctions(timesTwo, square); - assertEquals("Expected output of composed functions is 36", 36, (int) composed.apply(3)); + assertEquals(36, composed.apply(3), "Expected output of composed functions is 36"); } - /** - * Tests function composition with identity function. - */ + /** Tests function composition with identity function. */ @Test - public void testComposeWithIdentity() { + void testComposeWithIdentity() { Function identity = Function.identity(); Function timesThree = x -> x * 3; - Function composedLeft = FunctionComposer.composeFunctions(identity, timesThree); - Function composedRight = FunctionComposer.composeFunctions(timesThree, identity); + Function composedLeft = + FunctionComposer.composeFunctions(identity, timesThree); + Function composedRight = + FunctionComposer.composeFunctions(timesThree, identity); - assertEquals("Composition with identity on the left should be the same", 9, (int) composedLeft.apply(3)); - assertEquals("Composition with identity on the right should be the same", 9, (int) composedRight.apply(3)); + assertEquals( + 9, composedLeft.apply(3), "Composition with identity on the left should be the same"); + assertEquals( + 9, composedRight.apply(3), "Composition with identity on the right should be the same"); } - /** - * Tests function composition resulting in zero. - */ + /** Tests function composition resulting in zero. */ @Test - public void testComposeToZero() { + void testComposeToZero() { Function multiply = x -> x * 10; Function toZero = x -> 0; Function composed = FunctionComposer.composeFunctions(multiply, toZero); - assertEquals("Expected output of function composition leading to zero is 0", 0, (int) composed.apply(5)); + assertEquals( + 0, composed.apply(5), "Expected output of function composition leading to zero is 0"); } - /** - * Tests the composition with a negative function. - */ + /** Tests the composition with a negative function. */ @Test - public void testComposeNegative() { + void testComposeNegative() { Function negate = x -> -x; Function square = x -> x * x; Function composed = FunctionComposer.composeFunctions(negate, square); - assertEquals("Expected square of negative number to be positive", 9, (int) composed.apply(3)); + assertEquals(9, composed.apply(3), "Expected square of negative number to be positive"); } - /** - * Tests the composition of functions that cancel each other out. - */ + /** Tests the composition of functions that cancel each other out. */ @Test - public void testComposeInverseFunctions() { + void testComposeInverseFunctions() { Function timesTwo = x -> x * 2; Function half = x -> x / 2; Function composed = FunctionComposer.composeFunctions(timesTwo, half); - assertEquals("Expect the functions to cancel each other out", 5, (int) composed.apply(5)); + assertEquals(5, composed.apply(5), "Expect the functions to cancel each other out"); } } diff --git a/game-loop/README.md b/game-loop/README.md index 69d2d641193f..3de2804a8dc7 100644 --- a/game-loop/README.md +++ b/game-loop/README.md @@ -34,6 +34,10 @@ Wikipedia says > The central component of any game, from a programming standpoint, is the game loop. The game loop allows the game to run smoothly regardless of a user's input, or lack thereof. +Flowchart + +![Game Loop flowchart](./etc/game-loop-flowchart.png) + ## Programmatic Example of Game Loop Pattern in Java In our Java example, we illustrate a simple game loop controlling a bullet's movement, updating its position, ensuring smooth rendering, and responding to user inputs. The Game Loop is the main process driving all game rendering threads, present in all modern games. It handles input processing, internal status updates, rendering, AI, and other processes. Starting with a simple `Bullet` class, we demonstrate the movement of bullets in our game, focusing on their 1-dimensional position for demonstration purposes. diff --git a/game-loop/etc/game-loop-flowchart.png b/game-loop/etc/game-loop-flowchart.png new file mode 100644 index 000000000000..a3e73f75ed8b Binary files /dev/null and b/game-loop/etc/game-loop-flowchart.png differ diff --git a/game-loop/pom.xml b/game-loop/pom.xml index af782d4cec63..1ef70a6509b9 100644 --- a/game-loop/pom.xml +++ b/game-loop/pom.xml @@ -34,6 +34,14 @@ 4.0.0 game-loop + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/App.java b/game-loop/src/main/java/com/iluwatar/gameloop/App.java index f0193739642c..76ed426e6a3c 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/App.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/App.java @@ -27,20 +27,19 @@ import lombok.extern.slf4j.Slf4j; /** - * A game loop runs continuously during gameplay. Each turn of the loop, it processes - * user input without blocking, updates the game state, and renders the game. It tracks - * the passage of time to control the rate of gameplay. + * A game loop runs continuously during gameplay. Each turn of the loop, it processes user input + * without blocking, updates the game state, and renders the game. It tracks the passage of time to + * control the rate of gameplay. */ @Slf4j public class App { - /** - * Each type of game loop will run for 2 seconds. - */ + /** Each type of game loop will run for 2 seconds. */ private static final int GAME_LOOP_DURATION_TIME = 2000; /** * Program entry point. + * * @param args runtime arguments */ public static void main(String[] args) { @@ -71,5 +70,4 @@ public static void main(String[] args) { LOGGER.error(e.getMessage()); } } - } diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java b/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java index 90484b2d0655..00b1a8b773b7 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/Bullet.java @@ -27,14 +27,10 @@ import lombok.Getter; import lombok.Setter; -/** - * Bullet object class. - */ +/** Bullet object class. */ public class Bullet { - @Getter - @Setter - private float position; + @Getter @Setter private float position; public Bullet() { position = 0.0f; diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java index 3f775a9d2a77..58c4e0dfdca2 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/FixedStepGameLoop.java @@ -25,15 +25,13 @@ package com.iluwatar.gameloop; /** - * For fixed-step game loop, a certain amount of real time has elapsed since the - * last turn of the game loop. This is how much game time need to be simulated for - * the game’s “now” to catch up with the player’s. + * For fixed-step game loop, a certain amount of real time has elapsed since the last turn of the + * game loop. This is how much game time need to be simulated for the game’s “now” to catch up with + * the player’s. */ public class FixedStepGameLoop extends GameLoop { - /** - * 20 ms per frame = 50 FPS. - */ + /** 20 ms per frame = 50 FPS. */ private static final long MS_PER_FRAME = 20; @Override diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java index 12117e3b17c5..a60eaf547066 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/FrameBasedGameLoop.java @@ -25,12 +25,11 @@ package com.iluwatar.gameloop; /** - * Frame-based game loop is the easiest implementation. The loop always keeps spinning - * for the following three processes: processInput, update and render. The problem with - * it is you have no control over how fast the game runs. On a fast machine, that loop - * will spin so fast users won’t be able to see what’s going on. On a slow machine, the - * game will crawl. If you have a part of the game that’s content-heavy or does more AI - * or physics, the game will actually play slower there. + * Frame-based game loop is the easiest implementation. The loop always keeps spinning for the + * following three processes: processInput, update and render. The problem with it is you have no + * control over how fast the game runs. On a fast machine, that loop will spin so fast users won’t + * be able to see what’s going on. On a slow machine, the game will crawl. If you have a part of the + * game that’s content-heavy or does more AI or physics, the game will actually play slower there. */ public class FrameBasedGameLoop extends GameLoop { @@ -44,11 +43,10 @@ protected void processGameLoop() { } /** - * Each time when update() is invoked, a new frame is created, and the bullet will be - * moved 0.5f away from the current position. + * Each time when update() is invoked, a new frame is created, and the bullet will be moved 0.5f + * away from the current position. */ protected void update() { controller.moveBullet(0.5f); } - } diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java index 1ed2304ecb80..872aa8e8ce50 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameController.java @@ -25,16 +25,14 @@ package com.iluwatar.gameloop; /** - * Update and render objects in the game. Here we add a Bullet object to the - * game system to show how the game loop works. + * Update and render objects in the game. Here we add a Bullet object to the game system to show how + * the game loop works. */ public class GameController { protected final Bullet bullet; - /** - * Initialize Bullet instance. - */ + /** Initialize Bullet instance. */ public GameController() { bullet = new Bullet(); } @@ -57,6 +55,4 @@ public void moveBullet(float offset) { public float getBulletPosition() { return bullet.getPosition(); } - } - diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java index 6ad8c2c52c8b..0dcdb21f28ea 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameLoop.java @@ -28,9 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Abstract class for GameLoop implementation class. - */ +/** Abstract class for GameLoop implementation class. */ public abstract class GameLoop { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -39,26 +37,20 @@ public abstract class GameLoop { protected final GameController controller; - /** - * Initialize game status to be stopped. - */ + /** Initialize game status to be stopped. */ protected GameLoop() { controller = new GameController(); status = GameStatus.STOPPED; } - /** - * Run game loop. - */ + /** Run game loop. */ public void run() { status = GameStatus.RUNNING; Thread gameThread = new Thread(this::processGameLoop); gameThread.start(); } - /** - * Stop game loop. - */ + /** Stop game loop. */ public void stop() { status = GameStatus.STOPPED; } @@ -73,9 +65,8 @@ public boolean isGameRunning() { } /** - * Handle any user input that has happened since the last call. In order to - * simulate the situation in real-life game, here we add a random time lag. - * The time lag ranges from 50 ms to 250 ms. + * Handle any user input that has happened since the last call. In order to simulate the situation + * in real-life game, here we add a random time lag. The time lag ranges from 50 ms to 250 ms. */ protected void processInput() { try { @@ -88,18 +79,12 @@ protected void processInput() { } } - /** - * Render game frames to screen. Here we print bullet position to simulate - * this process. - */ + /** Render game frames to screen. Here we print bullet position to simulate this process. */ protected void render() { var position = controller.getBulletPosition(); logger.info("Current bullet position: {}", position); } - /** - * execute game loop logic. - */ + /** execute game loop logic. */ protected abstract void processGameLoop(); - } diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java b/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java index 8fb198c51b1f..316bb83d95d3 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/GameStatus.java @@ -24,11 +24,8 @@ */ package com.iluwatar.gameloop; -/** - * Enum class for game status. - */ +/** Enum class for game status. */ public enum GameStatus { - - RUNNING, STOPPED - + RUNNING, + STOPPED } diff --git a/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java b/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java index 98644224a34d..9c7c0f348c9c 100644 --- a/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java +++ b/game-loop/src/main/java/com/iluwatar/gameloop/VariableStepGameLoop.java @@ -25,10 +25,9 @@ package com.iluwatar.gameloop; /** - * The variable-step game loop chooses a time step to advance based on how much - * real time passed since the last frame. The longer the frame takes, the bigger - * steps the game takes. It always keeps up with real time because it will take - * bigger and bigger steps to get there. + * The variable-step game loop chooses a time step to advance based on how much real time passed + * since the last frame. The longer the frame takes, the bigger steps the game takes. It always + * keeps up with real time because it will take bigger and bigger steps to get there. */ public class VariableStepGameLoop extends GameLoop { @@ -48,5 +47,4 @@ protected void processGameLoop() { protected void update(Long elapsedTime) { controller.moveBullet(0.5f * elapsedTime / 1000); } - } diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java index 72997b744dee..9565030373bc 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/AppTest.java @@ -28,14 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * App unit test class. - */ +/** App unit test class. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java index 8cd00f286929..fdcd8a1e3cd8 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/FixedStepGameLoopTest.java @@ -26,13 +26,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -/** - * FixedStepGameLoop unit test class. - */ +/** FixedStepGameLoop unit test class. */ class FixedStepGameLoopTest { private FixedStepGameLoop gameLoop; @@ -52,5 +50,4 @@ void testUpdate() { gameLoop.update(); assertEquals(0.01f, gameLoop.controller.getBulletPosition(), 0); } - } diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java index fb93a3f0f6b2..ea2f80646dfe 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/FrameBasedGameLoopTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * FrameBasedGameLoop unit test class. - */ +/** FrameBasedGameLoop unit test class. */ class FrameBasedGameLoopTest { private FrameBasedGameLoop gameLoop; diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java index a8eef34318f4..c5855a88ea12 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/GameControllerTest.java @@ -54,5 +54,4 @@ void testMoveBullet() { void testGetBulletPosition() { assertEquals(controller.bullet.getPosition(), controller.getBulletPosition(), 0); } - } diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java index 83c631db0f22..c7dddd372bd5 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/GameLoopTest.java @@ -31,23 +31,21 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * GameLoop unit test class. - */ +/** GameLoop unit test class. */ class GameLoopTest { private GameLoop gameLoop; - /** - * Create mock implementation of GameLoop. - */ + /** Create mock implementation of GameLoop. */ @BeforeEach void setup() { - gameLoop = new GameLoop() { - @Override - protected void processGameLoop() { - } - }; + gameLoop = + new GameLoop() { + @Override + protected void processGameLoop() { + throw new UnsupportedOperationException("Not supported yet."); + } + }; } @AfterEach diff --git a/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java b/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java index 4c04eec0a6cf..b528c145c9b0 100644 --- a/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java +++ b/game-loop/src/test/java/com/iluwatar/gameloop/VariableStepGameLoopTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * VariableStepGameLoop unit test class. - */ +/** VariableStepGameLoop unit test class. */ class VariableStepGameLoopTest { private VariableStepGameLoop gameLoop; diff --git a/gateway/README.md b/gateway/README.md index 0df5d1b74e85..79c1a24f1992 100644 --- a/gateway/README.md +++ b/gateway/README.md @@ -35,6 +35,10 @@ Wikipedia says > A server that acts as an API front-end, receives API requests, enforces throttling and security policies, passes requests to the back-end service and then passes the response back to the requester. +Sequence diagram + +![Gateway sequence diagram](./etc/gateway-sequence-diagram.png) + ## Programmatic Example of Gateway Pattern in Java First, we define a `Gateway` interface. This interface represents the contract for our external services. Each service that we want to interact with will implement this interface. diff --git a/gateway/etc/gateway-sequence-diagram.png b/gateway/etc/gateway-sequence-diagram.png new file mode 100644 index 000000000000..167dff8ec690 Binary files /dev/null and b/gateway/etc/gateway-sequence-diagram.png differ diff --git a/gateway/pom.xml b/gateway/pom.xml index 88c15cd586ea..4fbde9cce9aa 100644 --- a/gateway/pom.xml +++ b/gateway/pom.xml @@ -36,13 +36,16 @@ gateway - org.junit.jupiter - junit-jupiter-engine - test + org.slf4j + slf4j-api - junit - junit + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/gateway/src/main/java/com/iluwatar/gateway/App.java b/gateway/src/main/java/com/iluwatar/gateway/App.java index 811cd4f7cc3b..5034748b20ec 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/App.java +++ b/gateway/src/main/java/com/iluwatar/gateway/App.java @@ -27,21 +27,21 @@ import lombok.extern.slf4j.Slf4j; /** - * the Gateway design pattern is a structural design pattern that provides a unified interface to a set of - * interfaces in a subsystem. It involves creating a Gateway interface that serves as a common entry point for - * interacting with various services, and concrete implementations of this interface for different external services. + * the Gateway design pattern is a structural design pattern that provides a unified interface to a + * set of interfaces in a subsystem. It involves creating a Gateway interface that serves as a + * common entry point for interacting with various services, and concrete implementations of this + * interface for different external services. * - *

    In this example, GateFactory is the factory class, and it provides a method to create different kinds of external - * services. ExternalServiceA, B, and C are virtual implementations of the external services. Each service provides its - * own implementation of the execute() method. The Gateway interface is the common interface for all external services. - * The App class serves as the main entry point for the application implementing the Gateway design pattern. Through - * the Gateway interface, the App class could call each service with much less complexity. + *

    In this example, GateFactory is the factory class, and it provides a method to create + * different kinds of external services. ExternalServiceA, B, and C are virtual implementations of + * the external services. Each service provides its own implementation of the execute() method. The + * Gateway interface is the common interface for all external services. The App class serves as the + * main entry point for the application implementing the Gateway design pattern. Through the Gateway + * interface, the App class could call each service with much less complexity. */ @Slf4j public class App { - /** - * Simulate an application calling external services. - */ + /** Simulate an application calling external services. */ public static void main(String[] args) throws Exception { GatewayFactory gatewayFactory = new GatewayFactory(); diff --git a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java index 5b90536ca8ff..892dc5d7b755 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java +++ b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceA.java @@ -24,12 +24,9 @@ */ package com.iluwatar.gateway; - import lombok.extern.slf4j.Slf4j; -/** -* ExternalServiceA is one of external services. -*/ +/** ExternalServiceA is one of external services. */ @Slf4j class ExternalServiceA implements Gateway { @Override diff --git a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java index 153cfc5b2d4c..dcd931ffb1f2 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java +++ b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceB.java @@ -24,12 +24,9 @@ */ package com.iluwatar.gateway; - import lombok.extern.slf4j.Slf4j; -/** -* ExternalServiceB is one of external services. -*/ +/** ExternalServiceB is one of external services. */ @Slf4j class ExternalServiceB implements Gateway { @Override @@ -39,4 +36,3 @@ public void execute() throws Exception { Thread.sleep(1000); } } - diff --git a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java index 6b0239f9468f..b01d79e37968 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java +++ b/gateway/src/main/java/com/iluwatar/gateway/ExternalServiceC.java @@ -24,12 +24,9 @@ */ package com.iluwatar.gateway; - import lombok.extern.slf4j.Slf4j; -/** -* ExternalServiceC is one of external services. -*/ +/** ExternalServiceC is one of external services. */ @Slf4j class ExternalServiceC implements Gateway { @Override diff --git a/gateway/src/main/java/com/iluwatar/gateway/Gateway.java b/gateway/src/main/java/com/iluwatar/gateway/Gateway.java index 40dc473d553e..36627681c859 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/Gateway.java +++ b/gateway/src/main/java/com/iluwatar/gateway/Gateway.java @@ -24,9 +24,7 @@ */ package com.iluwatar.gateway; -/** - * Service interface. - */ +/** Service interface. */ interface Gateway { void execute() throws Exception; -} \ No newline at end of file +} diff --git a/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java b/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java index 0f8c12ede17f..2501e5f72183 100644 --- a/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java +++ b/gateway/src/main/java/com/iluwatar/gateway/GatewayFactory.java @@ -28,8 +28,9 @@ import java.util.Map; /** - * The "GatewayFactory" class is responsible for providing different external services in this Gateway design pattern - * example. It allows clients to register and retrieve specific gateways based on unique keys. + * The "GatewayFactory" class is responsible for providing different external services in this + * Gateway design pattern example. It allows clients to register and retrieve specific gateways + * based on unique keys. */ public class GatewayFactory { private Map gateways = new HashMap<>(); diff --git a/gateway/src/test/java/com/iluwatar/gateway/AppTest.java b/gateway/src/test/java/com/iluwatar/gateway/AppTest.java index 76dc45e537b0..0776926edd38 100644 --- a/gateway/src/test/java/com/iluwatar/gateway/AppTest.java +++ b/gateway/src/test/java/com/iluwatar/gateway/AppTest.java @@ -24,86 +24,88 @@ */ package com.iluwatar.gateway; -import org.junit.Before; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import java.util.concurrent.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class AppTest { - private GatewayFactory gatewayFactory; - private ExecutorService executorService; - @Before - public void setUp() { - gatewayFactory = new GatewayFactory(); - executorService = Executors.newFixedThreadPool(2); - gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); - gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); - gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); - } + private GatewayFactory gatewayFactory; + private ExecutorService executorService; + + @BeforeEach + void setUp() { + gatewayFactory = new GatewayFactory(); + executorService = Executors.newFixedThreadPool(2); + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + } - @Test - public void testServiceAExecution() throws InterruptedException, ExecutionException { - // Test Service A execution - Future serviceAFuture = executorService.submit(() -> { - try { + @Test + void testServiceAExecution() throws InterruptedException, ExecutionException { + // Test Service A execution + Future serviceAFuture = + executorService.submit( + () -> { + try { Gateway serviceA = gatewayFactory.getGateway("ServiceA"); serviceA.execute(); - } catch (Exception e) { + } catch (Exception e) { fail("Service A should not throw an exception."); - } - }); + } + }); - // Wait for Service A to complete - serviceAFuture.get(); - } + // Wait for Service A to complete + serviceAFuture.get(); + } - @Test - public void testServiceCExecutionWithException() throws InterruptedException, ExecutionException { - // Test Service B execution with an exception - Future serviceBFuture = executorService.submit(() -> { - try { + @Test + void testServiceCExecutionWithException() throws InterruptedException, ExecutionException { + // Test Service B execution with an exception + Future serviceBFuture = + executorService.submit( + () -> { + try { Gateway serviceB = gatewayFactory.getGateway("ServiceB"); serviceB.execute(); - } catch (Exception e) { + } catch (Exception e) { fail("Service B should not throw an exception."); - } - }); + } + }); - // Wait for Service B to complete - serviceBFuture.get(); - } + // Wait for Service B to complete + serviceBFuture.get(); + } - @Test - public void testServiceCExecution() throws InterruptedException, ExecutionException { - // Test Service C execution - Future serviceCFuture = executorService.submit(() -> { - try { + @Test + void testServiceCExecution() throws InterruptedException, ExecutionException { + // Test Service C execution + Future serviceCFuture = + executorService.submit( + () -> { + try { Gateway serviceC = gatewayFactory.getGateway("ServiceC"); serviceC.execute(); - } catch (Exception e) { + } catch (Exception e) { fail("Service C should not throw an exception."); - } - }); + } + }); - // Wait for Service C to complete - serviceCFuture.get(); - } + // Wait for Service C to complete + serviceCFuture.get(); + } - @Test - public void testServiceCError() { - try { - ExternalServiceC serviceC = (ExternalServiceC) gatewayFactory.getGateway("ServiceC"); - serviceC.error(); - fail("Service C should throw an exception."); - } catch (Exception e) { - assertEquals("Service C encountered an error", e.getMessage()); - } + @Test + void testServiceCError() { + try { + ExternalServiceC serviceC = (ExternalServiceC) gatewayFactory.getGateway("ServiceC"); + serviceC.error(); + fail("Service C should throw an exception."); + } catch (Exception e) { + assertEquals("Service C encountered an error", e.getMessage()); } + } } diff --git a/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java b/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java index c2d118cc1d1a..f5e216cb2781 100644 --- a/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java +++ b/gateway/src/test/java/com/iluwatar/gateway/ServiceFactoryTest.java @@ -24,70 +24,69 @@ */ package com.iluwatar.gateway; +import static org.junit.jupiter.api.Assertions.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class ServiceFactoryTest { - private GatewayFactory gatewayFactory; - private ExecutorService executorService; - @Before - public void setUp() { - gatewayFactory = new GatewayFactory(); - executorService = Executors.newFixedThreadPool(2); - gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); - gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); - gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); - } + private GatewayFactory gatewayFactory; + private ExecutorService executorService; - @Test - public void testGatewayFactoryRegistrationAndRetrieval() { - Gateway serviceA = gatewayFactory.getGateway("ServiceA"); - Gateway serviceB = gatewayFactory.getGateway("ServiceB"); - Gateway serviceC = gatewayFactory.getGateway("ServiceC"); + @BeforeEach + void setUp() { + gatewayFactory = new GatewayFactory(); + executorService = Executors.newFixedThreadPool(2); + gatewayFactory.registerGateway("ServiceA", new ExternalServiceA()); + gatewayFactory.registerGateway("ServiceB", new ExternalServiceB()); + gatewayFactory.registerGateway("ServiceC", new ExternalServiceC()); + } - // Check if the retrieved instances match their expected types - assertTrue("ServiceA should be an instance of ExternalServiceA", serviceA instanceof ExternalServiceA); - assertTrue("ServiceB should be an instance of ExternalServiceB", serviceB instanceof ExternalServiceB); - assertTrue("ServiceC should be an instance of ExternalServiceC", serviceC instanceof ExternalServiceC); - } + @Test + void testGatewayFactoryRegistrationAndRetrieval() { + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + Gateway serviceB = gatewayFactory.getGateway("ServiceB"); + Gateway serviceC = gatewayFactory.getGateway("ServiceC"); - @Test - public void testGatewayFactoryRegistrationWithNonExistingKey() { - Gateway nonExistingService = gatewayFactory.getGateway("NonExistingService"); - assertNull(nonExistingService); - } + // Check if the retrieved instances match their expected types + assertTrue( + serviceA instanceof ExternalServiceA, "ServiceA should be an instance of ExternalServiceA"); + assertTrue( + serviceB instanceof ExternalServiceB, "ServiceB should be an instance of ExternalServiceB"); + assertTrue( + serviceC instanceof ExternalServiceC, "ServiceC should be an instance of ExternalServiceC"); + } - @Test - public void testGatewayFactoryConcurrency() throws InterruptedException { - int numThreads = 10; - CountDownLatch latch = new CountDownLatch(numThreads); - AtomicBoolean failed = new AtomicBoolean(false); + @Test + void testGatewayFactoryRegistrationWithNonExistingKey() { + Gateway nonExistingService = gatewayFactory.getGateway("NonExistingService"); + assertNull(nonExistingService); + } - for (int i = 0; i < numThreads; i++) { - executorService.submit(() -> { - try { - Gateway serviceA = gatewayFactory.getGateway("ServiceA"); - serviceA.execute(); - } catch (Exception e) { - failed.set(true); - } finally { - latch.countDown(); - } - }); - } + @Test + void testGatewayFactoryConcurrency() throws InterruptedException { + int numThreads = 10; + CountDownLatch latch = new CountDownLatch(numThreads); + AtomicBoolean failed = new AtomicBoolean(false); - latch.await(); - assertFalse("This should not fail", failed.get()); + for (int i = 0; i < numThreads; i++) { + executorService.submit( + () -> { + try { + Gateway serviceA = gatewayFactory.getGateway("ServiceA"); + serviceA.execute(); + } catch (Exception e) { + failed.set(true); + } finally { + latch.countDown(); + } + }); } + + latch.await(); + assertFalse(failed.get(), "This should not fail"); + } } diff --git a/guarded-suspension/README.md b/guarded-suspension/README.md index f13e40684d06..af0ec85d06f1 100644 --- a/guarded-suspension/README.md +++ b/guarded-suspension/README.md @@ -35,6 +35,10 @@ Wikipedia says > In concurrent programming, Guarded Suspension manages operations requiring a lock and a precondition, delaying execution until the precondition is met. +Sequence diagram + +![Guarded Suspension sequence diagram](./etc/guarded-suspension-sequence-diagram.png) + ## Programmatic Example of Guarded Suspension Pattern in Java The `GuardedQueue` class in Java showcases concurrent programming using the Guarded Suspension pattern. It includes synchronized methods that manage thread management and synchronization, demonstrating how threads wait for the right conditions to execute. diff --git a/guarded-suspension/etc/guarded-suspension-sequence-diagram.png b/guarded-suspension/etc/guarded-suspension-sequence-diagram.png new file mode 100644 index 000000000000..ee7752815c7b Binary files /dev/null and b/guarded-suspension/etc/guarded-suspension-sequence-diagram.png differ diff --git a/guarded-suspension/pom.xml b/guarded-suspension/pom.xml index 2e4fdec921e0..099bfe1e4c70 100644 --- a/guarded-suspension/pom.xml +++ b/guarded-suspension/pom.xml @@ -35,6 +35,14 @@ jar guarded-suspension + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java index 8503751d53a8..5c6b7e0547dc 100644 --- a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java +++ b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/App.java @@ -30,14 +30,13 @@ /** * Guarded-suspension is a concurrent design pattern for handling situation when to execute some - * action we need condition to be satisfied. - * The implementation utilizes a GuardedQueue, which features two primary methods: `get` and `put`. - * The key condition governing these operations is that elements cannot be retrieved (`get`) from - * an empty queue. When a thread attempts to retrieve an element under this condition, it triggers - * the invocation of the `wait` method from the Object class, causing the thread to pause. - * Conversely, when an element is added (`put`) to the queue by another thread, it invokes the - * `notify` method. This notifies the waiting thread that it can now successfully retrieve an - * element from the queue. + * action we need condition to be satisfied. The implementation utilizes a GuardedQueue, which + * features two primary methods: `get` and `put`. The key condition governing these operations is + * that elements cannot be retrieved (`get`) from an empty queue. When a thread attempts to retrieve + * an element under this condition, it triggers the invocation of the `wait` method from the Object + * class, causing the thread to pause. Conversely, when an element is added (`put`) to the queue by + * another thread, it invokes the `notify` method. This notifies the waiting thread that it can now + * successfully retrieve an element from the queue. */ @Slf4j public class App { @@ -50,7 +49,7 @@ public static void main(String[] args) { var guardedQueue = new GuardedQueue(); var executorService = Executors.newFixedThreadPool(3); - //here we create first thread which is supposed to get from guardedQueue + // here we create first thread which is supposed to get from guardedQueue executorService.execute(guardedQueue::get); // here we wait two seconds to show that the thread which is trying diff --git a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java index c53868b32832..346e7193a6a5 100644 --- a/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java +++ b/guarded-suspension/src/main/java/com/iluwatar/guarded/suspension/GuardedQueue.java @@ -33,7 +33,8 @@ * used to handle a situation when you want to execute a method on an object which is not in a * proper state. * - * @see http://java-design-patterns.com/patterns/guarded-suspension/ + * @see http://java-design-patterns.com/patterns/guarded-suspension/ */ @Slf4j public class GuardedQueue { diff --git a/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java b/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java index a88e969680ae..edbcf514586d 100644 --- a/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java +++ b/guarded-suspension/src/test/java/com/iluwatar/guarded/suspension/GuardedQueueTest.java @@ -31,9 +31,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; -/** - * Test for Guarded Queue. - */ +/** Test for Guarded Queue. */ @Slf4j class GuardedQueueTest { private volatile Integer value; @@ -59,5 +57,4 @@ void testPut() { g.put(12); assertEquals(Integer.valueOf(12), g.get()); } - } diff --git a/half-sync-half-async/README.md b/half-sync-half-async/README.md index 784197023c59..95fec081df73 100644 --- a/half-sync-half-async/README.md +++ b/half-sync-half-async/README.md @@ -34,6 +34,10 @@ Wikipedia says > The Half-Sync/Half-Async design pattern is used to solve situations where one part of the application runs synchronously while another runs asynchronously, and the two modules need to communicate with each other. +Sequence diagram + +![Half-Sync/Half-Async sequence diagram](./etc/half-sync-half-async-sequence-diagram.png) + ## Programmatic Example of Half-Sync/Half-Async Pattern in Java The Half-Sync/Half-Async design pattern is a concurrency pattern that separates synchronous and asynchronous processing in a system, simplifying the programming model without affecting performance. It's particularly useful in scenarios where you have a mix of short, mid, and long duration tasks. diff --git a/half-sync-half-async/etc/half-sync-half-async-sequence-diagram.png b/half-sync-half-async/etc/half-sync-half-async-sequence-diagram.png new file mode 100644 index 000000000000..f7fdabbe242a Binary files /dev/null and b/half-sync-half-async/etc/half-sync-half-async-sequence-diagram.png differ diff --git a/half-sync-half-async/pom.xml b/half-sync-half-async/pom.xml index 28cbf20239e0..33d6803db3da 100644 --- a/half-sync-half-async/pom.xml +++ b/half-sync-half-async/pom.xml @@ -34,6 +34,14 @@ half-sync-half-async + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java index 5d2c31290b81..1a8baafbc1ab 100644 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/App.java @@ -43,13 +43,14 @@ * *

    APPLICABILITY
    * UNIX network subsystems - In operating systems network operations are carried out asynchronously - * with help of hardware level interrupts.
    CORBA - At the asynchronous layer one thread is - * associated with each socket that is connected to the client. Thread blocks waiting for CORBA - * requests from the client. On receiving request it is inserted in the queuing layer which is then - * picked up by synchronous layer which processes the request and sends response back to the - * client.
    Android AsyncTask framework - Framework provides a way to execute long-running - * blocking calls, such as downloading a file, in background threads so that the UI thread remains - * free to respond to user inputs.
    + * with help of hardware level interrupts.
    + * CORBA - At the asynchronous layer one thread is associated with each socket that is connected to + * the client. Thread blocks waiting for CORBA requests from the client. On receiving request it is + * inserted in the queuing layer which is then picked up by synchronous layer which processes the + * request and sends response back to the client.
    + * Android AsyncTask framework - Framework provides a way to execute long-running blocking calls, + * such as downloading a file, in background threads so that the UI thread remains free to respond + * to user inputs.
    * *

    IMPLEMENTATION
    * The main method creates an asynchronous service which does not block the main thread while the @@ -90,9 +91,7 @@ public static void main(String[] args) { service.close(); } - /** - * ArithmeticSumTask. - */ + /** ArithmeticSumTask. */ static class ArithmeticSumTask implements AsyncTask { private final long numberOfElements; diff --git a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java index c7aaa165155b..ca31b0d8b0a1 100644 --- a/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java +++ b/half-sync-half-async/src/main/java/com/iluwatar/halfsynchalfasync/AsynchronousService.java @@ -58,7 +58,6 @@ public AsynchronousService(BlockingQueue workQueue) { service = new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS, workQueue); } - /** * A non-blocking method which performs the task provided in background and returns immediately. * @@ -79,30 +78,29 @@ public void execute(final AsyncTask task) { return; } - service.submit(new FutureTask<>(task) { - @Override - protected void done() { - super.done(); - try { - /* - * called in context of background thread. There is other variant possible where result is - * posted back and sits in the queue of caller thread which then picks it up for - * processing. An example of such a system is Android OS, where the UI elements can only - * be updated using UI thread. So result must be posted back in UI thread. - */ - task.onPostCall(get()); - } catch (InterruptedException e) { - // should not occur - } catch (ExecutionException e) { - task.onError(e.getCause()); - } - } - }); + service.submit( + new FutureTask<>(task) { + @Override + protected void done() { + super.done(); + try { + /* + * called in context of background thread. There is other variant possible where result is + * posted back and sits in the queue of caller thread which then picks it up for + * processing. An example of such a system is Android OS, where the UI elements can only + * be updated using UI thread. So result must be posted back in UI thread. + */ + task.onPostCall(get()); + } catch (InterruptedException e) { + // should not occur + } catch (ExecutionException e) { + task.onError(e.getCause()); + } + } + }); } - /** - * Stops the pool of workers. This is a blocking call to wait for all tasks to be completed. - */ + /** Stops the pool of workers. This is a blocking call to wait for all tasks to be completed. */ public void close() { service.shutdown(); try { diff --git a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java index d445a155c65b..a871c81cb26b 100644 --- a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java +++ b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AppTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.halfsynchalfasync; -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 { @Test diff --git a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java index b9060c42a12b..1360f7daa557 100644 --- a/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java +++ b/half-sync-half-async/src/test/java/com/iluwatar/halfsynchalfasync/AsynchronousServiceTest.java @@ -39,10 +39,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * AsynchronousServiceTest - * - */ +/** AsynchronousServiceTest */ class AsynchronousServiceTest { private AsynchronousService service; private AsyncTask task; @@ -99,5 +96,4 @@ void testPreCallException() { verifyNoMoreInteractions(task); } - -} \ No newline at end of file +} diff --git a/health-check/README.md b/health-check/README.md index 23c7f74f7675..f6fe0762f07e 100644 --- a/health-check/README.md +++ b/health-check/README.md @@ -30,6 +30,10 @@ In plain words > The Health Check Pattern is like a regular doctor's visit for services in a microservices architecture. It helps in early detection of issues and ensures that services are healthy and available. +Sequence diagram + +![Health Check sequence diagram](./etc/health-check-sequence-diagram.png) + ## Programmatic Example of Health Check Pattern in Java The Health Check design pattern is particularly useful in distributed systems where the health of individual components can affect the overall health of the system. Using Spring Boot Actuator, developers can easily implement health checks in Java applications. diff --git a/health-check/etc/health-check-sequence-diagram.png b/health-check/etc/health-check-sequence-diagram.png new file mode 100644 index 000000000000..65ddb26ab812 Binary files /dev/null and b/health-check/etc/health-check-sequence-diagram.png differ diff --git a/health-check/pom.xml b/health-check/pom.xml index a3ab135e17f7..203503ad03b6 100644 --- a/health-check/pom.xml +++ b/health-check/pom.xml @@ -36,43 +36,38 @@ health-check - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.2.3 - import - - - org.hibernate - hibernate-core - 6.4.4.Final - - - - + + org.springframework.boot + spring-boot-starter + org.springframework.boot spring-boot-starter-web - jakarta.xml.bind - jakarta.xml.bind-api + org.springframework.boot + spring-boot-starter-test + test - org.springframework.boot spring-boot-starter-actuator - - org.springframework.boot spring-boot-starter-data-jpa + + org.hibernate + hibernate-core + 6.4.4.Final + + + jakarta.xml.bind + jakarta.xml.bind-api + 4.0.2 + @@ -85,21 +80,7 @@ org.springframework.retry spring-retry - - - - - - org.junit.jupiter - junit-jupiter-engine - test - - - - - org.mockito - mockito-core - test + 2.0.11 @@ -113,13 +94,14 @@ org.assertj assertj-core - 3.26.3 + 3.27.3 test io.rest-assured rest-assured + 5.5.1 test diff --git a/health-check/src/main/java/com/iluwatar/health/check/App.java b/health-check/src/main/java/com/iluwatar/health/check/App.java index ae28452259d3..8f8faed9f821 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/App.java +++ b/health-check/src/main/java/com/iluwatar/health/check/App.java @@ -36,7 +36,6 @@ * their availability and responsiveness. For more information about health checks and their role in * microservice architectures, please refer to: [Microservices Health Checks * API]('https://microservices.io/patterns/observability/health-check-api.html'). - * */ @EnableCaching @EnableScheduling diff --git a/health-check/src/main/java/com/iluwatar/health/check/AsynchronousHealthChecker.java b/health-check/src/main/java/com/iluwatar/health/check/AsynchronousHealthChecker.java index 5cf015db8f4f..50433d48adf6 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/AsynchronousHealthChecker.java +++ b/health-check/src/main/java/com/iluwatar/health/check/AsynchronousHealthChecker.java @@ -37,10 +37,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.stereotype.Component; -/** - * An asynchronous health checker component that executes health checks in a separate thread. - * - */ +/** An asynchronous health checker component that executes health checks in a separate thread. */ @Slf4j @Component @RequiredArgsConstructor diff --git a/health-check/src/main/java/com/iluwatar/health/check/CpuHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/CpuHealthIndicator.java index c224a01b6019..451b1263bd0c 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/CpuHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/CpuHealthIndicator.java @@ -38,10 +38,7 @@ import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; -/** - * A health indicator that checks the health of the system's CPU. - * - */ +/** A health indicator that checks the health of the system's CPU. */ @Getter @Setter @Slf4j @@ -136,4 +133,4 @@ public Health health() { return Health.up().withDetails(details).build(); } } -} \ No newline at end of file +} diff --git a/health-check/src/main/java/com/iluwatar/health/check/CustomHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/CustomHealthIndicator.java index ccdf146246a6..54f2efe04db6 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/CustomHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/CustomHealthIndicator.java @@ -40,7 +40,6 @@ /** * A custom health indicator that periodically checks the health of a database and caches the * result. It leverages an asynchronous health checker to perform the health checks. - * */ @Slf4j @Component diff --git a/health-check/src/main/java/com/iluwatar/health/check/DatabaseTransactionHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/DatabaseTransactionHealthIndicator.java index 3f5b33798e7c..81658a27c727 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/DatabaseTransactionHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/DatabaseTransactionHealthIndicator.java @@ -41,7 +41,6 @@ * test transaction using a retry mechanism. If the transaction succeeds after multiple attempts, * the health indicator returns {@link Health#up()} and logs a success message. If all retry * attempts fail, the health indicator returns {@link Health#down()} and logs an error message. - * */ @Slf4j @Component diff --git a/health-check/src/main/java/com/iluwatar/health/check/GarbageCollectionHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/GarbageCollectionHealthIndicator.java index 460165275665..0790f0407ad4 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/GarbageCollectionHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/GarbageCollectionHealthIndicator.java @@ -44,7 +44,6 @@ * reports the health status accordingly. It gathers information about the collection count, * collection time, memory pool name, and garbage collector algorithm for each garbage collector and * presents the details in a structured manner. - * */ @Slf4j @Component diff --git a/health-check/src/main/java/com/iluwatar/health/check/HealthCheck.java b/health-check/src/main/java/com/iluwatar/health/check/HealthCheck.java index 46b0505c333c..6223acde083a 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/HealthCheck.java +++ b/health-check/src/main/java/com/iluwatar/health/check/HealthCheck.java @@ -34,7 +34,6 @@ /** * An entity class that represents a health check record in the database. This class is used to * persist the results of health checks performed by the `DatabaseTransactionHealthIndicator`. - * */ @Entity @Data diff --git a/health-check/src/main/java/com/iluwatar/health/check/HealthCheckRepository.java b/health-check/src/main/java/com/iluwatar/health/check/HealthCheckRepository.java index 83ac27792968..d5016323ac6f 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/HealthCheckRepository.java +++ b/health-check/src/main/java/com/iluwatar/health/check/HealthCheckRepository.java @@ -33,7 +33,6 @@ /** * A repository class for managing health check records in the database. This class provides methods * for checking the health of the database connection and performing test transactions. - * */ @Slf4j @Repository diff --git a/health-check/src/main/java/com/iluwatar/health/check/MemoryHealthIndicator.java b/health-check/src/main/java/com/iluwatar/health/check/MemoryHealthIndicator.java index b69fd9d74fb1..08d82dd4ad5b 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/MemoryHealthIndicator.java +++ b/health-check/src/main/java/com/iluwatar/health/check/MemoryHealthIndicator.java @@ -41,7 +41,6 @@ * A custom health indicator that checks the memory usage of the application and reports the health * status accordingly. It uses an asynchronous health checker to perform the health check and a * configurable memory usage threshold to determine the health status. - * */ @Slf4j @Component diff --git a/health-check/src/main/java/com/iluwatar/health/check/RetryConfig.java b/health-check/src/main/java/com/iluwatar/health/check/RetryConfig.java index f05c1eb91d60..026a4dd11efe 100644 --- a/health-check/src/main/java/com/iluwatar/health/check/RetryConfig.java +++ b/health-check/src/main/java/com/iluwatar/health/check/RetryConfig.java @@ -32,10 +32,7 @@ import org.springframework.retry.support.RetryTemplate; import org.springframework.stereotype.Component; -/** - * Configuration class for retry policies used in health check operations. - * - */ +/** Configuration class for retry policies used in health check operations. */ @Configuration @Component public class RetryConfig { diff --git a/health-check/src/test/java/AsynchronousHealthCheckerTest.java b/health-check/src/test/java/AsynchronousHealthCheckerTest.java index 52751c41b8b1..2241f1989a4f 100644 --- a/health-check/src/test/java/AsynchronousHealthCheckerTest.java +++ b/health-check/src/test/java/AsynchronousHealthCheckerTest.java @@ -45,10 +45,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -/** - * Tests for {@link AsynchronousHealthChecker}. - * - */ +/** Tests for {@link AsynchronousHealthChecker}. */ @Slf4j class AsynchronousHealthCheckerTest { diff --git a/health-check/src/test/java/CpuHealthIndicatorTest.java b/health-check/src/test/java/CpuHealthIndicatorTest.java index 8391dde33db6..c59f3fea7c3f 100644 --- a/health-check/src/test/java/CpuHealthIndicatorTest.java +++ b/health-check/src/test/java/CpuHealthIndicatorTest.java @@ -34,10 +34,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -/** - * Test class for the {@link CpuHealthIndicator} class. - * - */ +/** Test class for the {@link CpuHealthIndicator} class. */ class CpuHealthIndicatorTest { /** The CPU health indicator to be tested. */ diff --git a/health-check/src/test/java/CustomHealthIndicatorTest.java b/health-check/src/test/java/CustomHealthIndicatorTest.java index 1acb0e536c50..1c48861aac85 100644 --- a/health-check/src/test/java/CustomHealthIndicatorTest.java +++ b/health-check/src/test/java/CustomHealthIndicatorTest.java @@ -47,10 +47,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -/** - * Tests class< for {@link CustomHealthIndicator}. * - * - */ +/** Tests class< for {@link CustomHealthIndicator}. * */ class CustomHealthIndicatorTest { /** Mocked AsynchronousHealthChecker instance. */ diff --git a/health-check/src/test/java/DatabaseTransactionHealthIndicatorTest.java b/health-check/src/test/java/DatabaseTransactionHealthIndicatorTest.java index 0d7dfc2e9ea9..c396770d19d8 100644 --- a/health-check/src/test/java/DatabaseTransactionHealthIndicatorTest.java +++ b/health-check/src/test/java/DatabaseTransactionHealthIndicatorTest.java @@ -43,10 +43,7 @@ import org.springframework.boot.actuate.health.Status; import org.springframework.retry.support.RetryTemplate; -/** - * Unit tests for the {@link DatabaseTransactionHealthIndicator} class. - * - */ +/** Unit tests for the {@link DatabaseTransactionHealthIndicator} class. */ class DatabaseTransactionHealthIndicatorTest { /** Timeout value in seconds for the health check. */ diff --git a/health-check/src/test/java/GarbageCollectionHealthIndicatorTest.java b/health-check/src/test/java/GarbageCollectionHealthIndicatorTest.java index 01cbc938992f..6f3068b031d4 100644 --- a/health-check/src/test/java/GarbageCollectionHealthIndicatorTest.java +++ b/health-check/src/test/java/GarbageCollectionHealthIndicatorTest.java @@ -31,6 +31,7 @@ import java.lang.management.MemoryUsage; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -39,10 +40,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -/** - * Test class for {@link GarbageCollectionHealthIndicator}. - * - */ +/** Test class for {@link GarbageCollectionHealthIndicator}. */ class GarbageCollectionHealthIndicatorTest { /** Mocked garbage collector MXBean. */ @@ -72,6 +70,7 @@ protected List getMemoryPoolMxBeans() { } }); healthIndicator.setMemoryUsageThreshold(0.8); + Locale.setDefault(Locale.US); } /** Test case to verify that the health status is up when memory usage is low. */ diff --git a/health-check/src/test/java/HealthCheckRepositoryTest.java b/health-check/src/test/java/HealthCheckRepositoryTest.java index 7942535c40a5..8a630299dc85 100644 --- a/health-check/src/test/java/HealthCheckRepositoryTest.java +++ b/health-check/src/test/java/HealthCheckRepositoryTest.java @@ -35,10 +35,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -/** - * Tests class for {@link HealthCheckRepository}. - * - */ +/** Tests class for {@link HealthCheckRepository}. */ @ExtendWith(MockitoExtension.class) class HealthCheckRepositoryTest { diff --git a/health-check/src/test/java/HealthEndpointIntegrationTest.java b/health-check/src/test/java/HealthEndpointIntegrationTest.java index da8dfaeca48b..0d0a3d8f8052 100644 --- a/health-check/src/test/java/HealthEndpointIntegrationTest.java +++ b/health-check/src/test/java/HealthEndpointIntegrationTest.java @@ -48,7 +48,6 @@ * {"status":"DOWN","components":{"cpu":{"status":"DOWN","details":{"processCpuLoad":"100.00%", * * "availableProcessors":2,"systemCpuLoad":"100.00%","loadAverage":1.97,"timestamp":"2023-11-09T08:34:15.974557865Z", * * "error":"High system CPU load"}}} * - * */ @Slf4j @SpringBootTest( diff --git a/health-check/src/test/java/MemoryHealthIndicatorTest.java b/health-check/src/test/java/MemoryHealthIndicatorTest.java index 13676af659cc..8b8da1613408 100644 --- a/health-check/src/test/java/MemoryHealthIndicatorTest.java +++ b/health-check/src/test/java/MemoryHealthIndicatorTest.java @@ -41,10 +41,7 @@ import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Status; -/** - * Unit tests for {@link MemoryHealthIndicator}. - * - */ +/** Unit tests for {@link MemoryHealthIndicator}. */ @ExtendWith(MockitoExtension.class) class MemoryHealthIndicatorTest { diff --git a/health-check/src/test/java/RetryConfigTest.java b/health-check/src/test/java/RetryConfigTest.java index 4341aeef9a44..947616e45f18 100644 --- a/health-check/src/test/java/RetryConfigTest.java +++ b/health-check/src/test/java/RetryConfigTest.java @@ -32,10 +32,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.retry.support.RetryTemplate; -/** - * Unit tests for the {@link RetryConfig} class. - * - */ +/** Unit tests for the {@link RetryConfig} class. */ @SpringBootTest(classes = RetryConfig.class) class RetryConfigTest { diff --git a/hexagonal-architecture/README.md b/hexagonal-architecture/README.md index cbb9182b25d7..d6fea210a008 100644 --- a/hexagonal-architecture/README.md +++ b/hexagonal-architecture/README.md @@ -31,6 +31,11 @@ Wikipedia says > The hexagonal architecture, or ports and adapters architecture, is an architectural pattern used in software design. It aims at creating loosely coupled application components that can be easily connected to their software environment by means of ports and adapters. This makes components exchangeable at any level and facilitates test automation. +Architecture diagram + +![Hexagonal Architecture Diagram](./etc/hexagonal-architecture-diagram.png) + + ## Programmatic Example of Hexagonal Architecture Pattern in Java The Hexagonal Architecture, also known as Ports and Adapters, is a design pattern that aims to create a loosely coupled application where the core business logic is isolated from external interfaces like databases, user interfaces, or third-party services. This allows the core application to be independent and easily testable. @@ -168,10 +173,6 @@ Running the main function of App class produces the following output: In this example, the `LotteryAdministration` and `LotteryService` classes are the core of the application. They interact with external interfaces like `LotteryTicketRepository`, `LotteryEventLog`, and `WireTransfers` through dependency injection, keeping the core business logic decoupled from external concerns. This is a basic example of the Hexagonal Architecture pattern, where the core application is at the center of input/output systems. -## Detailed Explanation of Hexagonal Architecture Pattern with Real-World Examples - -![Hexagonal Architecture class diagram](./etc/hexagonal.png) - ## When to Use the Hexagonal Architecture Pattern in Java Hexagonal Architecture is particularly beneficial in scenarios: @@ -180,7 +181,7 @@ Hexagonal Architecture is particularly beneficial in scenarios: * There is a requirement for high testability and maintainability. * The application should remain unaffected by changes in external interfaces. -## Real-World Applications of Hexagonal Architecure Pattern in Java +## Real-World Applications of Hexagonal Architecture Pattern in Java * Implemented extensively within enterprise applications that leverage frameworks like Spring. * Used in microservices architectures to maintain clear boundaries and protocols between services. diff --git a/hexagonal-architecture/etc/hexagonal-architecture-diagram.png b/hexagonal-architecture/etc/hexagonal-architecture-diagram.png new file mode 100644 index 000000000000..39ca7d99b2d4 Binary files /dev/null and b/hexagonal-architecture/etc/hexagonal-architecture-diagram.png differ diff --git a/hexagonal-architecture/etc/hexagonal-architecture.urm.puml b/hexagonal-architecture/etc/hexagonal-architecture.urm.puml new file mode 100644 index 000000000000..f86e734747f3 --- /dev/null +++ b/hexagonal-architecture/etc/hexagonal-architecture.urm.puml @@ -0,0 +1,282 @@ +@startuml +package com.iluwatar.hexagonal.sampledata { + class SampleData { + - PLAYERS : List {static} + - RANDOM : SecureRandom {static} + + SampleData() + - getRandomPlayerDetails() : PlayerDetails {static} + + submitTickets(lotteryService : LotteryService, numTickets : int) {static} + } +} +package com.iluwatar.hexagonal.service { + class ConsoleLottery { + - LOGGER : Logger {static} + + ConsoleLottery() + + main(args : String[]) {static} + - printMainMenu() {static} + - readString(scanner : Scanner) : String {static} + } + interface LotteryConsoleService { + + addFundsToLotteryAccount(WireTransfers, Scanner) {abstract} + + checkTicket(LotteryService, Scanner) {abstract} + + queryLotteryAccountFunds(WireTransfers, Scanner) {abstract} + + submitTicket(LotteryService, Scanner) {abstract} + } + class LotteryConsoleServiceImpl { + - logger : Logger + + LotteryConsoleServiceImpl(logger : Logger) + + addFundsToLotteryAccount(bank : WireTransfers, scanner : Scanner) + + checkTicket(service : LotteryService, scanner : Scanner) + + queryLotteryAccountFunds(bank : WireTransfers, scanner : Scanner) + - readString(scanner : Scanner) : String + + submitTicket(service : LotteryService, scanner : Scanner) + } +} +package com.iluwatar.hexagonal.mongo { + class MongoConnectionPropertiesLoader { + - DEFAULT_HOST : String {static} + - DEFAULT_PORT : int {static} + - LOGGER : Logger {static} + + MongoConnectionPropertiesLoader() + + load() {static} + } +} +package com.iluwatar.hexagonal.domain { + class LotteryAdministration { + - notifications : LotteryEventLog + - repository : LotteryTicketRepository + - wireTransfers : WireTransfers + + LotteryAdministration(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers) + + getAllSubmittedTickets() : Map + + performLottery() : LotteryNumbers + + resetLottery() + } + class LotteryConstants { + + PLAYER_MAX_BALANCE : int {static} + + PRIZE_AMOUNT : int {static} + + SERVICE_BANK_ACCOUNT : String {static} + + SERVICE_BANK_ACCOUNT_BALANCE : int {static} + + TICKET_PRIZE : int {static} + - LotteryConstants() + } + class LotteryNumbers { + + MAX_NUMBER : int {static} + + MIN_NUMBER : int {static} + + NUM_NUMBERS : int {static} + - numbers : Set + - LotteryNumbers() + - LotteryNumbers(givenNumbers : Set) + # canEqual(other : Object) : boolean + + create(givenNumbers : Set) : LotteryNumbers {static} + + createRandom() : LotteryNumbers {static} + + equals(o : Object) : boolean + - generateRandomNumbers() + + getNumbers() : Set + + getNumbersAsString() : String + + hashCode() : int + + toString() : String + } + -class RandomNumberGenerator { + - randomIterator : OfInt + + RandomNumberGenerator(min : int, max : int) + + nextInt() : int + } + class LotteryService { + - notifications : LotteryEventLog + - repository : LotteryTicketRepository + - wireTransfers : WireTransfers + + LotteryService(repository : LotteryTicketRepository, notifications : LotteryEventLog, wireTransfers : WireTransfers) + + checkTicketForPrize(id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult + + submitTicket(ticket : LotteryTicket) : Optional + } + class LotteryTicketCheckResult { + - prizeAmount : int + - result : CheckResult + + LotteryTicketCheckResult(result : CheckResult) + + LotteryTicketCheckResult(result : CheckResult, prizeAmount : int) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getPrizeAmount() : int + + getResult() : CheckResult + + hashCode() : int + } + enum CheckResult { + + NO_PRIZE {static} + + TICKET_NOT_SUBMITTED {static} + + WIN_PRIZE {static} + + valueOf(name : String) : CheckResult {static} + + values() : CheckResult[] {static} + } + class LotteryTicketId { + - id : int + - numAllocated : AtomicInteger {static} + + LotteryTicketId() + + LotteryTicketId(id : int) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : int + + hashCode() : int + + toString() : String + } + class LotteryUtils { + - LotteryUtils() + + checkTicketForPrize(repository : LotteryTicketRepository, id : LotteryTicketId, winningNumbers : LotteryNumbers) : LotteryTicketCheckResult {static} + } +} +package com.iluwatar.hexagonal.banking { + class InMemoryBank { + - accounts : Map {static} + + InMemoryBank() + + getFunds(bankAccount : String) : int + + setFunds(bankAccount : String, amount : int) + + transferFunds(amount : int, sourceAccount : String, destinationAccount : String) : boolean + } + class MongoBank { + - DEFAULT_ACCOUNTS_COLLECTION : String {static} + - DEFAULT_DB : String {static} + - accountsCollection : MongoCollection + - database : MongoDatabase + - mongoClient : MongoClient + + MongoBank() + + MongoBank(dbName : String, accountsCollectionName : String) + + connect() + + connect(dbName : String, accountsCollectionName : String) + + getAccountsCollection() : MongoCollection + + getDatabase() : MongoDatabase + + getFunds(bankAccount : String) : int + + getMongoClient() : MongoClient + + setFunds(bankAccount : String, amount : int) + + transferFunds(amount : int, sourceAccount : String, destinationAccount : String) : boolean + } + interface WireTransfers { + + getFunds(String) : int {abstract} + + setFunds(String, int) {abstract} + + transferFunds(int, String, String) : boolean {abstract} + } +} +package com.iluwatar.hexagonal.database { + class InMemoryTicketRepository { + - tickets : Map {static} + + InMemoryTicketRepository() + + deleteAll() + + findAll() : Map + + findById(id : LotteryTicketId) : Optional + + save(ticket : LotteryTicket) : Optional + } + interface LotteryTicketRepository { + + deleteAll() {abstract} + + findAll() : Map {abstract} + + findById(LotteryTicketId) : Optional {abstract} + + save(LotteryTicket) : Optional {abstract} + } + class MongoTicketRepository { + - DEFAULT_COUNTERS_COLLECTION : String {static} + - DEFAULT_DB : String {static} + - DEFAULT_TICKETS_COLLECTION : String {static} + - TICKET_ID : String {static} + - countersCollection : MongoCollection + - database : MongoDatabase + - mongoClient : MongoClient + - ticketsCollection : MongoCollection + + MongoTicketRepository() + + MongoTicketRepository(dbName : String, ticketsCollectionName : String, countersCollectionName : String) + + connect() + + connect(dbName : String, ticketsCollectionName : String, countersCollectionName : String) + + deleteAll() + - docToTicket(doc : Document) : LotteryTicket + + findAll() : Map + + findById(id : LotteryTicketId) : Optional + + getCountersCollection() : MongoCollection + + getNextId() : int + + getTicketsCollection() : MongoCollection + - initCounters() + + save(ticket : LotteryTicket) : Optional + } +} +package com.iluwatar.hexagonal { + class App { + + App() + + main(args : String[]) {static} + } +} +package com.iluwatar.hexagonal.administration { + class ConsoleAdministration { + - LOGGER : Logger {static} + + ConsoleAdministration() + + main(args : String[]) {static} + - printMainMenu() {static} + - readString(scanner : Scanner) : String {static} + } + interface ConsoleAdministrationSrv { + + getAllSubmittedTickets() {abstract} + + performLottery() {abstract} + + resetLottery() {abstract} + } + class ConsoleAdministrationSrvImpl { + - administration : LotteryAdministration + - logger : Logger + + ConsoleAdministrationSrvImpl(administration : LotteryAdministration, logger : Logger) + + getAllSubmittedTickets() + + performLottery() + + resetLottery() + } +} +package com.iluwatar.hexagonal.eventlog { + interface LotteryEventLog { + + prizeError(PlayerDetails, int) {abstract} + + ticketDidNotWin(PlayerDetails) {abstract} + + ticketSubmitError(PlayerDetails) {abstract} + + ticketSubmitted(PlayerDetails) {abstract} + + ticketWon(PlayerDetails, int) {abstract} + } + class MongoEventLog { + - DEFAULT_DB : String {static} + - DEFAULT_EVENTS_COLLECTION : String {static} + - EMAIL : String {static} + + MESSAGE : String {static} + - PHONE : String {static} + - database : MongoDatabase + - eventsCollection : MongoCollection + - mongoClient : MongoClient + - stdOutEventLog : StdOutEventLog + + MongoEventLog() + + MongoEventLog(dbName : String, eventsCollectionName : String) + + connect() + + connect(dbName : String, eventsCollectionName : String) + + getDatabase() : MongoDatabase + + getEventsCollection() : MongoCollection + + getMongoClient() : MongoClient + + prizeError(details : PlayerDetails, prizeAmount : int) + + ticketDidNotWin(details : PlayerDetails) + + ticketSubmitError(details : PlayerDetails) + + ticketSubmitted(details : PlayerDetails) + + ticketWon(details : PlayerDetails, prizeAmount : int) + } + class StdOutEventLog { + - LOGGER : Logger {static} + + StdOutEventLog() + + prizeError(details : PlayerDetails, prizeAmount : int) + + ticketDidNotWin(details : PlayerDetails) + + ticketSubmitError(details : PlayerDetails) + + ticketSubmitted(details : PlayerDetails) + + ticketWon(details : PlayerDetails, prizeAmount : int) + } +} +LotteryAdministration --> "-wireTransfers" WireTransfers +LotteryService --> "-notifications" LotteryEventLog +LotteryAdministration --> "-repository" LotteryTicketRepository +MongoEventLog --> "-stdOutEventLog" StdOutEventLog +LotteryService --> "-wireTransfers" WireTransfers +LotteryAdministration --> "-notifications" LotteryEventLog +ConsoleAdministrationSrvImpl --> "-administration" LotteryAdministration +LotteryService --> "-repository" LotteryTicketRepository +LotteryTicketCheckResult --> "-result" CheckResult +ConsoleAdministrationSrvImpl ..|> ConsoleAdministrationSrv +InMemoryBank ..|> WireTransfers +MongoBank ..|> WireTransfers +InMemoryTicketRepository ..|> LotteryTicketRepository +MongoTicketRepository ..|> LotteryTicketRepository +MongoEventLog ..|> LotteryEventLog +StdOutEventLog ..|> LotteryEventLog +LotteryConsoleServiceImpl ..|> LotteryConsoleService +@enduml \ No newline at end of file diff --git a/hexagonal-architecture/pom.xml b/hexagonal-architecture/pom.xml index 633e39f0004b..1ac50cecfa04 100644 --- a/hexagonal-architecture/pom.xml +++ b/hexagonal-architecture/pom.xml @@ -34,6 +34,14 @@ hexagonal-architecture + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -46,7 +54,7 @@ de.flapdoodle.embed de.flapdoodle.embed.mongo - 4.18.0 + 4.20.0 test diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/App.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/App.java index 13b173a6aa75..b2ea0e635ced 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/App.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/App.java @@ -58,9 +58,7 @@ */ public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var injector = Guice.createInjector(new LotteryTestingModule()); diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java index 718758483ad2..80c47e3d1279 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministration.java @@ -33,15 +33,11 @@ import java.util.Scanner; import lombok.extern.slf4j.Slf4j; -/** - * Console interface for lottery administration. - */ +/** Console interface for lottery administration. */ @Slf4j public class ConsoleAdministration { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { MongoConnectionPropertiesLoader.load(); var injector = Guice.createInjector(new LotteryModule()); diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java index 44eefccc3d87..f361ede46f12 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrv.java @@ -24,23 +24,15 @@ */ package com.iluwatar.hexagonal.administration; -/** - * Console interface for lottery administration. - */ +/** Console interface for lottery administration. */ public interface ConsoleAdministrationSrv { - /** - * Get all submitted tickets. - */ + /** Get all submitted tickets. */ void getAllSubmittedTickets(); - /** - * Draw lottery numbers. - */ + /** Draw lottery numbers. */ void performLottery(); - /** - * Begin new lottery round. - */ + /** Begin new lottery round. */ void resetLottery(); } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java index 8808f507c13f..02612ff83685 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/administration/ConsoleAdministrationSrvImpl.java @@ -27,16 +27,12 @@ import com.iluwatar.hexagonal.domain.LotteryAdministration; import org.slf4j.Logger; -/** - * Console implementation for lottery administration. - */ +/** Console implementation for lottery administration. */ public class ConsoleAdministrationSrvImpl implements ConsoleAdministrationSrv { private final LotteryAdministration administration; private final Logger logger; - /** - * Constructor. - */ + /** Constructor. */ public ConsoleAdministrationSrvImpl(LotteryAdministration administration, Logger logger) { this.administration = administration; this.logger = logger; @@ -44,7 +40,8 @@ public ConsoleAdministrationSrvImpl(LotteryAdministration administration, Logger @Override public void getAllSubmittedTickets() { - administration.getAllSubmittedTickets() + administration + .getAllSubmittedTickets() .forEach((k, v) -> logger.info("Key: {}, Value: {}", k, v)); } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java index f1754a65fc6c..421d5c32dfc4 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/InMemoryBank.java @@ -28,16 +28,14 @@ import java.util.HashMap; import java.util.Map; -/** - * Banking implementation. - */ +/** Banking implementation. */ public class InMemoryBank implements WireTransfers { private static final Map accounts = new HashMap<>(); static { - accounts - .put(LotteryConstants.SERVICE_BANK_ACCOUNT, LotteryConstants.SERVICE_BANK_ACCOUNT_BALANCE); + accounts.put( + LotteryConstants.SERVICE_BANK_ACCOUNT, LotteryConstants.SERVICE_BANK_ACCOUNT_BALANCE); } @Override diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java index 23f1cc8fcc41..c7af4693137e 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/MongoBank.java @@ -32,51 +32,39 @@ import lombok.Getter; import org.bson.Document; -/** - * Mongo based banking adapter. - */ +/** Mongo based banking adapter. */ public class MongoBank implements WireTransfers { private static final String DEFAULT_DB = "lotteryDB"; private static final String DEFAULT_ACCOUNTS_COLLECTION = "accounts"; - @Getter - private MongoClient mongoClient; - @Getter - private MongoDatabase database; - @Getter - private MongoCollection accountsCollection; + @Getter private MongoClient mongoClient; + @Getter private MongoDatabase database; + @Getter private MongoCollection accountsCollection; - /** - * Constructor. - */ + /** Constructor. */ public MongoBank() { connect(); } - /** - * Constructor accepting parameters. - */ + /** Constructor accepting parameters. */ public MongoBank(String dbName, String accountsCollectionName) { connect(dbName, accountsCollectionName); } - /** - * Connect to database with default parameters. - */ + /** Connect to database with default parameters. */ public void connect() { connect(DEFAULT_DB, DEFAULT_ACCOUNTS_COLLECTION); } - /** - * Connect to database with given parameters. - */ + /** Connect to database with given parameters. */ public void connect(String dbName, String accountsCollectionName) { if (mongoClient != null) { mongoClient.close(); } - mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); database = mongoClient.getDatabase(dbName); accountsCollection = database.getCollection(accountsCollectionName); } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java index d88827d8fd4c..f8981420933f 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/banking/WireTransfers.java @@ -24,24 +24,15 @@ */ package com.iluwatar.hexagonal.banking; -/** - * Interface to bank accounts. - */ +/** Interface to bank accounts. */ public interface WireTransfers { - /** - * Set amount of funds for bank account. - */ + /** Set amount of funds for bank account. */ void setFunds(String bankAccount, int amount); - /** - * Get amount of funds for bank account. - */ + /** Get amount of funds for bank account. */ int getFunds(String bankAccount); - /** - * Transfer funds from one bank account to another. - */ + /** Transfer funds from one bank account to another. */ boolean transferFunds(int amount, String sourceBackAccount, String destinationBankAccount); - } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java index 8c0aa1d74637..5805e80aade6 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/InMemoryTicketRepository.java @@ -30,9 +30,7 @@ import java.util.Map; import java.util.Optional; -/** - * Mock database for lottery tickets. - */ +/** Mock database for lottery tickets. */ public class InMemoryTicketRepository implements LotteryTicketRepository { private static final Map tickets = new HashMap<>(); diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java index 6302b6dd6feb..29ca16af70c3 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/LotteryTicketRepository.java @@ -29,29 +29,18 @@ import java.util.Map; import java.util.Optional; -/** - * Interface for accessing lottery tickets in database. - */ +/** Interface for accessing lottery tickets in database. */ public interface LotteryTicketRepository { - /** - * Find lottery ticket by id. - */ + /** Find lottery ticket by id. */ Optional findById(LotteryTicketId id); - /** - * Save lottery ticket. - */ + /** Save lottery ticket. */ Optional save(LotteryTicket ticket); - /** - * Get all lottery tickets. - */ + /** Get all lottery tickets. */ Map findAll(); - /** - * Delete all lottery tickets. - */ + /** Delete all lottery tickets. */ void deleteAll(); - } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java index 9817a14a7b38..ecb26c9c470b 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/database/MongoTicketRepository.java @@ -40,9 +40,7 @@ import lombok.Getter; import org.bson.Document; -/** - * Mongo lottery ticket database. - */ +/** Mongo lottery ticket database. */ public class MongoTicketRepository implements LotteryTicketRepository { private static final String DEFAULT_DB = "lotteryDB"; @@ -52,43 +50,33 @@ public class MongoTicketRepository implements LotteryTicketRepository { private MongoClient mongoClient; private MongoDatabase database; - @Getter - private MongoCollection ticketsCollection; - @Getter - private MongoCollection countersCollection; + @Getter private MongoCollection ticketsCollection; + @Getter private MongoCollection countersCollection; - /** - * Constructor. - */ + /** Constructor. */ public MongoTicketRepository() { connect(); } - /** - * Constructor accepting parameters. - */ - public MongoTicketRepository(String dbName, String ticketsCollectionName, - String countersCollectionName) { + /** Constructor accepting parameters. */ + public MongoTicketRepository( + String dbName, String ticketsCollectionName, String countersCollectionName) { connect(dbName, ticketsCollectionName, countersCollectionName); } - /** - * Connect to database with default parameters. - */ + /** Connect to database with default parameters. */ public void connect() { connect(DEFAULT_DB, DEFAULT_TICKETS_COLLECTION, DEFAULT_COUNTERS_COLLECTION); } - /** - * Connect to database with given parameters. - */ - public void connect(String dbName, String ticketsCollectionName, - String countersCollectionName) { + /** Connect to database with given parameters. */ + public void connect(String dbName, String ticketsCollectionName, String countersCollectionName) { if (mongoClient != null) { mongoClient.close(); } - mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); database = mongoClient.getDatabase(dbName); ticketsCollection = database.getCollection(ticketsCollectionName); countersCollection = database.getCollection(countersCollectionName); @@ -140,10 +128,7 @@ public Optional save(LotteryTicket ticket) { @Override public Map findAll() { - return ticketsCollection - .find(new Document()) - .into(new ArrayList<>()) - .stream() + return ticketsCollection.find(new Document()).into(new ArrayList<>()).stream() .map(this::docToTicket) .collect(Collectors.toMap(LotteryTicket::id, Function.identity())); } @@ -154,11 +139,12 @@ public void deleteAll() { } private LotteryTicket docToTicket(Document doc) { - var playerDetails = new PlayerDetails(doc.getString("email"), doc.getString("bank"), - doc.getString("phone")); - var numbers = Arrays.stream(doc.getString("numbers").split(",")) - .map(Integer::parseInt) - .collect(Collectors.toSet()); + var playerDetails = + new PlayerDetails(doc.getString("email"), doc.getString("bank"), doc.getString("phone")); + var numbers = + Arrays.stream(doc.getString("numbers").split(",")) + .map(Integer::parseInt) + .collect(Collectors.toSet()); var lotteryNumbers = LotteryNumbers.create(numbers); var ticketId = new LotteryTicketId(doc.getInteger(TICKET_ID)); return new LotteryTicket(ticketId, playerDetails, lotteryNumbers); diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java index e21b028cca8c..28cf0fbbd22a 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryAdministration.java @@ -33,36 +33,30 @@ import com.iluwatar.hexagonal.eventlog.LotteryEventLog; import java.util.Map; -/** - * Lottery administration implementation. - */ +/** Lottery administration implementation. */ public class LotteryAdministration { private final LotteryTicketRepository repository; private final LotteryEventLog notifications; private final WireTransfers wireTransfers; - /** - * Constructor. - */ + /** Constructor. */ @Inject - public LotteryAdministration(LotteryTicketRepository repository, LotteryEventLog notifications, - WireTransfers wireTransfers) { + public LotteryAdministration( + LotteryTicketRepository repository, + LotteryEventLog notifications, + WireTransfers wireTransfers) { this.repository = repository; this.notifications = notifications; this.wireTransfers = wireTransfers; } - /** - * Get all the lottery tickets submitted for lottery. - */ + /** Get all the lottery tickets submitted for lottery. */ public Map getAllSubmittedTickets() { return repository.findAll(); } - /** - * Draw lottery numbers. - */ + /** Draw lottery numbers. */ public LotteryNumbers performLottery() { var numbers = LotteryNumbers.createRandom(); var tickets = getAllSubmittedTickets(); @@ -84,9 +78,7 @@ public LotteryNumbers performLottery() { return numbers; } - /** - * Begin new lottery round. - */ + /** Begin new lottery round. */ public void resetLottery() { repository.deleteAll(); } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java index 2001d57ace99..21dcbc2d48be 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryConstants.java @@ -24,18 +24,14 @@ */ package com.iluwatar.hexagonal.domain; -/** - * Lottery domain constants. - */ +/** Lottery domain constants. */ public class LotteryConstants { - private LotteryConstants() { - } + private LotteryConstants() {} public static final int PRIZE_AMOUNT = 100000; public static final String SERVICE_BANK_ACCOUNT = "123-123"; public static final int TICKET_PRIZE = 3; public static final int SERVICE_BANK_ACCOUNT_BALANCE = 150000; public static final int PLAYER_MAX_BALANCE = 100; - } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java index 83836046d621..3319251f479d 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryNumbers.java @@ -47,17 +47,13 @@ public class LotteryNumbers { public static final int MAX_NUMBER = 20; public static final int NUM_NUMBERS = 4; - /** - * Constructor. Creates random lottery numbers. - */ + /** Constructor. Creates random lottery numbers. */ private LotteryNumbers() { numbers = new HashSet<>(); generateRandomNumbers(); } - /** - * Constructor. Uses given numbers. - */ + /** Constructor. Uses given numbers. */ private LotteryNumbers(Set givenNumbers) { numbers = new HashSet<>(); numbers.addAll(givenNumbers); @@ -99,9 +95,7 @@ public String getNumbersAsString() { return Joiner.on(',').join(numbers); } - /** - * Generates 4 unique random numbers between 1-20 into numbers set. - */ + /** Generates 4 unique random numbers between 1-20 into numbers set. */ private void generateRandomNumbers() { numbers.clear(); var generator = new RandomNumberGenerator(MIN_NUMBER, MAX_NUMBER); @@ -111,9 +105,7 @@ private void generateRandomNumbers() { } } - /** - * Helper class for generating random numbers. - */ + /** Helper class for generating random numbers. */ private static class RandomNumberGenerator { private final PrimitiveIterator.OfInt randomIterator; @@ -138,5 +130,4 @@ public int nextInt() { return randomIterator.nextInt(); } } - } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java index 8b6521498f68..38d33ab957e4 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryService.java @@ -33,29 +33,25 @@ import com.iluwatar.hexagonal.eventlog.LotteryEventLog; import java.util.Optional; -/** - * Implementation for lottery service. - */ +/** Implementation for lottery service. */ public class LotteryService { private final LotteryTicketRepository repository; private final LotteryEventLog notifications; private final WireTransfers wireTransfers; - /** - * Constructor. - */ + /** Constructor. */ @Inject - public LotteryService(LotteryTicketRepository repository, LotteryEventLog notifications, - WireTransfers wireTransfers) { + public LotteryService( + LotteryTicketRepository repository, + LotteryEventLog notifications, + WireTransfers wireTransfers) { this.repository = repository; this.notifications = notifications; this.wireTransfers = wireTransfers; } - /** - * Submit lottery ticket to participate in the lottery. - */ + /** Submit lottery ticket to participate in the lottery. */ public Optional submitTicket(LotteryTicket ticket) { var playerDetails = ticket.playerDetails(); var playerAccount = playerDetails.bankAccount(); @@ -71,13 +67,9 @@ public Optional submitTicket(LotteryTicket ticket) { return optional; } - /** - * Check if lottery ticket has won. - */ + /** Check if lottery ticket has won. */ public LotteryTicketCheckResult checkTicketForPrize( - LotteryTicketId id, - LotteryNumbers winningNumbers - ) { + LotteryTicketId id, LotteryNumbers winningNumbers) { return LotteryUtils.checkTicketForPrize(repository, id, winningNumbers); } } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java index bc031f660dba..0b895f8169a2 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicket.java @@ -24,10 +24,9 @@ */ package com.iluwatar.hexagonal.domain; -/** - * Immutable value object representing lottery ticket. - */ -public record LotteryTicket(LotteryTicketId id, PlayerDetails playerDetails, LotteryNumbers lotteryNumbers) { +/** Immutable value object representing lottery ticket. */ +public record LotteryTicket( + LotteryTicketId id, PlayerDetails playerDetails, LotteryNumbers lotteryNumbers) { @Override public int hashCode() { diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java index 7b1290e23c89..ff0245f30b1e 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResult.java @@ -28,17 +28,13 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Represents lottery ticket check result. - */ +/** Represents lottery ticket check result. */ @Getter @EqualsAndHashCode @RequiredArgsConstructor public class LotteryTicketCheckResult { - /** - * Enumeration of Type of Outcomes of a Lottery. - */ + /** Enumeration of Type of Outcomes of a Lottery. */ public enum CheckResult { WIN_PRIZE, NO_PRIZE, @@ -48,12 +44,9 @@ public enum CheckResult { private final CheckResult result; private final int prizeAmount; - /** - * Constructor. - */ + /** Constructor. */ public LotteryTicketCheckResult(CheckResult result) { this.result = result; prizeAmount = 0; } - } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java index f53c992e18b9..4bd1074fb3d9 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryTicketId.java @@ -29,9 +29,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Lottery ticked id. - */ +/** Lottery ticked id. */ @Getter @EqualsAndHashCode @RequiredArgsConstructor @@ -48,5 +46,4 @@ public LotteryTicketId() { public String toString() { return String.format("%d", id); } - } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java index d14c7ce48acf..5e4ed138c7df 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/LotteryUtils.java @@ -27,22 +27,14 @@ import com.iluwatar.hexagonal.database.LotteryTicketRepository; import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult.CheckResult; -/** - * Lottery utilities. - */ +/** Lottery utilities. */ public class LotteryUtils { - private LotteryUtils() { - } + private LotteryUtils() {} - /** - * Checks if lottery ticket has won. - */ + /** Checks if lottery ticket has won. */ public static LotteryTicketCheckResult checkTicketForPrize( - LotteryTicketRepository repository, - LotteryTicketId id, - LotteryNumbers winningNumbers - ) { + LotteryTicketRepository repository, LotteryTicketId id, LotteryNumbers winningNumbers) { var optional = repository.findById(id); if (optional.isPresent()) { if (optional.get().lotteryNumbers().equals(winningNumbers)) { diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java index fe638ae3226c..5fa3a3145253 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/domain/PlayerDetails.java @@ -24,7 +24,5 @@ */ package com.iluwatar.hexagonal.domain; -/** - * Immutable value object containing lottery player details. - */ +/** Immutable value object containing lottery player details. */ public record PlayerDetails(String email, String bankAccount, String phoneNumber) {} diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java index b33ec322f97a..6cea51b6bbe7 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/LotteryEventLog.java @@ -26,34 +26,21 @@ import com.iluwatar.hexagonal.domain.PlayerDetails; -/** - * Event log for lottery events. - */ +/** Event log for lottery events. */ public interface LotteryEventLog { - /** - * lottery ticket submitted. - */ + /** lottery ticket submitted. */ void ticketSubmitted(PlayerDetails details); - /** - * error submitting lottery ticket. - */ + /** error submitting lottery ticket. */ void ticketSubmitError(PlayerDetails details); - /** - * lottery ticket did not win. - */ + /** lottery ticket did not win. */ void ticketDidNotWin(PlayerDetails details); - /** - * lottery ticket won. - */ + /** lottery ticket won. */ void ticketWon(PlayerDetails details, int prizeAmount); - /** - * error paying the prize. - */ + /** error paying the prize. */ void prizeError(PlayerDetails details, int prizeAmount); - } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java index bc3c621e9d3d..47120d3d7840 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/MongoEventLog.java @@ -31,9 +31,7 @@ import lombok.Getter; import org.bson.Document; -/** - * Mongo based event log. - */ +/** Mongo based event log. */ public class MongoEventLog implements LotteryEventLog { private static final String DEFAULT_DB = "lotteryDB"; @@ -42,45 +40,35 @@ public class MongoEventLog implements LotteryEventLog { private static final String PHONE = "phone"; public static final String MESSAGE = "message"; - @Getter - private MongoClient mongoClient; - @Getter - private MongoDatabase database; - @Getter - private MongoCollection eventsCollection; + @Getter private MongoClient mongoClient; + @Getter private MongoDatabase database; + @Getter private MongoCollection eventsCollection; private final StdOutEventLog stdOutEventLog = new StdOutEventLog(); - /** - * Constructor. - */ + /** Constructor. */ public MongoEventLog() { connect(); } - /** - * Constructor accepting parameters. - */ + /** Constructor accepting parameters. */ public MongoEventLog(String dbName, String eventsCollectionName) { connect(dbName, eventsCollectionName); } - /** - * Connect to database with default parameters. - */ + /** Connect to database with default parameters. */ public void connect() { connect(DEFAULT_DB, DEFAULT_EVENTS_COLLECTION); } - /** - * Connect to database with given parameters. - */ + /** Connect to database with given parameters. */ public void connect(String dbName, String eventsCollectionName) { if (mongoClient != null) { mongoClient.close(); } - mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); database = mongoClient.getDatabase(dbName); eventsCollection = database.getCollection(eventsCollectionName); } @@ -90,8 +78,8 @@ public void ticketSubmitted(PlayerDetails details) { var document = new Document(EMAIL, details.email()); document.put(PHONE, details.phoneNumber()); document.put("bank", details.bankAccount()); - document - .put(MESSAGE, "Lottery ticket was submitted and bank account was charged for 3 credits."); + document.put( + MESSAGE, "Lottery ticket was submitted and bank account was charged for 3 credits."); eventsCollection.insertOne(document); stdOutEventLog.ticketSubmitted(details); } @@ -121,9 +109,10 @@ public void ticketWon(PlayerDetails details, int prizeAmount) { var document = new Document(EMAIL, details.email()); document.put(PHONE, details.phoneNumber()); document.put("bank", details.bankAccount()); - document.put(MESSAGE, String - .format("Lottery ticket won! The bank account was deposited with %d credits.", - prizeAmount)); + document.put( + MESSAGE, + String.format( + "Lottery ticket won! The bank account was deposited with %d credits.", prizeAmount)); eventsCollection.insertOne(document); stdOutEventLog.ticketWon(details, prizeAmount); } @@ -133,8 +122,10 @@ public void prizeError(PlayerDetails details, int prizeAmount) { var document = new Document(EMAIL, details.email()); document.put(PHONE, details.phoneNumber()); document.put("bank", details.bankAccount()); - document.put(MESSAGE, String - .format("Lottery ticket won! Unfortunately the bank credit transfer of %d failed.", + document.put( + MESSAGE, + String.format( + "Lottery ticket won! Unfortunately the bank credit transfer of %d failed.", prizeAmount)); eventsCollection.insertOne(document); stdOutEventLog.prizeError(details, prizeAmount); diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java index d57645115d86..1ddd93d5932b 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/eventlog/StdOutEventLog.java @@ -27,39 +27,47 @@ import com.iluwatar.hexagonal.domain.PlayerDetails; import lombok.extern.slf4j.Slf4j; -/** - * Standard output event log. - */ +/** Standard output event log. */ @Slf4j public class StdOutEventLog implements LotteryEventLog { @Override public void ticketSubmitted(PlayerDetails details) { - LOGGER.info("Lottery ticket for {} was submitted. Bank account {} was charged for 3 credits.", - details.email(), details.bankAccount()); + LOGGER.info( + "Lottery ticket for {} was submitted. Bank account {} was charged for 3 credits.", + details.email(), + details.bankAccount()); } @Override public void ticketDidNotWin(PlayerDetails details) { - LOGGER.info("Lottery ticket for {} was checked and unfortunately did not win this time.", + LOGGER.info( + "Lottery ticket for {} was checked and unfortunately did not win this time.", details.email()); } @Override public void ticketWon(PlayerDetails details, int prizeAmount) { - LOGGER.info("Lottery ticket for {} has won! The bank account {} was deposited with {} credits.", - details.email(), details.bankAccount(), prizeAmount); + LOGGER.info( + "Lottery ticket for {} has won! The bank account {} was deposited with {} credits.", + details.email(), + details.bankAccount(), + prizeAmount); } @Override public void prizeError(PlayerDetails details, int prizeAmount) { - LOGGER.error("Lottery ticket for {} has won! Unfortunately the bank credit transfer of" - + " {} failed.", details.email(), prizeAmount); + LOGGER.error( + "Lottery ticket for {} has won! Unfortunately the bank credit transfer of" + " {} failed.", + details.email(), + prizeAmount); } @Override public void ticketSubmitError(PlayerDetails details) { - LOGGER.error("Lottery ticket for {} could not be submitted because the credit transfer" - + " of 3 credits failed.", details.email()); + LOGGER.error( + "Lottery ticket for {} could not be submitted because the credit transfer" + + " of 3 credits failed.", + details.email()); } } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java index 1f1d16d4e1cd..c45117bc6e6c 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryModule.java @@ -32,9 +32,7 @@ import com.iluwatar.hexagonal.eventlog.LotteryEventLog; import com.iluwatar.hexagonal.eventlog.MongoEventLog; -/** - * Guice module for binding production dependencies. - */ +/** Guice module for binding production dependencies. */ public class LotteryModule extends AbstractModule { @Override protected void configure() { diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java index 1e67f60a7135..1ed43183be3d 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/module/LotteryTestingModule.java @@ -32,9 +32,7 @@ import com.iluwatar.hexagonal.eventlog.LotteryEventLog; import com.iluwatar.hexagonal.eventlog.StdOutEventLog; -/** - * Guice module for testing dependencies. - */ +/** Guice module for testing dependencies. */ public class LotteryTestingModule extends AbstractModule { @Override protected void configure() { diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java index 40261090ec75..5a90579898e9 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/mongo/MongoConnectionPropertiesLoader.java @@ -28,18 +28,14 @@ import java.util.Properties; import lombok.extern.slf4j.Slf4j; -/** - * Mongo connection properties loader. - */ +/** Mongo connection properties loader. */ @Slf4j public class MongoConnectionPropertiesLoader { private static final String DEFAULT_HOST = "localhost"; private static final int DEFAULT_PORT = 27017; - /** - * Try to load connection properties from file. Fall back to default connection properties. - */ + /** Try to load connection properties from file. Fall back to default connection properties. */ public static void load() { var host = DEFAULT_HOST; var port = DEFAULT_PORT; diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java index 2dc191604f90..7d0bb4136f68 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/sampledata/SampleData.java @@ -36,56 +36,54 @@ import java.util.List; import java.util.stream.Collectors; -/** - * Utilities for creating sample lottery tickets. - */ +/** Utilities for creating sample lottery tickets. */ public class SampleData { private static final List PLAYERS; private static final SecureRandom RANDOM = new SecureRandom(); static { - PLAYERS = List.of( - new PlayerDetails("john@google.com", "312-342", "+3242434242"), - new PlayerDetails("mary@google.com", "234-987", "+23452346"), - new PlayerDetails("steve@google.com", "833-836", "+63457543"), - new PlayerDetails("wayne@google.com", "319-826", "+24626"), - new PlayerDetails("johnie@google.com", "983-322", "+3635635"), - new PlayerDetails("andy@google.com", "934-734", "+0898245"), - new PlayerDetails("richard@google.com", "536-738", "+09845325"), - new PlayerDetails("kevin@google.com", "453-936", "+2423532"), - new PlayerDetails("arnold@google.com", "114-988", "+5646346524"), - new PlayerDetails("ian@google.com", "663-765", "+928394235"), - new PlayerDetails("robin@google.com", "334-763", "+35448"), - new PlayerDetails("ted@google.com", "735-964", "+98752345"), - new PlayerDetails("larry@google.com", "734-853", "+043842423"), - new PlayerDetails("calvin@google.com", "334-746", "+73294135"), - new PlayerDetails("jacob@google.com", "444-766", "+358042354"), - new PlayerDetails("edwin@google.com", "895-345", "+9752435"), - new PlayerDetails("mary@google.com", "760-009", "+34203542"), - new PlayerDetails("lolita@google.com", "425-907", "+9872342"), - new PlayerDetails("bruno@google.com", "023-638", "+673824122"), - new PlayerDetails("peter@google.com", "335-886", "+5432503945"), - new PlayerDetails("warren@google.com", "225-946", "+9872341324"), - new PlayerDetails("monica@google.com", "265-748", "+134124"), - new PlayerDetails("ollie@google.com", "190-045", "+34453452"), - new PlayerDetails("yngwie@google.com", "241-465", "+9897641231"), - new PlayerDetails("lars@google.com", "746-936", "+42345298345"), - new PlayerDetails("bobbie@google.com", "946-384", "+79831742"), - new PlayerDetails("tyron@google.com", "310-992", "+0498837412"), - new PlayerDetails("tyrell@google.com", "032-045", "+67834134"), - new PlayerDetails("nadja@google.com", "000-346", "+498723"), - new PlayerDetails("wendy@google.com", "994-989", "+987324454"), - new PlayerDetails("luke@google.com", "546-634", "+987642435"), - new PlayerDetails("bjorn@google.com", "342-874", "+7834325"), - new PlayerDetails("lisa@google.com", "024-653", "+980742154"), - new PlayerDetails("anton@google.com", "834-935", "+876423145"), - new PlayerDetails("bruce@google.com", "284-936", "+09843212345"), - new PlayerDetails("ray@google.com", "843-073", "+678324123"), - new PlayerDetails("ron@google.com", "637-738", "+09842354"), - new PlayerDetails("xavier@google.com", "143-947", "+375245"), - new PlayerDetails("harriet@google.com", "842-404", "+131243252") - ); + PLAYERS = + List.of( + new PlayerDetails("john@google.com", "312-342", "+3242434242"), + new PlayerDetails("mary@google.com", "234-987", "+23452346"), + new PlayerDetails("steve@google.com", "833-836", "+63457543"), + new PlayerDetails("wayne@google.com", "319-826", "+24626"), + new PlayerDetails("johnie@google.com", "983-322", "+3635635"), + new PlayerDetails("andy@google.com", "934-734", "+0898245"), + new PlayerDetails("richard@google.com", "536-738", "+09845325"), + new PlayerDetails("kevin@google.com", "453-936", "+2423532"), + new PlayerDetails("arnold@google.com", "114-988", "+5646346524"), + new PlayerDetails("ian@google.com", "663-765", "+928394235"), + new PlayerDetails("robin@google.com", "334-763", "+35448"), + new PlayerDetails("ted@google.com", "735-964", "+98752345"), + new PlayerDetails("larry@google.com", "734-853", "+043842423"), + new PlayerDetails("calvin@google.com", "334-746", "+73294135"), + new PlayerDetails("jacob@google.com", "444-766", "+358042354"), + new PlayerDetails("edwin@google.com", "895-345", "+9752435"), + new PlayerDetails("mary@google.com", "760-009", "+34203542"), + new PlayerDetails("lolita@google.com", "425-907", "+9872342"), + new PlayerDetails("bruno@google.com", "023-638", "+673824122"), + new PlayerDetails("peter@google.com", "335-886", "+5432503945"), + new PlayerDetails("warren@google.com", "225-946", "+9872341324"), + new PlayerDetails("monica@google.com", "265-748", "+134124"), + new PlayerDetails("ollie@google.com", "190-045", "+34453452"), + new PlayerDetails("yngwie@google.com", "241-465", "+9897641231"), + new PlayerDetails("lars@google.com", "746-936", "+42345298345"), + new PlayerDetails("bobbie@google.com", "946-384", "+79831742"), + new PlayerDetails("tyron@google.com", "310-992", "+0498837412"), + new PlayerDetails("tyrell@google.com", "032-045", "+67834134"), + new PlayerDetails("nadja@google.com", "000-346", "+498723"), + new PlayerDetails("wendy@google.com", "994-989", "+987324454"), + new PlayerDetails("luke@google.com", "546-634", "+987642435"), + new PlayerDetails("bjorn@google.com", "342-874", "+7834325"), + new PlayerDetails("lisa@google.com", "024-653", "+980742154"), + new PlayerDetails("anton@google.com", "834-935", "+876423145"), + new PlayerDetails("bruce@google.com", "284-936", "+09843212345"), + new PlayerDetails("ray@google.com", "843-073", "+678324123"), + new PlayerDetails("ron@google.com", "637-738", "+09842354"), + new PlayerDetails("xavier@google.com", "143-947", "+375245"), + new PlayerDetails("harriet@google.com", "842-404", "+131243252")); var wireTransfers = new InMemoryBank(); PLAYERS.stream() .map(PlayerDetails::bankAccount) @@ -94,9 +92,7 @@ public class SampleData { .forEach(wireTransfers::setFunds); } - /** - * Inserts lottery tickets into the database based on the sample data. - */ + /** Inserts lottery tickets into the database based on the sample data. */ public static void submitTickets(LotteryService lotteryService, int numTickets) { for (var i = 0; i < numTickets; i++) { var randomPlayerDetails = getRandomPlayerDetails(); diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java index b273a828f3da..8832b71a5241 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/ConsoleLottery.java @@ -32,15 +32,11 @@ import java.util.Scanner; import lombok.extern.slf4j.Slf4j; -/** - * Console interface for lottery players. - */ +/** Console interface for lottery players. */ @Slf4j public class ConsoleLottery { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { MongoConnectionPropertiesLoader.load(); var injector = Guice.createInjector(new LotteryModule()); diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java index baa244564fc0..39fbb08c31f4 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleService.java @@ -28,28 +28,17 @@ import com.iluwatar.hexagonal.domain.LotteryService; import java.util.Scanner; - -/** - * Console interface for lottery service. - */ +/** Console interface for lottery service. */ public interface LotteryConsoleService { void checkTicket(LotteryService service, Scanner scanner); - /** - * Submit lottery ticket to participate in the lottery. - */ + /** Submit lottery ticket to participate in the lottery. */ void submitTicket(LotteryService service, Scanner scanner); - /** - * Add funds to lottery account. - */ + /** Add funds to lottery account. */ void addFundsToLotteryAccount(WireTransfers bank, Scanner scanner); - - /** - * Recovery funds from lottery account. - */ + /** Recovery funds from lottery account. */ void queryLotteryAccountFunds(WireTransfers bank, Scanner scanner); - } diff --git a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java index 44f8bea099c0..dbdd16f79a4e 100644 --- a/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java +++ b/hexagonal-architecture/src/main/java/com/iluwatar/hexagonal/service/LotteryConsoleServiceImpl.java @@ -36,16 +36,12 @@ import java.util.stream.Collectors; import org.slf4j.Logger; -/** - * Console implementation for lottery console service. - */ +/** Console implementation for lottery console service. */ public class LotteryConsoleServiceImpl implements LotteryConsoleService { private final Logger logger; - /** - * Constructor. - */ + /** Constructor. */ public LotteryConsoleServiceImpl(Logger logger) { this.logger = logger; } @@ -57,10 +53,11 @@ public void checkTicket(LotteryService service, Scanner scanner) { logger.info("Give the 4 comma separated winning numbers?"); var numbers = readString(scanner); try { - var winningNumbers = Arrays.stream(numbers.split(",")) - .map(Integer::parseInt) - .limit(4) - .collect(Collectors.toSet()); + var winningNumbers = + Arrays.stream(numbers.split(",")) + .map(Integer::parseInt) + .limit(4) + .collect(Collectors.toSet()); final var lotteryTicketId = new LotteryTicketId(Integer.parseInt(id)); final var lotteryNumbers = LotteryNumbers.create(winningNumbers); @@ -90,15 +87,15 @@ public void submitTicket(LotteryService service, Scanner scanner) { logger.info("Give 4 comma separated lottery numbers?"); var numbers = readString(scanner); try { - var chosen = Arrays.stream(numbers.split(",")) - .map(Integer::parseInt) - .collect(Collectors.toSet()); + var chosen = + Arrays.stream(numbers.split(",")).map(Integer::parseInt).collect(Collectors.toSet()); var lotteryNumbers = LotteryNumbers.create(chosen); var lotteryTicket = new LotteryTicket(new LotteryTicketId(), details, lotteryNumbers); - service.submitTicket(lotteryTicket).ifPresentOrElse( - (id) -> logger.info("Submitted lottery ticket with id: {}", id), - () -> logger.info("Failed submitting lottery ticket - please try again.") - ); + service + .submitTicket(lotteryTicket) + .ifPresentOrElse( + (id) -> logger.info("Submitted lottery ticket with id: {}", id), + () -> logger.info("Failed submitting lottery ticket - please try again.")); } catch (Exception e) { logger.info("Failed submitting lottery ticket - please try again."); } diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/AppTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/AppTest.java index 1d8152b837df..1022ccb09c21 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/AppTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/AppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.hexagonal; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Unit test for simple App. - */ +import org.junit.jupiter.api.Test; + +/** Unit test for simple App. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java index 740dea788acd..4e3c1fac7b7d 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/InMemoryBankTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for banking - */ +/** Tests for banking */ class InMemoryBankTest { private final WireTransfers bank = new InMemoryBank(); diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java index 7e5dc39bf04b..8253ac7cb279 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/banking/MongoBankTest.java @@ -39,9 +39,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests for Mongo banking adapter - */ +/** Tests for Mongo banking adapter */ class MongoBankTest { private static final String TEST_DB = "lotteryDBTest"; @@ -56,8 +54,6 @@ class MongoBankTest { private static ServerAddress serverAddress; - - @BeforeAll static void setUp() { mongodProcess = Mongod.instance().start(Version.Main.V7_0); @@ -73,7 +69,6 @@ static void tearDown() { mongodProcess.close(); } - @BeforeEach void init() { System.setProperty("mongo-host", serverAddress.getHost()); @@ -97,4 +92,4 @@ void testFundTransfers() { assertEquals(1, mongoBank.getFunds("000-000")); assertEquals(9, mongoBank.getFunds("111-111")); } -} \ No newline at end of file +} diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java index 6fa1d5a3eb5c..5d9017033edf 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/InMemoryTicketRepositoryTest.java @@ -31,9 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests for {@link LotteryTicketRepository} - */ +/** Tests for {@link LotteryTicketRepository} */ class InMemoryTicketRepositoryTest { private final LotteryTicketRepository repository = new InMemoryTicketRepository(); diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java index ee8329bd9b36..0a45adcace8b 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/database/MongoTicketRepositoryTest.java @@ -37,9 +37,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -/** - * Tests for Mongo based ticket repository - */ +/** Tests for Mongo based ticket repository */ @Disabled class MongoTicketRepositoryTest { @@ -52,12 +50,13 @@ class MongoTicketRepositoryTest { @BeforeEach void init() { MongoConnectionPropertiesLoader.load(); - var mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + var mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); mongoClient.dropDatabase(TEST_DB); mongoClient.close(); - repository = new MongoTicketRepository(TEST_DB, TEST_TICKETS_COLLECTION, - TEST_COUNTERS_COLLECTION); + repository = + new MongoTicketRepository(TEST_DB, TEST_TICKETS_COLLECTION, TEST_COUNTERS_COLLECTION); } @Test diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java index f2690fb391a9..0aa740690802 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryNumbersTest.java @@ -32,9 +32,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link LotteryNumbers} - */ +/** Unit tests for {@link LotteryNumbers} */ class LotteryNumbersTest { @Test diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java index d7f4e951dabf..c71f887882ad 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTest.java @@ -39,18 +39,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test the lottery system - */ +/** Test the lottery system */ class LotteryTest { private final Injector injector; - @Inject - private LotteryAdministration administration; - @Inject - private LotteryService service; - @Inject - private WireTransfers wireTransfers; + @Inject private LotteryAdministration administration; + @Inject private LotteryService service; + @Inject private WireTransfers wireTransfers; LotteryTest() { this.injector = Guice.createInjector(new LotteryTestingModule()); @@ -70,14 +65,20 @@ void testLottery() { assertEquals(0, administration.getAllSubmittedTickets().size()); // players submit the lottery tickets - var ticket1 = service.submitTicket(LotteryTestUtils.createLotteryTicket("cvt@bbb.com", - "123-12312", "+32425255", Set.of(1, 2, 3, 4))); + var ticket1 = + service.submitTicket( + LotteryTestUtils.createLotteryTicket( + "cvt@bbb.com", "123-12312", "+32425255", Set.of(1, 2, 3, 4))); assertTrue(ticket1.isPresent()); - var ticket2 = service.submitTicket(LotteryTestUtils.createLotteryTicket("ant@bac.com", - "123-12312", "+32423455", Set.of(11, 12, 13, 14))); + var ticket2 = + service.submitTicket( + LotteryTestUtils.createLotteryTicket( + "ant@bac.com", "123-12312", "+32423455", Set.of(11, 12, 13, 14))); assertTrue(ticket2.isPresent()); - var ticket3 = service.submitTicket(LotteryTestUtils.createLotteryTicket("arg@boo.com", - "123-12312", "+32421255", Set.of(6, 8, 13, 19))); + var ticket3 = + service.submitTicket( + LotteryTestUtils.createLotteryTicket( + "arg@boo.com", "123-12312", "+32421255", Set.of(6, 8, 13, 19))); assertTrue(ticket3.isPresent()); assertEquals(3, administration.getAllSubmittedTickets().size()); @@ -85,8 +86,10 @@ void testLottery() { var winningNumbers = administration.performLottery(); // cheat a bit for testing sake, use winning numbers to submit another ticket - var ticket4 = service.submitTicket(LotteryTestUtils.createLotteryTicket("lucky@orb.com", - "123-12312", "+12421255", winningNumbers.getNumbers())); + var ticket4 = + service.submitTicket( + LotteryTestUtils.createLotteryTicket( + "lucky@orb.com", "123-12312", "+12421255", winningNumbers.getNumbers())); assertTrue(ticket4.isPresent()); assertEquals(4, administration.getAllSubmittedTickets().size()); diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java index 6e11a95b0144..05af100dc68d 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketCheckResultTest.java @@ -30,9 +30,7 @@ import com.iluwatar.hexagonal.domain.LotteryTicketCheckResult.CheckResult; import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link LotteryTicketCheckResult} - */ +/** Unit tests for {@link LotteryTicketCheckResult} */ class LotteryTicketCheckResultTest { @Test diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java index 8456e77421f6..cde988eaa6d2 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketIdTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for lottery ticket id - */ +/** Tests for lottery ticket id */ class LotteryTicketIdTest { @Test diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java index ce316b347615..9b8e400c8957 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/LotteryTicketTest.java @@ -30,9 +30,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; -/** - * Test Lottery Tickets for equality - */ +/** Test Lottery Tickets for equality */ class LotteryTicketTest { @Test diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java index 732a4b14cd68..266f907797fb 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/domain/PlayerDetailsTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link PlayerDetails} - */ +/** Unit tests for {@link PlayerDetails} */ class PlayerDetailsTest { @Test diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java index 3216ac7ebd51..0227d9a6da6f 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/eventlog/MongoEventLogTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -/** - * Tests for Mongo event log - */ +/** Tests for Mongo event log */ @Disabled class MongoEventLogTest { @@ -47,8 +45,9 @@ class MongoEventLogTest { @BeforeEach void init() { MongoConnectionPropertiesLoader.load(); - var mongoClient = new MongoClient(System.getProperty("mongo-host"), - Integer.parseInt(System.getProperty("mongo-port"))); + var mongoClient = + new MongoClient( + System.getProperty("mongo-host"), Integer.parseInt(System.getProperty("mongo-port"))); mongoClient.dropDatabase(TEST_DB); mongoClient.close(); mongoEventLog = new MongoEventLog(TEST_DB, TEST_EVENTS_COLLECTION); diff --git a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java index 3b6babaa13d0..3002879ba468 100644 --- a/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java +++ b/hexagonal-architecture/src/test/java/com/iluwatar/hexagonal/test/LotteryTestUtils.java @@ -30,9 +30,7 @@ import com.iluwatar.hexagonal.domain.PlayerDetails; import java.util.Set; -/** - * Utilities for lottery tests - */ +/** Utilities for lottery tests */ public class LotteryTestUtils { /** @@ -45,8 +43,8 @@ public static LotteryTicket createLotteryTicket() { /** * @return lottery ticket */ - public static LotteryTicket createLotteryTicket(String email, String account, String phone, - Set givenNumbers) { + public static LotteryTicket createLotteryTicket( + String email, String account, String phone, Set givenNumbers) { var details = new PlayerDetails(email, account, phone); var numbers = LotteryNumbers.create(givenNumbers); return new LotteryTicket(new LotteryTicketId(), details, numbers); diff --git a/identity-map/README.md b/identity-map/README.md index e2c5318bb12f..a63e11423fa2 100644 --- a/identity-map/README.md +++ b/identity-map/README.md @@ -31,6 +31,10 @@ Wikipedia says > In the design of DBMS, the identity map pattern is a database access design pattern used to improve performance by providing a context-specific, in-memory cache to prevent duplicate retrieval of the same object data from the database. +Sequence diagram + +![Identity Map sequence diagram](./etc/identity-map-sequence-diagram.png) + ## Programmatic Example of Identity Map Pattern in Java For the purpose of this demonstration in Java programming, assume we have already created a database instance, showcasing the Identity Map pattern to avoid duplicate objects in memory. diff --git a/identity-map/etc/identity-map-sequence-diagram.png b/identity-map/etc/identity-map-sequence-diagram.png new file mode 100644 index 000000000000..41011f30d266 Binary files /dev/null and b/identity-map/etc/identity-map-sequence-diagram.png differ diff --git a/identity-map/pom.xml b/identity-map/pom.xml index 5125e1130278..cf151c1ffb0a 100644 --- a/identity-map/pom.xml +++ b/identity-map/pom.xml @@ -37,13 +37,16 @@ identity-map - org.junit.jupiter - junit-jupiter-api - test + org.slf4j + slf4j-api - junit - junit + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/App.java b/identity-map/src/main/java/com/iluwatar/identitymap/App.java index 2625531df486..e3538f4b3f83 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/App.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/App.java @@ -27,12 +27,12 @@ import lombok.extern.slf4j.Slf4j; /** - * The basic idea behind the Identity Map is to have a series of maps containing objects that have been pulled from the database. - * The below example demonstrates the identity map pattern by creating a sample DB. - * Since only 1 DB has been created we only have 1 map corresponding to it for the purpose of this demo. - * When you load an object from the database, you first check the map. - * If there’s an object in it that corresponds to the one you’re loading, you return it. If not, you go to the database, - * putting the objects on the map for future reference as you load them. + * The basic idea behind the Identity Map is to have a series of maps containing objects that have + * been pulled from the database. The below example demonstrates the identity map pattern by + * creating a sample DB. Since only 1 DB has been created we only have 1 map corresponding to it for + * the purpose of this demo. When you load an object from the database, you first check the map. If + * there’s an object in it that corresponds to the one you’re loading, you return it. If not, you go + * to the database, putting the objects on the map for future reference as you load them. */ @Slf4j public class App { diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java b/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java index 18ed4cc9fca6..9a02108545a1 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/IdNotFoundException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.identitymap; -/** - * Using Runtime Exception to control the flow in case Person Id doesn't exist. - */ +/** Using Runtime Exception to control the flow in case Person Id doesn't exist. */ public class IdNotFoundException extends RuntimeException { public IdNotFoundException(final String message) { super(message); diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java b/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java index 633dff35d703..7543cfb0bc61 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/IdentityMap.java @@ -30,20 +30,20 @@ import lombok.extern.slf4j.Slf4j; /** - * This class stores the map into which we will be caching records after loading them from a DataBase. - * Stores the records as a Hash Map with the personNationalIDs as keys. + * This class stores the map into which we will be caching records after loading them from a + * DataBase. Stores the records as a Hash Map with the personNationalIDs as keys. */ @Slf4j @Getter public class IdentityMap { private Map personMap = new HashMap<>(); - /** - * Add person to the map. - */ + + /** Add person to the map. */ public void addPerson(Person person) { if (!personMap.containsKey(person.getPersonNationalId())) { personMap.put(person.getPersonNationalId(), person); - } else { // Ensure that addPerson does not update a record. This situation will never arise in our implementation. Added only for testing purposes. + } else { // Ensure that addPerson does not update a record. This situation will never arise in + // our implementation. Added only for testing purposes. LOGGER.info("Key already in Map"); } } @@ -63,14 +63,11 @@ public Person getPerson(int id) { return person; } - /** - * Get the size of the map. - */ + /** Get the size of the map. */ public int size() { if (personMap == null) { return 0; } return personMap.size(); } - } diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/Person.java b/identity-map/src/main/java/com/iluwatar/identitymap/Person.java index e3e7b872997a..bc14bff50451 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/Person.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/Person.java @@ -31,28 +31,27 @@ import lombok.Getter; import lombok.Setter; -/** - * Person definition. - */ +/** Person definition. */ @EqualsAndHashCode(onlyExplicitlyIncluded = true) @Getter @Setter @AllArgsConstructor public final class Person implements Serializable { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; - @EqualsAndHashCode.Include - private int personNationalId; + @EqualsAndHashCode.Include private int personNationalId; private String name; private long phoneNum; @Override public String toString() { - return "Person ID is : " + personNationalId + " ; Person Name is : " + name + " ; Phone Number is :" + phoneNum; - + return "Person ID is : " + + personNationalId + + " ; Person Name is : " + + name + + " ; Phone Number is :" + + phoneNum; } - } diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java index 7cee5e06a95f..8a2d7481ddd2 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulator.java @@ -24,9 +24,7 @@ */ package com.iluwatar.identitymap; -/** - * Simulator interface for Person DB. - */ +/** Simulator interface for Person DB. */ public interface PersonDbSimulator { Person find(int personNationalId); @@ -35,5 +33,4 @@ public interface PersonDbSimulator { void update(Person person); void delete(int personNationalId); - } diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java index 22abb44f2da4..e6c4be3eb89b 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/PersonDbSimulatorImplementation.java @@ -30,26 +30,26 @@ import lombok.extern.slf4j.Slf4j; /** - * This is a sample database implementation. The database is in the form of an arraylist which stores records of - * different persons. The personNationalId acts as the primary key for a record. - * Operations : - * -> find (look for object with a particular ID) - * -> insert (insert record for a new person into the database) - * -> update (update the record of a person). To do this, create a new person instance with the same ID as the record you - * want to update. Then call this method with that person as an argument. - * -> delete (delete the record for a particular ID) + * This is a sample database implementation. The database is in the form of an arraylist which + * stores records of different persons. The personNationalId acts as the primary key for a record. + * Operations : -> find (look for object with a particular ID) -> insert (insert record for a new + * person into the database) -> update (update the record of a person). To do this, create a new + * person instance with the same ID as the record you want to update. Then call this method with + * that person as an argument. -> delete (delete the record for a particular ID) */ @Slf4j public class PersonDbSimulatorImplementation implements PersonDbSimulator { - //This simulates a table in the database. To extend logic to multiple tables just add more lists to the implementation. + // This simulates a table in the database. To extend logic to multiple tables just add more lists + // to the implementation. private List personList = new ArrayList<>(); static final String NOT_IN_DATA_BASE = " not in DataBase"; static final String ID_STR = "ID : "; @Override public Person find(int personNationalId) throws IdNotFoundException { - Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == personNationalId).findFirst(); + Optional elem = + personList.stream().filter(p -> p.getPersonNationalId() == personNationalId).findFirst(); if (elem.isEmpty()) { throw new IdNotFoundException(ID_STR + personNationalId + NOT_IN_DATA_BASE); } @@ -59,7 +59,10 @@ public Person find(int personNationalId) throws IdNotFoundException { @Override public void insert(Person person) { - Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == person.getPersonNationalId()).findFirst(); + Optional elem = + personList.stream() + .filter(p -> p.getPersonNationalId() == person.getPersonNationalId()) + .findFirst(); if (elem.isPresent()) { LOGGER.info("Record already exists."); return; @@ -69,7 +72,10 @@ public void insert(Person person) { @Override public void update(Person person) throws IdNotFoundException { - Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == person.getPersonNationalId()).findFirst(); + Optional elem = + personList.stream() + .filter(p -> p.getPersonNationalId() == person.getPersonNationalId()) + .findFirst(); if (elem.isPresent()) { elem.get().setName(person.getName()); elem.get().setPhoneNum(person.getPhoneNum()); @@ -85,7 +91,8 @@ public void update(Person person) throws IdNotFoundException { * @param id : personNationalId for person whose record is to be deleted. */ public void delete(int id) throws IdNotFoundException { - Optional elem = personList.stream().filter(p -> p.getPersonNationalId() == id).findFirst(); + Optional elem = + personList.stream().filter(p -> p.getPersonNationalId() == id).findFirst(); if (elem.isPresent()) { personList.remove(elem.get()); LOGGER.info("Record deleted successfully."); @@ -94,14 +101,11 @@ public void delete(int id) throws IdNotFoundException { throw new IdNotFoundException(ID_STR + id + NOT_IN_DATA_BASE); } - /** - * Return the size of the database. - */ + /** Return the size of the database. */ public int size() { if (personList == null) { return 0; } return personList.size(); } - } diff --git a/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java b/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java index d358e24e4857..35cbcf84ea67 100644 --- a/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java +++ b/identity-map/src/main/java/com/iluwatar/identitymap/PersonFinder.java @@ -29,11 +29,11 @@ import lombok.extern.slf4j.Slf4j; /** - * Any object of this class stores a DataBase and an Identity Map. When we try to look for a key we first check if - * it has been cached in the Identity Map and return it if it is indeed in the map. - * If that is not the case then go to the DataBase, get the record, store it in the - * Identity Map and then return the record. Now if we look for the record again we will find it in the table itself which - * will make lookup faster. + * Any object of this class stores a DataBase and an Identity Map. When we try to look for a key we + * first check if it has been cached in the Identity Map and return it if it is indeed in the map. + * If that is not the case then go to the DataBase, get the record, store it in the Identity Map and + * then return the record. Now if we look for the record again we will find it in the table itself + * which will make lookup faster. */ @Slf4j @Getter @@ -43,6 +43,7 @@ public class PersonFinder { // Access to the Identity Map private IdentityMap identityMap = new IdentityMap(); private PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); + /** * get person corresponding to input ID. * diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java index b946a44a602e..8ef0e78d6078 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/AppTest.java @@ -23,13 +23,14 @@ * THE SOFTWARE. */ package com.iluwatar.identitymap; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - class AppTest { +import org.junit.jupiter.api.Test; + +class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java index 74bbe3d412f5..98289e6d5ec7 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/IdentityMapTest.java @@ -29,7 +29,7 @@ class IdentityMapTest { @Test - void addToMap(){ + void addToMap() { // new instance of an identity map(not connected to any DB here) IdentityMap idMap = new IdentityMap(); // Dummy person instances @@ -46,10 +46,12 @@ void addToMap(){ idMap.addPerson(person4); idMap.addPerson(person5); // Test no duplicate in our Map. - Assertions.assertEquals(4,idMap.size(),"Size of the map is incorrect"); + Assertions.assertEquals(4, idMap.size(), "Size of the map is incorrect"); // Test record not updated by add method. - Assertions.assertEquals(27304159,idMap.getPerson(11).getPhoneNum(),"Incorrect return value for phone number"); + Assertions.assertEquals( + 27304159, idMap.getPerson(11).getPhoneNum(), "Incorrect return value for phone number"); } + @Test void testGetFromMap() { // new instance of an identity map(not connected to any DB here) @@ -67,8 +69,8 @@ void testGetFromMap() { idMap.addPerson(person4); idMap.addPerson(person5); // Test for dummy persons in the map - Assertions.assertEquals(person1,idMap.getPerson(11),"Incorrect person record returned"); - Assertions.assertEquals(person4,idMap.getPerson(44),"Incorrect person record returned"); + Assertions.assertEquals(person1, idMap.getPerson(11), "Incorrect person record returned"); + Assertions.assertEquals(person4, idMap.getPerson(44), "Incorrect person record returned"); // Test for person with given id not in map Assertions.assertNull(idMap.getPerson(1), "Incorrect person record returned"); } diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java index 6823f39193cb..7861cb2f6200 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/PersonDbSimulatorImplementationTest.java @@ -29,10 +29,10 @@ class PersonDbSimulatorImplementationTest { @Test - void testInsert(){ + void testInsert() { // DataBase initialization. PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); - Assertions.assertEquals(0,db.size(),"Size of null database should be 0"); + Assertions.assertEquals(0, db.size(), "Size of null database should be 0"); // Dummy persons. Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); @@ -41,71 +41,77 @@ void testInsert(){ db.insert(person2); db.insert(person3); // Test size after insertion. - Assertions.assertEquals(3,db.size(),"Incorrect size for database."); + Assertions.assertEquals(3, db.size(), "Incorrect size for database."); Person person4 = new Person(4, "Finn", 20499078); Person person5 = new Person(5, "Michael", 40599078); db.insert(person4); db.insert(person5); // Test size after more insertions. - Assertions.assertEquals(5,db.size(),"Incorrect size for database."); - Person person5duplicate = new Person(5,"Kevin",89589122); + Assertions.assertEquals(5, db.size(), "Incorrect size for database."); + Person person5duplicate = new Person(5, "Kevin", 89589122); db.insert(person5duplicate); // Test size after attempt to insert record with duplicate key. - Assertions.assertEquals(5,db.size(),"Incorrect size for data base"); + Assertions.assertEquals(5, db.size(), "Incorrect size for data base"); } + @Test - void findNotInDb(){ + void findNotInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); // Test if IdNotFoundException is thrown where expected. - Assertions.assertThrows(IdNotFoundException.class,()->db.find(3)); + Assertions.assertThrows(IdNotFoundException.class, () -> db.find(3)); } + @Test - void findInDb(){ + void findInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); - Assertions.assertEquals(person2,db.find(2),"Record that was found was incorrect."); + Assertions.assertEquals(person2, db.find(2), "Record that was found was incorrect."); } + @Test - void updateNotInDb(){ + void updateNotInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); - Person person3 = new Person(3,"Micheal",25671234); + Person person3 = new Person(3, "Micheal", 25671234); // Test if IdNotFoundException is thrown when person with ID 3 is not in DB. - Assertions.assertThrows(IdNotFoundException.class,()->db.update(person3)); + Assertions.assertThrows(IdNotFoundException.class, () -> db.update(person3)); } + @Test - void updateInDb(){ + void updateInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); - Person person = new Person(2,"Thomas",42273690); + Person person = new Person(2, "Thomas", 42273690); db.update(person); - Assertions.assertEquals(person,db.find(2),"Incorrect update."); + Assertions.assertEquals(person, db.find(2), "Incorrect update."); } + @Test - void deleteNotInDb(){ + void deleteNotInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); db.insert(person1); db.insert(person2); // Test if IdNotFoundException is thrown when person with this ID not in DB. - Assertions.assertThrows(IdNotFoundException.class,()->db.delete(3)); + Assertions.assertThrows(IdNotFoundException.class, () -> db.delete(3)); } + @Test - void deleteInDb(){ + void deleteInDb() { PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); Person person1 = new Person(1, "Thomas", 27304159); Person person2 = new Person(2, "John", 42273631); @@ -114,9 +120,8 @@ void deleteInDb(){ // delete the record. db.delete(1); // test size of database after deletion. - Assertions.assertEquals(1,db.size(),"Size after deletion is incorrect."); + Assertions.assertEquals(1, db.size(), "Size after deletion is incorrect."); // try to find deleted record in db. - Assertions.assertThrows(IdNotFoundException.class,()->db.find(1)); + Assertions.assertThrows(IdNotFoundException.class, () -> db.find(1)); } - } diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java index 88f70604844b..9257a3b0a28c 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/PersonFinderTest.java @@ -29,7 +29,7 @@ class PersonFinderTest { @Test - void personFoundInDB(){ + void personFoundInDB() { // personFinderInstance PersonFinder personFinder = new PersonFinder(); // init database for our personFinder @@ -48,14 +48,20 @@ void personFoundInDB(){ db.insert(person5); personFinder.setDb(db); - Assertions.assertEquals(person1,personFinder.getPerson(1),"Find person returns incorrect record."); - Assertions.assertEquals(person3,personFinder.getPerson(3),"Find person returns incorrect record."); - Assertions.assertEquals(person2,personFinder.getPerson(2),"Find person returns incorrect record."); - Assertions.assertEquals(person5,personFinder.getPerson(5),"Find person returns incorrect record."); - Assertions.assertEquals(person4,personFinder.getPerson(4),"Find person returns incorrect record."); + Assertions.assertEquals( + person1, personFinder.getPerson(1), "Find person returns incorrect record."); + Assertions.assertEquals( + person3, personFinder.getPerson(3), "Find person returns incorrect record."); + Assertions.assertEquals( + person2, personFinder.getPerson(2), "Find person returns incorrect record."); + Assertions.assertEquals( + person5, personFinder.getPerson(5), "Find person returns incorrect record."); + Assertions.assertEquals( + person4, personFinder.getPerson(4), "Find person returns incorrect record."); } + @Test - void personFoundInIdMap(){ + void personFoundInIdMap() { // personFinderInstance PersonFinder personFinder = new PersonFinder(); // init database for our personFinder @@ -76,19 +82,20 @@ void personFoundInIdMap(){ // Assure key is not in the ID map. Assertions.assertFalse(personFinder.getIdentityMap().getPersonMap().containsKey(3)); // Assure key is in the database. - Assertions.assertEquals(person3,personFinder.getPerson(3),"Finder returns incorrect record."); + Assertions.assertEquals(person3, personFinder.getPerson(3), "Finder returns incorrect record."); // Assure that the record for this key is cached in the Map now. Assertions.assertTrue(personFinder.getIdentityMap().getPersonMap().containsKey(3)); // Find the record again. This time it will be found in the map. - Assertions.assertEquals(person3,personFinder.getPerson(3),"Finder returns incorrect record."); + Assertions.assertEquals(person3, personFinder.getPerson(3), "Finder returns incorrect record."); } + @Test - void personNotFoundInDB(){ + void personNotFoundInDB() { PersonFinder personFinder = new PersonFinder(); // init database for our personFinder PersonDbSimulatorImplementation db = new PersonDbSimulatorImplementation(); personFinder.setDb(db); - Assertions.assertThrows(IdNotFoundException.class,()->personFinder.getPerson(1)); + Assertions.assertThrows(IdNotFoundException.class, () -> personFinder.getPerson(1)); // Dummy persons Person person1 = new Person(1, "John", 27304159); Person person2 = new Person(2, "Thomas", 42273631); @@ -102,11 +109,10 @@ void personNotFoundInDB(){ db.insert(person5); personFinder.setDb(db); // Assure that the database has been updated. - Assertions.assertEquals(person4,personFinder.getPerson(4),"Find returns incorrect record"); + Assertions.assertEquals(person4, personFinder.getPerson(4), "Find returns incorrect record"); // Assure key is in DB now. - Assertions.assertDoesNotThrow(()->personFinder.getPerson(1)); + Assertions.assertDoesNotThrow(() -> personFinder.getPerson(1)); // Assure key not in DB. - Assertions.assertThrows(IdNotFoundException.class,()->personFinder.getPerson(6)); - + Assertions.assertThrows(IdNotFoundException.class, () -> personFinder.getPerson(6)); } } diff --git a/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java b/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java index 7c491bf597f5..8199daaa2a62 100644 --- a/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java +++ b/identity-map/src/test/java/com/iluwatar/identitymap/PersonTest.java @@ -29,15 +29,15 @@ class PersonTest { @Test - void testEquality(){ + void testEquality() { // dummy persons. - Person person1 = new Person(1,"Harry",989950022); - Person person2 = new Person(2,"Kane",989920011); - Assertions.assertNotEquals(person1,person2,"Incorrect equality condition"); + Person person1 = new Person(1, "Harry", 989950022); + Person person2 = new Person(2, "Kane", 989920011); + Assertions.assertNotEquals(person1, person2, "Incorrect equality condition"); // person with duplicate nationalID. - Person person3 = new Person(2,"John",789012211); + Person person3 = new Person(2, "John", 789012211); // If nationalID is equal then persons are equal(even if name or phoneNum are different). // This situation will never arise in this implementation. Only for testing. - Assertions.assertEquals(person2,person3,"Incorrect inequality condition"); + Assertions.assertEquals(person2, person3, "Incorrect inequality condition"); } } diff --git a/intercepting-filter/README.md b/intercepting-filter/README.md index 6bf0ec4882c3..c6f6723f534f 100644 --- a/intercepting-filter/README.md +++ b/intercepting-filter/README.md @@ -31,6 +31,10 @@ Wikipedia says > Intercepting Filter is a Java pattern which creates pluggable filters to process common services in a standard manner without requiring changes to core request processing code. +Architecture diagram + +![Intercepting Filter Architecture Diagram](./etc/intercepting-filter-architecture-diagram.png) + ## Programmatic Example of Intercepting Filter Pattern in Java In this article, we delve into the Intercepting Filter Pattern and provide a Java example to illustrate its use. This pattern is essential for Java web development, offering a modular approach to handling common services such as logging, authentication, and data compression. diff --git a/intercepting-filter/etc/intercepting-filter-architecture-diagram.png b/intercepting-filter/etc/intercepting-filter-architecture-diagram.png new file mode 100644 index 000000000000..f70c0ec0c441 Binary files /dev/null and b/intercepting-filter/etc/intercepting-filter-architecture-diagram.png differ diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AbstractFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AbstractFilter.java index 6972873254b2..7803ef878571 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AbstractFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AbstractFilter.java @@ -24,15 +24,12 @@ */ package com.iluwatar.intercepting.filter; -/** - * Base class for order processing filters. Handles chain management. - */ +/** Base class for order processing filters. Handles chain management. */ public abstract class AbstractFilter implements Filter { private Filter next; - public AbstractFilter() { - } + public AbstractFilter() {} public AbstractFilter(Filter next) { this.next = next; diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java index 9e08ea008a77..0f8aeeb5567e 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/AddressFilter.java @@ -27,7 +27,6 @@ /** * Concrete implementation of filter This filter is responsible for checking/filtering the input in * the address field. - * */ public class AddressFilter extends AbstractFilter { diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java index 454cc88009db..53559ede1a78 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/App.java @@ -44,7 +44,6 @@ * *

    In this example we check whether the order request is valid through pre-processing done via * {@link Filter}. Each field has its own corresponding {@link Filter}. - * */ public class App { diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java index ea3d3a728503..96f2e2acdc6f 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Client.java @@ -45,12 +45,10 @@ * *

    This is where {@link Filter}s come to play as the client pre-processes the request before * being displayed in the {@link Target}. - * */ public class Client extends JFrame { // NOSONAR - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private transient FilterManager filterManager; private final JLabel jl; @@ -59,9 +57,7 @@ public class Client extends JFrame { // NOSONAR private final JButton clearButton; private final JButton processButton; - /** - * Constructor. - */ + /** Constructor. */ public Client() { super("Client System"); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); @@ -100,10 +96,11 @@ private void setup() { panel.add(clearButton); panel.add(processButton); - clearButton.addActionListener(e -> { - Arrays.stream(jtAreas).forEach(i -> i.setText("")); - Arrays.stream(jtFields).forEach(i -> i.setText("")); - }); + clearButton.addActionListener( + e -> { + Arrays.stream(jtAreas).forEach(i -> i.setText("")); + Arrays.stream(jtFields).forEach(i -> i.setText("")); + }); processButton.addActionListener(this::actionPerformed); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/ContactFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/ContactFilter.java index 96f3f5582db4..14b4914444d5 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/ContactFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/ContactFilter.java @@ -28,7 +28,6 @@ * Concrete implementation of filter This filter checks for the contact field in which it checks if * the input consist of numbers and it also checks if the input follows the length constraint (11 * digits). - * */ public class ContactFilter extends AbstractFilter { @@ -36,7 +35,8 @@ public class ContactFilter extends AbstractFilter { public String execute(Order order) { var result = super.execute(order); var contactNumber = order.getContactNumber(); - if (contactNumber == null || contactNumber.matches(".*[^\\d]+.*") + if (contactNumber == null + || contactNumber.matches(".*[^\\d]+.*") || contactNumber.length() != 11) { return result + "Invalid contact number! "; } else { diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/DepositFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/DepositFilter.java index b56abb9f3837..2c13d831e5ac 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/DepositFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/DepositFilter.java @@ -24,10 +24,7 @@ */ package com.iluwatar.intercepting.filter; -/** - * Concrete implementation of filter This checks for the deposit code. - * - */ +/** Concrete implementation of filter This checks for the deposit code. */ public class DepositFilter extends AbstractFilter { @Override diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java index 82c36144e559..fcadf86a7a4a 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Filter.java @@ -27,27 +27,18 @@ /** * Filters perform certain tasks prior or after execution of request by request handler. In this * case, before the request is handled by the target, the request undergoes through each Filter - * */ public interface Filter { - /** - * Execute order processing filter. - */ + /** Execute order processing filter. */ String execute(Order order); - /** - * Set next filter in chain after this. - */ + /** Set next filter in chain after this. */ void setNext(Filter filter); - /** - * Get next filter in chain after this. - */ + /** Get next filter in chain after this. */ Filter getNext(); - /** - * Get last filter in the chain. - */ + /** Get last filter in the chain. */ Filter getLast(); } diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java index bf71d23c62c8..1fc27095a341 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterChain.java @@ -24,19 +24,12 @@ */ package com.iluwatar.intercepting.filter; - -/** - * Filter Chain carries multiple filters and help to execute them in defined order on target. - * - */ +/** Filter Chain carries multiple filters and help to execute them in defined order on target. */ public class FilterChain { private Filter chain; - - /** - * Adds filter. - */ + /** Adds filter. */ public void addFilter(Filter filter) { if (chain == null) { chain = filter; @@ -45,9 +38,7 @@ public void addFilter(Filter filter) { } } - /** - * Execute filter chain. - */ + /** Execute filter chain. */ public String execute(Order order) { if (chain != null) { return chain.execute(order); diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java index 62f20d905630..93303dd9ad60 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/FilterManager.java @@ -24,10 +24,7 @@ */ package com.iluwatar.intercepting.filter; -/** - * Filter Manager manages the filters and {@link FilterChain}. - * - */ +/** Filter Manager manages the filters and {@link FilterChain}. */ public class FilterManager { private final FilterChain filterChain; diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java index aed8346abde8..19f80a4eec08 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/NameFilter.java @@ -27,7 +27,6 @@ /** * Concrete implementation of filter. This filter checks if the input in the Name field is valid. * (alphanumeric) - * */ public class NameFilter extends AbstractFilter { diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java index 778c9d40304f..576b26a4c3e1 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Order.java @@ -27,12 +27,9 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; import lombok.Setter; -/** - * Order class carries the order data. - */ +/** Order class carries the order data. */ @Getter @Setter @NoArgsConstructor diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/OrderFilter.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/OrderFilter.java index 131359461782..f3eed5f1a1a1 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/OrderFilter.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/OrderFilter.java @@ -24,10 +24,7 @@ */ package com.iluwatar.intercepting.filter; -/** - * Concrete implementation of filter. This checks for the order field. - * - */ +/** Concrete implementation of filter. This checks for the order field. */ public class OrderFilter extends AbstractFilter { @Override diff --git a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java index b228a332fe2f..92b76745e1c9 100644 --- a/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java +++ b/intercepting-filter/src/main/java/com/iluwatar/intercepting/filter/Target.java @@ -39,28 +39,23 @@ import javax.swing.WindowConstants; import javax.swing.table.DefaultTableModel; -/** - * This is where the requests are displayed after being validated by filters. - * - */ -public class Target extends JFrame { //NOSONAR +/** This is where the requests are displayed after being validated by filters. */ +public class Target extends JFrame { // NOSONAR - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private final JTable jt; private final DefaultTableModel dtm; private final JButton del; - /** - * Constructor. - */ + /** Constructor. */ public Target() { super("Order System"); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); setSize(640, 480); - dtm = new DefaultTableModel( - new Object[]{"Name", "Contact Number", "Address", "Deposit Number", "Order"}, 0); + dtm = + new DefaultTableModel( + new Object[] {"Name", "Contact Number", "Address", "Deposit Number", "Order"}, 0); jt = new JTable(dtm); del = new JButton("Delete"); setup(); @@ -85,7 +80,7 @@ private void setup() { } public void execute(String[] request) { - dtm.addRow(new Object[]{request[0], request[1], request[2], request[3], request[4]}); + dtm.addRow(new Object[] {request[0], request[1], request[2], request[3], request[4]}); } class TargetListener implements ActionListener { diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java index 78d4d0bdd7d9..27e5aaba2e05 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.intercepting.filter; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java index 74c86e204949..969386c823cc 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterManagerTest.java @@ -34,10 +34,7 @@ import org.junit.jupiter.api.Test; -/** - * FilterManagerTest - * - */ +/** FilterManagerTest */ class FilterManagerTest { @Test @@ -66,4 +63,4 @@ void testAddFilter() { verify(filter, times(1)).execute(any(Order.class)); verifyNoMoreInteractions(target, filter, order); } -} \ No newline at end of file +} diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java index 8377a88d2609..60b12a9302e7 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/FilterTest.java @@ -33,10 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * FilterTest - * - */ +/** FilterTest */ class FilterTest { private static final Order PERFECT_ORDER = @@ -49,41 +46,36 @@ class FilterTest { static List getTestData() { return List.of( - new Object[]{new NameFilter(), PERFECT_ORDER, ""}, - new Object[]{new NameFilter(), WRONG_NAME, "Invalid name!"}, - new Object[]{new NameFilter(), WRONG_CONTACT, ""}, - new Object[]{new NameFilter(), WRONG_ADDRESS, ""}, - new Object[]{new NameFilter(), WRONG_DEPOSIT, ""}, - new Object[]{new NameFilter(), WRONG_ORDER, ""}, - - new Object[]{new ContactFilter(), PERFECT_ORDER, ""}, - new Object[]{new ContactFilter(), WRONG_NAME, ""}, - new Object[]{new ContactFilter(), WRONG_CONTACT, "Invalid contact number!"}, - new Object[]{new ContactFilter(), WRONG_ADDRESS, ""}, - new Object[]{new ContactFilter(), WRONG_DEPOSIT, ""}, - new Object[]{new ContactFilter(), WRONG_ORDER, ""}, - - new Object[]{new AddressFilter(), PERFECT_ORDER, ""}, - new Object[]{new AddressFilter(), WRONG_NAME, ""}, - new Object[]{new AddressFilter(), WRONG_CONTACT, ""}, - new Object[]{new AddressFilter(), WRONG_ADDRESS, "Invalid address!"}, - new Object[]{new AddressFilter(), WRONG_DEPOSIT, ""}, - new Object[]{new AddressFilter(), WRONG_ORDER, ""}, - - new Object[]{new DepositFilter(), PERFECT_ORDER, ""}, - new Object[]{new DepositFilter(), WRONG_NAME, ""}, - new Object[]{new DepositFilter(), WRONG_CONTACT, ""}, - new Object[]{new DepositFilter(), WRONG_ADDRESS, ""}, - new Object[]{new DepositFilter(), WRONG_DEPOSIT, "Invalid deposit number!"}, - new Object[]{new DepositFilter(), WRONG_ORDER, ""}, - - new Object[]{new OrderFilter(), PERFECT_ORDER, ""}, - new Object[]{new OrderFilter(), WRONG_NAME, ""}, - new Object[]{new OrderFilter(), WRONG_CONTACT, ""}, - new Object[]{new OrderFilter(), WRONG_ADDRESS, ""}, - new Object[]{new OrderFilter(), WRONG_DEPOSIT, ""}, - new Object[]{new OrderFilter(), WRONG_ORDER, "Invalid order!"} - ); + new Object[] {new NameFilter(), PERFECT_ORDER, ""}, + new Object[] {new NameFilter(), WRONG_NAME, "Invalid name!"}, + new Object[] {new NameFilter(), WRONG_CONTACT, ""}, + new Object[] {new NameFilter(), WRONG_ADDRESS, ""}, + new Object[] {new NameFilter(), WRONG_DEPOSIT, ""}, + new Object[] {new NameFilter(), WRONG_ORDER, ""}, + new Object[] {new ContactFilter(), PERFECT_ORDER, ""}, + new Object[] {new ContactFilter(), WRONG_NAME, ""}, + new Object[] {new ContactFilter(), WRONG_CONTACT, "Invalid contact number!"}, + new Object[] {new ContactFilter(), WRONG_ADDRESS, ""}, + new Object[] {new ContactFilter(), WRONG_DEPOSIT, ""}, + new Object[] {new ContactFilter(), WRONG_ORDER, ""}, + new Object[] {new AddressFilter(), PERFECT_ORDER, ""}, + new Object[] {new AddressFilter(), WRONG_NAME, ""}, + new Object[] {new AddressFilter(), WRONG_CONTACT, ""}, + new Object[] {new AddressFilter(), WRONG_ADDRESS, "Invalid address!"}, + new Object[] {new AddressFilter(), WRONG_DEPOSIT, ""}, + new Object[] {new AddressFilter(), WRONG_ORDER, ""}, + new Object[] {new DepositFilter(), PERFECT_ORDER, ""}, + new Object[] {new DepositFilter(), WRONG_NAME, ""}, + new Object[] {new DepositFilter(), WRONG_CONTACT, ""}, + new Object[] {new DepositFilter(), WRONG_ADDRESS, ""}, + new Object[] {new DepositFilter(), WRONG_DEPOSIT, "Invalid deposit number!"}, + new Object[] {new DepositFilter(), WRONG_ORDER, ""}, + new Object[] {new OrderFilter(), PERFECT_ORDER, ""}, + new Object[] {new OrderFilter(), WRONG_NAME, ""}, + new Object[] {new OrderFilter(), WRONG_CONTACT, ""}, + new Object[] {new OrderFilter(), WRONG_ADDRESS, ""}, + new Object[] {new OrderFilter(), WRONG_DEPOSIT, ""}, + new Object[] {new OrderFilter(), WRONG_ORDER, "Invalid order!"}); } @ParameterizedTest @@ -100,5 +92,4 @@ void testNext(Filter filter) { assertNull(filter.getNext()); assertSame(filter, filter.getLast()); } - } diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java index ffa282fcb35a..3cb6ed3c8acf 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/OrderTest.java @@ -28,10 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * OrderTest - * - */ +/** OrderTest */ class OrderTest { private static final String EXPECTED_VALUE = "test"; @@ -70,5 +67,4 @@ void testSetOrder() { order.setOrderItem(EXPECTED_VALUE); assertEquals(EXPECTED_VALUE, order.getOrderItem()); } - } diff --git a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/TargetTest.java b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/TargetTest.java index a1164f74c7f1..c202020f7f55 100644 --- a/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/TargetTest.java +++ b/intercepting-filter/src/test/java/com/iluwatar/intercepting/filter/TargetTest.java @@ -29,17 +29,14 @@ import org.junit.jupiter.api.Test; -/** - * TargetTest - * - */ +/** TargetTest */ class TargetTest { - - @Test - void testSetup(){ - final var target = new Target(); - assertEquals(target.getSize().getWidth(), Double.valueOf(640)); - assertEquals(target.getSize().getHeight(), Double.valueOf(480)); - assertTrue(target.isVisible()); - } + + @Test + void testSetup() { + final var target = new Target(); + assertEquals(target.getSize().getWidth(), Double.valueOf(640)); + assertEquals(target.getSize().getHeight(), Double.valueOf(480)); + assertTrue(target.isVisible()); + } } diff --git a/interpreter/README.md b/interpreter/README.md index 0eeb035dcfe3..9e9dd54397f8 100644 --- a/interpreter/README.md +++ b/interpreter/README.md @@ -32,6 +32,10 @@ Wikipedia says > In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol (terminal or nonterminal) in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence for a client. +Sequence diagram + +![Interpreter sequence diagram](./etc/interpreter-sequence-diagram.png) + ## Programmatic Example of Interpreter Pattern in Java To be able to interpret basic math in Java, we need a hierarchy of expressions. The `Expression` class is the base, and concrete implementations like `NumberExpression` handle specific parts of the grammar. The Interpreter pattern in Java simplifies parsing and evaluating arithmetic expressions by translating them into a structured format that the application can process. @@ -174,10 +178,6 @@ Executing the program produces the following console output. 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- result: 8 ``` -## Detailed Explanation of Interpreter Pattern with Real-World Examples - -![Interpreter](./etc/interpreter_1.png "Interpreter") - ## When to Use the Interpreter Pattern in Java Use the Interpreter design pattern when there is a language to interpret, and you can represent statements in the language as abstract syntax trees. The Interpreter pattern works best when diff --git a/interpreter/etc/interpreter-sequence-diagram.png b/interpreter/etc/interpreter-sequence-diagram.png new file mode 100644 index 000000000000..fbee04524724 Binary files /dev/null and b/interpreter/etc/interpreter-sequence-diagram.png differ diff --git a/interpreter/pom.xml b/interpreter/pom.xml index 9f2129424fa4..86c45ce224b2 100644 --- a/interpreter/pom.xml +++ b/interpreter/pom.xml @@ -34,6 +34,14 @@ interpreter + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/App.java b/interpreter/src/main/java/com/iluwatar/interpreter/App.java index df4dd1ec441b..7f0c0b2444b2 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/App.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/App.java @@ -38,13 +38,13 @@ * *

    Expressions can be evaluated using prefix, infix or postfix notations This sample uses * postfix, where operator comes after the operands. - * */ @Slf4j public class App { /** * Program entry point. + * * @param args program arguments */ public static void main(String[] args) { @@ -64,8 +64,10 @@ public static void main(String[] args) { // the stack var rightExpression = stack.pop(); var leftExpression = stack.pop(); - LOGGER.info("popped from stack left: {} right: {}", - leftExpression.interpret(), rightExpression.interpret()); + LOGGER.info( + "popped from stack left: {} right: {}", + leftExpression.interpret(), + rightExpression.interpret()); var operator = getOperatorInstance(s, leftExpression, rightExpression); LOGGER.info("operator: {}", operator); var result = operator.interpret(); @@ -86,6 +88,7 @@ public static void main(String[] args) { /** * Checks whether the input parameter is an operator. + * * @param s input string * @return true if the input parameter is an operator */ @@ -95,6 +98,7 @@ public static boolean isOperator(String s) { /** * Returns correct expression based on the parameters. + * * @param s input string * @param left expression * @param right expression diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/Expression.java b/interpreter/src/main/java/com/iluwatar/interpreter/Expression.java index a5f364d368b0..44b4ea278b0c 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/Expression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/Expression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * Expression. - */ +/** Expression. */ public abstract class Expression { public abstract int interpret(); diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java index 12738add6d9e..c6d7f55812f1 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/MinusExpression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * MinusExpression. - */ +/** MinusExpression. */ public class MinusExpression extends Expression { private final Expression leftExpression; @@ -46,5 +44,4 @@ public int interpret() { public String toString() { return "-"; } - } diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java index d3803526d955..a5c1efe4fb63 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/MultiplyExpression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * MultiplyExpression. - */ +/** MultiplyExpression. */ public class MultiplyExpression extends Expression { private final Expression leftExpression; @@ -46,5 +44,4 @@ public int interpret() { public String toString() { return "*"; } - } diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java index 49f8c39ef5c3..31a632c3469a 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/NumberExpression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * NumberExpression. - */ +/** NumberExpression. */ public class NumberExpression extends Expression { private final int number; diff --git a/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java b/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java index 2fbea275aa75..4109f397d8ef 100644 --- a/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java +++ b/interpreter/src/main/java/com/iluwatar/interpreter/PlusExpression.java @@ -24,9 +24,7 @@ */ package com.iluwatar.interpreter; -/** - * PlusExpression. - */ +/** PlusExpression. */ public class PlusExpression extends Expression { private final Expression leftExpression; diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java index 56acffee5d81..4c72970c9742 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.interpreter; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java index a84e8a4c79ef..80d76416e43d 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/ExpressionTest.java @@ -54,19 +54,15 @@ static Stream prepareParameters(final IntBinaryOperator resultCalc) { final var testData = new ArrayList(); for (var i = -10; i < 10; i++) { for (var j = -10; j < 10; j++) { - testData.add(Arguments.of( - new NumberExpression(i), - new NumberExpression(j), - resultCalc.applyAsInt(i, j) - )); + testData.add( + Arguments.of( + new NumberExpression(i), new NumberExpression(j), resultCalc.applyAsInt(i, j))); } } return testData.stream(); } - /** - * The expected {@link E#toString()} response - */ + /** The expected {@link E#toString()} response */ private final String expectedToString; /** @@ -78,11 +74,11 @@ static Stream prepareParameters(final IntBinaryOperator resultCalc) { * Create a new test instance with the given parameters and expected results * * @param expectedToString The expected {@link E#toString()} response - * @param factory Factory, used to create a new test object instance + * @param factory Factory, used to create a new test object instance */ - ExpressionTest(final String expectedToString, - final BiFunction factory - ) { + ExpressionTest( + final String expectedToString, + final BiFunction factory) { this.expectedToString = expectedToString; this.factory = factory; } @@ -94,9 +90,7 @@ static Stream prepareParameters(final IntBinaryOperator resultCalc) { */ public abstract Stream expressionProvider(); - /** - * Verify if the expression calculates the correct result when calling {@link E#interpret()} - */ + /** Verify if the expression calculates the correct result when calling {@link E#interpret()} */ @ParameterizedTest @MethodSource("expressionProvider") void testInterpret(NumberExpression first, NumberExpression second, int result) { @@ -105,9 +99,7 @@ void testInterpret(NumberExpression first, NumberExpression second, int result) assertEquals(result, expression.interpret()); } - /** - * Verify if the expression has the expected {@link E#toString()} value - */ + /** Verify if the expression has the expected {@link E#toString()} value */ @ParameterizedTest @MethodSource("expressionProvider") void testToString(NumberExpression first, NumberExpression second) { diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java index 1ae8337d54fe..d773a89707a6 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/MinusExpressionTest.java @@ -27,10 +27,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.provider.Arguments; -/** - * MinusExpressionTest - * - */ +/** MinusExpressionTest */ class MinusExpressionTest extends ExpressionTest { /** @@ -43,11 +40,8 @@ public Stream expressionProvider() { return prepareParameters((f, s) -> f - s); } - /** - * Create a new test instance using the given test parameters and expected result - */ + /** Create a new test instance using the given test parameters and expected result */ public MinusExpressionTest() { super("-", MinusExpression::new); } - -} \ No newline at end of file +} diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java index c04bf705b9ff..6d73341a3f93 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/MultiplyExpressionTest.java @@ -27,10 +27,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.provider.Arguments; -/** - * MultiplyExpressionTest - * - */ +/** MultiplyExpressionTest */ class MultiplyExpressionTest extends ExpressionTest { /** @@ -43,11 +40,8 @@ public Stream expressionProvider() { return prepareParameters((f, s) -> f * s); } - /** - * Create a new test instance using the given test parameters and expected result - */ + /** Create a new test instance using the given test parameters and expected result */ public MultiplyExpressionTest() { super("*", MultiplyExpression::new); } - -} \ No newline at end of file +} diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java index 459cf71e2527..9d917c03abcb 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/NumberExpressionTest.java @@ -31,10 +31,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -/** - * NumberExpressionTest - * - */ +/** NumberExpressionTest */ class NumberExpressionTest extends ExpressionTest { /** @@ -47,9 +44,7 @@ public Stream expressionProvider() { return prepareParameters((f, s) -> f); } - /** - * Create a new test instance using the given test parameters and expected result - */ + /** Create a new test instance using the given test parameters and expected result */ public NumberExpressionTest() { super("number", (f, s) -> f); } @@ -65,5 +60,4 @@ void testFromString(NumberExpression first) { final var numberExpression = new NumberExpression(testStringValue); assertEquals(expectedValue, numberExpression.interpret()); } - -} \ No newline at end of file +} diff --git a/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java b/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java index 3dc04a06cd0f..a93f27c84abd 100644 --- a/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java +++ b/interpreter/src/test/java/com/iluwatar/interpreter/PlusExpressionTest.java @@ -27,10 +27,7 @@ import java.util.stream.Stream; import org.junit.jupiter.params.provider.Arguments; -/** - * PlusExpressionTest - * - */ +/** PlusExpressionTest */ class PlusExpressionTest extends ExpressionTest { /** @@ -43,11 +40,8 @@ public Stream expressionProvider() { return prepareParameters(Integer::sum); } - /** - * Create a new test instance using the given test parameters and expected result - */ + /** Create a new test instance using the given test parameters and expected result */ public PlusExpressionTest() { super("+", PlusExpression::new); } - -} \ No newline at end of file +} diff --git a/iterator/README.md b/iterator/README.md index 49bca61a09f5..bda2afde9e6e 100644 --- a/iterator/README.md +++ b/iterator/README.md @@ -35,6 +35,10 @@ Wikipedia says > In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. +Sequence diagram + +![Iterator sequence diagram](./etc/iterator-sequence-diagram.png) + ## Programmatic Example of Iterator Pattern in Java The main class in our Java Iterator Design Pattern example is the `TreasureChest` that contains items. This demonstrates how to implement and use iterators for efficient collection traversal in Java. diff --git a/iterator/etc/iterator-sequence-diagram.png b/iterator/etc/iterator-sequence-diagram.png new file mode 100644 index 000000000000..ce887054c132 Binary files /dev/null and b/iterator/etc/iterator-sequence-diagram.png differ diff --git a/iterator/pom.xml b/iterator/pom.xml index 4afb9278c09c..4578eed1baa0 100644 --- a/iterator/pom.xml +++ b/iterator/pom.xml @@ -34,6 +34,14 @@ iterator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java b/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java index 8796a7939d09..a027a286265f 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java +++ b/iterator/src/main/java/com/iluwatar/iterator/bst/BstIterator.java @@ -33,7 +33,7 @@ * expect to retrieve TreeNodes according to the Integer's natural ordering (1, 2, 3...) * * @param This Iterator has been implemented with generic typing to allow for TreeNodes of - * different value types + * different value types */ public class BstIterator> implements Iterator> { @@ -83,5 +83,4 @@ public TreeNode next() throws NoSuchElementException { pushPathToNextSmallest(next.getRight()); return next; } - } diff --git a/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java b/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java index 358b2e5a9fe6..a24f98d58bcd 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java +++ b/iterator/src/main/java/com/iluwatar/iterator/bst/TreeNode.java @@ -37,13 +37,9 @@ public class TreeNode> { private final T val; - @Getter - @Setter - private TreeNode left; + @Getter @Setter private TreeNode left; - @Getter - @Setter - private TreeNode right; + @Getter @Setter private TreeNode right; /** * Creates a TreeNode with a given value, and null children. @@ -129,5 +125,4 @@ private boolean isLessThanOrEqualTo(T val) { public String toString() { return val.toString(); } - } diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/Item.java b/iterator/src/main/java/com/iluwatar/iterator/list/Item.java index 776258ecfe79..0188078a1b02 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/Item.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/Item.java @@ -28,15 +28,11 @@ import lombok.Getter; import lombok.Setter; -/** - * Item. - */ +/** Item. */ @AllArgsConstructor public class Item { - @Getter - @Setter - private ItemType type; + @Getter @Setter private ItemType type; private final String name; @Override diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/ItemType.java b/iterator/src/main/java/com/iluwatar/iterator/list/ItemType.java index 1325cd3655bd..2655a7aa091f 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/ItemType.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/ItemType.java @@ -24,11 +24,10 @@ */ package com.iluwatar.iterator.list; -/** - * ItemType enumeration. - */ +/** ItemType enumeration. */ public enum ItemType { - - ANY, WEAPON, RING, POTION - + ANY, + WEAPON, + RING, + POTION } diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java index 2a0472dcb4a9..1eb3905a7fd0 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChest.java @@ -28,39 +28,33 @@ import java.util.ArrayList; import java.util.List; -/** - * TreasureChest, the collection class. - */ +/** TreasureChest, the collection class. */ public class TreasureChest { private final List items; - /** - * Constructor. - */ + /** Constructor. */ public TreasureChest() { - items = List.of( - new Item(ItemType.POTION, "Potion of courage"), - new Item(ItemType.RING, "Ring of shadows"), - new Item(ItemType.POTION, "Potion of wisdom"), - new Item(ItemType.POTION, "Potion of blood"), - new Item(ItemType.WEAPON, "Sword of silver +1"), - new Item(ItemType.POTION, "Potion of rust"), - new Item(ItemType.POTION, "Potion of healing"), - new Item(ItemType.RING, "Ring of armor"), - new Item(ItemType.WEAPON, "Steel halberd"), - new Item(ItemType.WEAPON, "Dagger of poison")); + items = + List.of( + new Item(ItemType.POTION, "Potion of courage"), + new Item(ItemType.RING, "Ring of shadows"), + new Item(ItemType.POTION, "Potion of wisdom"), + new Item(ItemType.POTION, "Potion of blood"), + new Item(ItemType.WEAPON, "Sword of silver +1"), + new Item(ItemType.POTION, "Potion of rust"), + new Item(ItemType.POTION, "Potion of healing"), + new Item(ItemType.RING, "Ring of armor"), + new Item(ItemType.WEAPON, "Steel halberd"), + new Item(ItemType.WEAPON, "Dagger of poison")); } public Iterator iterator(ItemType itemType) { return new TreasureChestItemIterator(this, itemType); } - /** - * Get all items. - */ + /** Get all items. */ public List getItems() { return new ArrayList<>(items); } - } diff --git a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java index 25f6e1434421..51c33f931acb 100644 --- a/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java +++ b/iterator/src/main/java/com/iluwatar/iterator/list/TreasureChestItemIterator.java @@ -26,18 +26,14 @@ import com.iluwatar.iterator.Iterator; -/** - * TreasureChestItemIterator. - */ +/** TreasureChestItemIterator. */ public class TreasureChestItemIterator implements Iterator { private final TreasureChest chest; private int idx; private final ItemType type; - /** - * Constructor. - */ + /** Constructor. */ public TreasureChestItemIterator(TreasureChest chest, ItemType type) { this.chest = chest; this.type = type; diff --git a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java index da2ed3c67034..13766bbc9847 100644 --- a/iterator/src/test/java/com/iluwatar/iterator/AppTest.java +++ b/iterator/src/test/java/com/iluwatar/iterator/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.iterator; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/iterator/src/test/java/com/iluwatar/iterator/bst/BstIteratorTest.java b/iterator/src/test/java/com/iluwatar/iterator/bst/BstIteratorTest.java index 2014aadc8e21..a075ce75f3ea 100644 --- a/iterator/src/test/java/com/iluwatar/iterator/bst/BstIteratorTest.java +++ b/iterator/src/test/java/com/iluwatar/iterator/bst/BstIteratorTest.java @@ -56,7 +56,9 @@ void createTrees() { @Test void nextForEmptyTree() { var iter = new BstIterator<>(emptyRoot); - assertThrows(NoSuchElementException.class, iter::next, + assertThrows( + NoSuchElementException.class, + iter::next, "next() should throw an IllegalStateException if hasNext() is false."); } @@ -100,5 +102,4 @@ void nextAndHasNextOverEntirePopulatedTree() { assertEquals(Integer.valueOf(7), iter.next().getVal(), "Sixth Node is 7."); assertFalse(iter.hasNext(), "Iterator hasNext() should be false, end of tree."); } - } diff --git a/iterator/src/test/java/com/iluwatar/iterator/list/TreasureChestTest.java b/iterator/src/test/java/com/iluwatar/iterator/list/TreasureChestTest.java index c7c54a8cbbf7..3c298180d065 100644 --- a/iterator/src/test/java/com/iluwatar/iterator/list/TreasureChestTest.java +++ b/iterator/src/test/java/com/iluwatar/iterator/list/TreasureChestTest.java @@ -32,10 +32,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * TreasureChestTest - * - */ +/** TreasureChestTest */ class TreasureChestTest { /** @@ -45,17 +42,16 @@ class TreasureChestTest { */ public static List dataProvider() { return List.of( - new Object[]{new Item(ItemType.POTION, "Potion of courage")}, - new Object[]{new Item(ItemType.RING, "Ring of shadows")}, - new Object[]{new Item(ItemType.POTION, "Potion of wisdom")}, - new Object[]{new Item(ItemType.POTION, "Potion of blood")}, - new Object[]{new Item(ItemType.WEAPON, "Sword of silver +1")}, - new Object[]{new Item(ItemType.POTION, "Potion of rust")}, - new Object[]{new Item(ItemType.POTION, "Potion of healing")}, - new Object[]{new Item(ItemType.RING, "Ring of armor")}, - new Object[]{new Item(ItemType.WEAPON, "Steel halberd")}, - new Object[]{new Item(ItemType.WEAPON, "Dagger of poison")} - ); + new Object[] {new Item(ItemType.POTION, "Potion of courage")}, + new Object[] {new Item(ItemType.RING, "Ring of shadows")}, + new Object[] {new Item(ItemType.POTION, "Potion of wisdom")}, + new Object[] {new Item(ItemType.POTION, "Potion of blood")}, + new Object[] {new Item(ItemType.WEAPON, "Sword of silver +1")}, + new Object[] {new Item(ItemType.POTION, "Potion of rust")}, + new Object[] {new Item(ItemType.POTION, "Potion of healing")}, + new Object[] {new Item(ItemType.RING, "Ring of armor")}, + new Object[] {new Item(ItemType.WEAPON, "Steel halberd")}, + new Object[] {new Item(ItemType.WEAPON, "Dagger of poison")}); } /** @@ -82,7 +78,6 @@ void testIterator(Item expectedItem) { } fail("Expected to find item [" + expectedItem + "] using iterator, but we didn't."); - } /** @@ -109,7 +104,5 @@ void testGetItems(Item expectedItem) { } fail("Expected to find item [" + expectedItem + "] in the item list, but we didn't."); - } - -} \ No newline at end of file +} diff --git a/layered-architecture/README.md b/layered-architecture/README.md index 9ab4b8d344b8..9692d6e0e2db 100644 --- a/layered-architecture/README.md +++ b/layered-architecture/README.md @@ -38,6 +38,10 @@ Wikipedia says > In software engineering, multitier architecture (often referred to as n-tier architecture) or multilayered architecture is a client–server architecture in which presentation, application processing, and data management functions are physically separated. +Architecture diagram + +![Layered Architecture Diagram](./etc/layered-architecture-diagram.png) + ## Programmatic Example of Layered Architecture in Java On the data layer, we keep our cake building blocks. `Cake` consist of layers and topping. @@ -86,10 +90,6 @@ public class CakeViewImpl implements View { } ``` -## Layered Architecture Pattern Class Diagram - -![Layered Architecture](./etc/layers.png "Layered Architecture") - ## When to Use the Layered Architecture Pattern in Java This pattern is suitable for structuring applications that can be divided into groups where each group has a specific role or responsibility. Common in enterprise applications, it simplifies dependencies, enhances maintainability, and supports scaling and technology stack segregation. diff --git a/layered-architecture/etc/layered-architecture-diagram.png b/layered-architecture/etc/layered-architecture-diagram.png new file mode 100644 index 000000000000..8bdc13afeb52 Binary files /dev/null and b/layered-architecture/etc/layered-architecture-diagram.png differ diff --git a/layered-architecture/pom.xml b/layered-architecture/pom.xml index 07ca0bdefd11..c91fc733ef64 100644 --- a/layered-architecture/pom.xml +++ b/layered-architecture/pom.xml @@ -38,18 +38,6 @@ layers layers - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.2.4 - import - - - - org.springframework.boot @@ -59,17 +47,11 @@ org.springframework.boot spring-boot-starter-data-jpa - com.h2database h2 runtime - - org.projectlombok - lombok - true - org.springframework.boot spring-boot-starter-test @@ -80,16 +62,19 @@ - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.layers.Runner + + + + + diff --git a/layered-architecture/src/main/java/com/iluwatar/layers/Runner.java b/layered-architecture/src/main/java/com/iluwatar/layers/Runner.java index 5e9d28758a56..4c4fb68377ae 100644 --- a/layered-architecture/src/main/java/com/iluwatar/layers/Runner.java +++ b/layered-architecture/src/main/java/com/iluwatar/layers/Runner.java @@ -35,18 +35,18 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; -import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.stereotype.Component; import service.CakeBakingService; import view.CakeViewImpl; /** - * The Runner class is the entry point of the application. - * It implements CommandLineRunner, which means it will execute the run method after the application context is loaded. + * The Runner class is the entry point of the application. It implements CommandLineRunner, which + * means it will execute the run method after the application context is loaded. * - *

    The Runner class is responsible for initializing the cake baking service with sample data and creating a view to render the cakes. - * It uses the CakeBakingService to save new layers and toppings and to bake new cakes. - * It also handles exceptions that might occur during the cake baking process.

    + *

    The Runner class is responsible for initializing the cake baking service with sample data and + * creating a view to render the cakes. It uses the CakeBakingService to save new layers and + * toppings and to bake new cakes. It also handles exceptions that might occur during the cake + * baking process. */ @EntityScan(basePackages = "entity") @ComponentScan(basePackages = {"com.iluwatar.layers", "service", "dto", "exception", "view", "dao"}) @@ -63,7 +63,7 @@ public Runner(CakeBakingService cakeBakingService) { @Override public void run(String... args) { - //initialize sample data + // initialize sample data initializeData(); // create view and render it var cakeView = new CakeViewImpl(cakeBakingService); @@ -74,9 +74,7 @@ public static void main(String[] args) { SpringApplication.run(Runner.class, args); } - /** - * Initializes the example data. - */ + /** Initializes the example data. */ private void initializeData() { cakeBakingService.saveNewLayer(new CakeLayerInfo("chocolate", 1200)); cakeBakingService.saveNewLayer(new CakeLayerInfo("banana", 900)); @@ -88,17 +86,25 @@ private void initializeData() { cakeBakingService.saveNewTopping(new CakeToppingInfo("candies", 350)); cakeBakingService.saveNewTopping(new CakeToppingInfo("cherry", 350)); - var cake1 = new CakeInfo(new CakeToppingInfo("candies", 0), - List.of(new CakeLayerInfo("chocolate", 0), new CakeLayerInfo("banana", 0), - new CakeLayerInfo(STRAWBERRY, 0))); + var cake1 = + new CakeInfo( + new CakeToppingInfo("candies", 0), + List.of( + new CakeLayerInfo("chocolate", 0), + new CakeLayerInfo("banana", 0), + new CakeLayerInfo(STRAWBERRY, 0))); try { cakeBakingService.bakeNewCake(cake1); } catch (CakeBakingException e) { LOGGER.error("Cake baking exception", e); } - var cake2 = new CakeInfo(new CakeToppingInfo("cherry", 0), - List.of(new CakeLayerInfo("vanilla", 0), new CakeLayerInfo("lemon", 0), - new CakeLayerInfo(STRAWBERRY, 0))); + var cake2 = + new CakeInfo( + new CakeToppingInfo("cherry", 0), + List.of( + new CakeLayerInfo("vanilla", 0), + new CakeLayerInfo("lemon", 0), + new CakeLayerInfo(STRAWBERRY, 0))); try { cakeBakingService.bakeNewCake(cake2); } catch (CakeBakingException e) { diff --git a/layered-architecture/src/main/java/com/iluwatar/layers/app/LayersApp.java b/layered-architecture/src/main/java/com/iluwatar/layers/app/LayersApp.java index 58828b6eadaa..2870366e00bd 100644 --- a/layered-architecture/src/main/java/com/iluwatar/layers/app/LayersApp.java +++ b/layered-architecture/src/main/java/com/iluwatar/layers/app/LayersApp.java @@ -33,8 +33,8 @@ /** * The Layers pattern is a structural design pattern that organizes system architecture into * distinct layers, each with a specific responsibility and abstraction level. This separation - * allows for increased modularity, facilitating independent development, maintenance, and reuse - * of each layer. Commonly, layers interact with each other through well-defined interfaces, with + * allows for increased modularity, facilitating independent development, maintenance, and reuse of + * each layer. Commonly, layers interact with each other through well-defined interfaces, with * higher layers (more abstract) depending on lower layers (more concrete), but not vice versa, * promoting a clear hierarchy and separation of concerns. */ @@ -46,7 +46,5 @@ public class LayersApp { public static void main(String[] args) { SpringApplication.run(LayersApp.class, args); - } - } diff --git a/layered-architecture/src/main/java/dao/CakeDao.java b/layered-architecture/src/main/java/dao/CakeDao.java index 06d74bace26c..aae92a175bb0 100644 --- a/layered-architecture/src/main/java/dao/CakeDao.java +++ b/layered-architecture/src/main/java/dao/CakeDao.java @@ -28,8 +28,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -/** - * CRUD repository for cakes. - */ +/** CRUD repository for cakes. */ @Repository public interface CakeDao extends JpaRepository {} diff --git a/layered-architecture/src/main/java/dao/CakeLayerDao.java b/layered-architecture/src/main/java/dao/CakeLayerDao.java index 90a523f16892..0e4415ce4c42 100644 --- a/layered-architecture/src/main/java/dao/CakeLayerDao.java +++ b/layered-architecture/src/main/java/dao/CakeLayerDao.java @@ -28,10 +28,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -/** - * CRUD repository for cake layers. - */ +/** CRUD repository for cake layers. */ @Repository -public interface CakeLayerDao extends JpaRepository { - -} +public interface CakeLayerDao extends JpaRepository {} diff --git a/layered-architecture/src/main/java/dao/CakeToppingDao.java b/layered-architecture/src/main/java/dao/CakeToppingDao.java index 0d0c8836dcaa..bc7b4a56f723 100644 --- a/layered-architecture/src/main/java/dao/CakeToppingDao.java +++ b/layered-architecture/src/main/java/dao/CakeToppingDao.java @@ -24,16 +24,10 @@ */ package dao; - - import entity.CakeTopping; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -/** - * CRUD repository cake toppings. - */ +/** CRUD repository cake toppings. */ @Repository -public interface CakeToppingDao extends JpaRepository { - -} +public interface CakeToppingDao extends JpaRepository {} diff --git a/layered-architecture/src/main/java/dto/CakeInfo.java b/layered-architecture/src/main/java/dto/CakeInfo.java index 0bda58239dd8..475c994481ab 100644 --- a/layered-architecture/src/main/java/dto/CakeInfo.java +++ b/layered-architecture/src/main/java/dto/CakeInfo.java @@ -27,36 +27,28 @@ import java.util.List; -/** - * DTO for cakes. - */ +/** DTO for cakes. */ public class CakeInfo { public final Long id; public final CakeToppingInfo cakeToppingInfo; public final List cakeLayerInfos; - /** - * Constructor. - */ + /** Constructor. */ public CakeInfo(Long id, CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { this.id = id; this.cakeToppingInfo = cakeToppingInfo; this.cakeLayerInfos = cakeLayerInfos; } - /** - * Constructor. - */ + /** Constructor. */ public CakeInfo(CakeToppingInfo cakeToppingInfo, List cakeLayerInfos) { this.id = null; this.cakeToppingInfo = cakeToppingInfo; this.cakeLayerInfos = cakeLayerInfos; } - /** - * Calculate calories. - */ + /** Calculate calories. */ public int calculateTotalCalories() { var total = cakeToppingInfo != null ? cakeToppingInfo.calories : 0; total += cakeLayerInfos.stream().mapToInt(c -> c.calories).sum(); @@ -65,7 +57,8 @@ public int calculateTotalCalories() { @Override public String toString() { - return String.format("CakeInfo id=%d topping=%s layers=%s totalCalories=%d", id, - cakeToppingInfo, cakeLayerInfos, calculateTotalCalories()); + return String.format( + "CakeInfo id=%d topping=%s layers=%s totalCalories=%d", + id, cakeToppingInfo, cakeLayerInfos, calculateTotalCalories()); } } diff --git a/layered-architecture/src/main/java/dto/CakeLayerInfo.java b/layered-architecture/src/main/java/dto/CakeLayerInfo.java index 6689b87461c2..e7e464e2dc13 100644 --- a/layered-architecture/src/main/java/dto/CakeLayerInfo.java +++ b/layered-architecture/src/main/java/dto/CakeLayerInfo.java @@ -25,27 +25,21 @@ package dto; -/** - * DTO for cake layers. - */ +/** DTO for cake layers. */ public class CakeLayerInfo { public final Long id; public final String name; public final int calories; - /** - * Constructor. - */ + /** Constructor. */ public CakeLayerInfo(Long id, String name, int calories) { this.id = id; this.name = name; this.calories = calories; } - /** - * Constructor. - */ + /** Constructor. */ public CakeLayerInfo(String name, int calories) { this.id = null; this.name = name; diff --git a/layered-architecture/src/main/java/dto/CakeToppingInfo.java b/layered-architecture/src/main/java/dto/CakeToppingInfo.java index a3657208668e..9c48d869ce65 100644 --- a/layered-architecture/src/main/java/dto/CakeToppingInfo.java +++ b/layered-architecture/src/main/java/dto/CakeToppingInfo.java @@ -25,27 +25,21 @@ package dto; -/** - * DTO for cake toppings. - */ +/** DTO for cake toppings. */ public class CakeToppingInfo { public final Long id; public final String name; public final int calories; - /** - * Constructor. - */ + /** Constructor. */ public CakeToppingInfo(Long id, String name, int calories) { this.id = id; this.name = name; this.calories = calories; } - /** - * Constructor. - */ + /** Constructor. */ public CakeToppingInfo(String name, int calories) { this.id = null; this.name = name; @@ -54,7 +48,6 @@ public CakeToppingInfo(String name, int calories) { @Override public String toString() { - return String.format("CakeToppingInfo id=%d name=%s calories=%d", id, name, - calories); + return String.format("CakeToppingInfo id=%d name=%s calories=%d", id, name, calories); } } diff --git a/layered-architecture/src/main/java/entity/Cake.java b/layered-architecture/src/main/java/entity/Cake.java index 92dcc5ecff26..58a8af659a60 100644 --- a/layered-architecture/src/main/java/entity/Cake.java +++ b/layered-architecture/src/main/java/entity/Cake.java @@ -37,17 +37,13 @@ import lombok.Getter; import lombok.Setter; -/** - * Cake entity. - */ +/** Cake entity. */ @Entity @Getter @Setter public class Cake { - @Id - @GeneratedValue - private Long id; + @Id @GeneratedValue private Long id; @OneToOne(cascade = CascadeType.REMOVE) private CakeTopping topping; diff --git a/layered-architecture/src/main/java/entity/CakeLayer.java b/layered-architecture/src/main/java/entity/CakeLayer.java index ad517317c79e..798ecbd17664 100644 --- a/layered-architecture/src/main/java/entity/CakeLayer.java +++ b/layered-architecture/src/main/java/entity/CakeLayer.java @@ -25,7 +25,6 @@ package entity; - import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -38,9 +37,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; -/** - * CakeLayer entity. - */ +/** CakeLayer entity. */ @Entity @Getter @Setter @@ -50,9 +47,7 @@ @EqualsAndHashCode public class CakeLayer { - @Id - @GeneratedValue - private Long id; + @Id @GeneratedValue private Long id; private String name; diff --git a/layered-architecture/src/main/java/entity/CakeTopping.java b/layered-architecture/src/main/java/entity/CakeTopping.java index 997dc6ddb311..8b1c5f38be30 100644 --- a/layered-architecture/src/main/java/entity/CakeTopping.java +++ b/layered-architecture/src/main/java/entity/CakeTopping.java @@ -25,7 +25,6 @@ package entity; - import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -38,9 +37,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; -/** - * CakeTopping entity. - */ +/** CakeTopping entity. */ @Entity @Getter @Setter @@ -50,9 +47,7 @@ @EqualsAndHashCode public class CakeTopping { - @Id - @GeneratedValue - private Long id; + @Id @GeneratedValue private Long id; private String name; @@ -70,5 +65,4 @@ public CakeTopping(String name, int calories) { public String toString() { return String.format("id=%s name=%s calories=%d", id, name, calories); } - } diff --git a/layered-architecture/src/main/java/exception/CakeBakingException.java b/layered-architecture/src/main/java/exception/CakeBakingException.java index 0af1e28cd19e..13af2b550261 100644 --- a/layered-architecture/src/main/java/exception/CakeBakingException.java +++ b/layered-architecture/src/main/java/exception/CakeBakingException.java @@ -28,17 +28,13 @@ import java.io.Serial; import org.springframework.stereotype.Component; -/** - * Custom exception used in cake baking. - */ +/** Custom exception used in cake baking. */ @Component public class CakeBakingException extends Exception { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; - public CakeBakingException() { - } + public CakeBakingException() {} public CakeBakingException(String message) { super(message); diff --git a/layered-architecture/src/main/java/service/CakeBakingService.java b/layered-architecture/src/main/java/service/CakeBakingService.java index 8ab5dd3a82b1..be34797d66c4 100644 --- a/layered-architecture/src/main/java/service/CakeBakingService.java +++ b/layered-architecture/src/main/java/service/CakeBakingService.java @@ -32,40 +32,26 @@ import java.util.List; import org.springframework.stereotype.Service; -/** - * Service for cake baking operations. - */ +/** Service for cake baking operations. */ @Service public interface CakeBakingService { - /** - * Bakes new cake according to parameters. - */ + /** Bakes new cake according to parameters. */ void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException; - /** - * Get all cakes. - */ + /** Get all cakes. */ List getAllCakes(); - /** - * Store new cake topping. - */ + /** Store new cake topping. */ void saveNewTopping(CakeToppingInfo toppingInfo); - /** - * Get available cake toppings. - */ + /** Get available cake toppings. */ List getAvailableToppings(); - /** - * Add new cake layer. - */ + /** Add new cake layer. */ void saveNewLayer(CakeLayerInfo layerInfo); - /** - * Get available cake layers. - */ + /** Get available cake layers. */ List getAvailableLayers(); void deleteAllCakes(); @@ -73,5 +59,4 @@ public interface CakeBakingService { void deleteAllLayers(); void deleteAllToppings(); - } diff --git a/layered-architecture/src/main/java/service/CakeBakingServiceImpl.java b/layered-architecture/src/main/java/service/CakeBakingServiceImpl.java index 2161c3ef7da7..6af2123d9d68 100644 --- a/layered-architecture/src/main/java/service/CakeBakingServiceImpl.java +++ b/layered-architecture/src/main/java/service/CakeBakingServiceImpl.java @@ -43,9 +43,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -/** - * Implementation of CakeBakingService. - */ +/** Implementation of CakeBakingService. */ @Service @Transactional public class CakeBakingServiceImpl implements CakeBakingService { @@ -62,8 +60,8 @@ public class CakeBakingServiceImpl implements CakeBakingService { * @param cakeToppingDao the DAO for cake topping-related operations */ @Autowired - public CakeBakingServiceImpl(CakeDao cakeDao, CakeLayerDao cakeLayerDao, - CakeToppingDao cakeToppingDao) { + public CakeBakingServiceImpl( + CakeDao cakeDao, CakeLayerDao cakeLayerDao, CakeToppingDao cakeToppingDao) { this.cakeDao = cakeDao; this.cakeLayerDao = cakeLayerDao; this.cakeToppingDao = cakeToppingDao; @@ -73,7 +71,8 @@ public CakeBakingServiceImpl(CakeDao cakeDao, CakeLayerDao cakeLayerDao, public void bakeNewCake(CakeInfo cakeInfo) throws CakeBakingException { var allToppings = getAvailableToppingEntities(); var matchingToppings = - allToppings.stream().filter(t -> t.getName().equals(cakeInfo.cakeToppingInfo.name)) + allToppings.stream() + .filter(t -> t.getName().equals(cakeInfo.cakeToppingInfo.name)) .toList(); if (matchingToppings.isEmpty()) { throw new CakeBakingException( @@ -184,7 +183,9 @@ public List getAllCakes() { List result = new ArrayList<>(); for (Cake cake : cakeDao.findAll()) { var cakeToppingInfo = - new CakeToppingInfo(cake.getTopping().getId(), cake.getTopping().getName(), + new CakeToppingInfo( + cake.getTopping().getId(), + cake.getTopping().getName(), cake.getTopping().getCalories()); List cakeLayerInfos = new ArrayList<>(); for (var layer : cake.getLayers()) { diff --git a/layered-architecture/src/main/java/view/CakeViewImpl.java b/layered-architecture/src/main/java/view/CakeViewImpl.java index edb9ebffe04a..a01d1c1600a0 100644 --- a/layered-architecture/src/main/java/view/CakeViewImpl.java +++ b/layered-architecture/src/main/java/view/CakeViewImpl.java @@ -29,9 +29,7 @@ import org.slf4j.LoggerFactory; import service.CakeBakingService; -/** - * View implementation for displaying cakes. - */ +/** View implementation for displaying cakes. */ public class CakeViewImpl implements View { private final CakeBakingService cakeBakingService; diff --git a/layered-architecture/src/main/java/view/View.java b/layered-architecture/src/main/java/view/View.java index f8ff8b53579b..78403c6013a5 100644 --- a/layered-architecture/src/main/java/view/View.java +++ b/layered-architecture/src/main/java/view/View.java @@ -25,11 +25,8 @@ package view; -/** - * View interface. - */ +/** View interface. */ public interface View { void render(); - } diff --git a/layered-architecture/src/test/java/com/iluwatar/layers/app/LayersAppTests.java b/layered-architecture/src/test/java/com/iluwatar/layers/app/LayersAppTests.java index da361e3f0c73..e51bca34d12f 100644 --- a/layered-architecture/src/test/java/com/iluwatar/layers/app/LayersAppTests.java +++ b/layered-architecture/src/test/java/com/iluwatar/layers/app/LayersAppTests.java @@ -45,5 +45,4 @@ class LayersAppTests { void contextLoads() { assertNotNull(applicationContext); } - } diff --git a/layered-architecture/src/test/java/com/iluwatar/layers/entity/CakeTest.java b/layered-architecture/src/test/java/com/iluwatar/layers/entity/CakeTest.java index a59986c6cf2e..eea31e9355ac 100644 --- a/layered-architecture/src/test/java/com/iluwatar/layers/entity/CakeTest.java +++ b/layered-architecture/src/test/java/com/iluwatar/layers/entity/CakeTest.java @@ -38,9 +38,9 @@ import org.junit.jupiter.api.Test; /** - * This class contains unit tests for the Cake class. - * It tests the functionality of setting and getting the id, topping, and layers of a Cake object. - * It also tests the functionality of adding a layer to a Cake object and converting a Cake object to a string. + * This class contains unit tests for the Cake class. It tests the functionality of setting and + * getting the id, topping, and layers of a Cake object. It also tests the functionality of adding a + * layer to a Cake object and converting a Cake object to a string. */ class CakeTest { @@ -70,8 +70,11 @@ void testSetLayers() { assertNotNull(cake.getLayers()); assertTrue(cake.getLayers().isEmpty()); - final var expectedLayers = Set.of(new CakeLayer("layer1", 1000), new CakeLayer("layer2", 2000), - new CakeLayer("layer3", 3000)); + final var expectedLayers = + Set.of( + new CakeLayer("layer1", 1000), + new CakeLayer("layer2", 2000), + new CakeLayer("layer3", 3000)); cake.setLayers(expectedLayers); assertEquals(expectedLayers, cake.getLayers()); } @@ -112,10 +115,9 @@ void testToString() { cake.setTopping(topping); cake.addLayer(layer); - final var expected = "id=1234 topping=id=2345 name=topping calories=20 " - + "layers=[id=3456 name=layer calories=100]"; + final var expected = + "id=1234 topping=id=2345 name=topping calories=20 " + + "layers=[id=3456 name=layer calories=100]"; assertEquals(expected, cake.toString()); - } - } diff --git a/layered-architecture/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java b/layered-architecture/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java index 5939abe74d8d..9acb49d2a289 100644 --- a/layered-architecture/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java +++ b/layered-architecture/src/test/java/com/iluwatar/layers/exception/CakeBakingExceptionTest.java @@ -32,17 +32,15 @@ import org.junit.jupiter.api.Test; /** - * Tests for the {@link CakeBakingException} class. - * This class contains unit tests to verify the correct functionality - * of the {@code CakeBakingException} class constructors, including the default constructor - * and the constructor that accepts a message parameter. + * Tests for the {@link CakeBakingException} class. This class contains unit tests to verify the + * correct functionality of the {@code CakeBakingException} class constructors, including the + * default constructor and the constructor that accepts a message parameter. */ class CakeBakingExceptionTest { /** - * Tests the default constructor of {@link CakeBakingException}. - * Ensures that an exception created with the default constructor has - * {@code null} as its message and cause. + * Tests the default constructor of {@link CakeBakingException}. Ensures that an exception created + * with the default constructor has {@code null} as its message and cause. */ @Test void testConstructor() { @@ -52,18 +50,20 @@ void testConstructor() { } /** - * Tests the constructor of {@link CakeBakingException} that accepts a message. - * Ensures that an exception created with this constructor correctly stores the provided message - * and has {@code null} as its cause. + * Tests the constructor of {@link CakeBakingException} that accepts a message. Ensures that an + * exception created with this constructor correctly stores the provided message and has {@code + * null} as its cause. */ @Test void testConstructorWithMessage() { final var expectedMessage = "message"; final var exception = new CakeBakingException(expectedMessage); - assertEquals(expectedMessage, exception.getMessage(), + assertEquals( + expectedMessage, + exception.getMessage(), "The stored message should match the expected message."); - assertNull(exception.getCause(), + assertNull( + exception.getCause(), "The cause should be null when an exception is created with only a message."); } - } diff --git a/layered-architecture/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java b/layered-architecture/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java index cd0edb48cb3e..a14c0076073a 100644 --- a/layered-architecture/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java +++ b/layered-architecture/src/test/java/com/iluwatar/layers/service/CakeBakingServiceImplTest.java @@ -44,10 +44,7 @@ import org.springframework.boot.test.context.SpringBootTest; import service.CakeBakingServiceImpl; -/** - * Constructs a new instance of CakeBakingServiceImplTest. - * - */ +/** Constructs a new instance of CakeBakingServiceImplTest. */ @SpringBootTest(classes = LayersApp.class) class CakeBakingServiceImplTest { @@ -65,7 +62,6 @@ void setUp() { cakeBakingService.deleteAllToppings(); } - @Test void testLayers() { final var initialLayers = cakeBakingService.getAvailableLayers(); @@ -84,7 +80,6 @@ void testLayers() { assertNotNull(layer.toString()); assertTrue(layer.calories > 0); } - } @Test @@ -105,7 +100,6 @@ void testToppings() { assertNotNull(topping.toString()); assertTrue(topping.calories > 0); } - } @Test @@ -141,7 +135,6 @@ void testBakeCakes() throws CakeBakingException { assertFalse(cakeInfo.cakeLayerInfos.isEmpty()); assertTrue(cakeInfo.calculateTotalCalories() > 0); } - } @Test @@ -152,7 +145,8 @@ void testBakeCakeMissingTopping() { cakeBakingService.saveNewLayer(layer2); final var missingTopping = new CakeToppingInfo("Topping1", 1000); - assertThrows(CakeBakingException.class, + assertThrows( + CakeBakingException.class, () -> cakeBakingService.bakeNewCake(new CakeInfo(missingTopping, List.of(layer1, layer2)))); } @@ -169,7 +163,8 @@ void testBakeCakeMissingLayer() { cakeBakingService.saveNewLayer(layer1); final var missingLayer = new CakeLayerInfo("Layer2", 2000); - assertThrows(CakeBakingException.class, + assertThrows( + CakeBakingException.class, () -> cakeBakingService.bakeNewCake(new CakeInfo(topping1, List.of(layer1, missingLayer)))); } @@ -190,8 +185,10 @@ void testBakeCakesUsedLayer() throws CakeBakingException { cakeBakingService.saveNewLayer(layer2); cakeBakingService.bakeNewCake(new CakeInfo(topping1, List.of(layer1, layer2))); - assertThrows(CakeBakingException.class, () -> cakeBakingService.bakeNewCake( - new CakeInfo(topping2, Collections.singletonList(layer2)))); + assertThrows( + CakeBakingException.class, + () -> + cakeBakingService.bakeNewCake( + new CakeInfo(topping2, Collections.singletonList(layer2)))); } - } diff --git a/layered-architecture/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java b/layered-architecture/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java index 1d7546a598db..2e3b1ae598b8 100644 --- a/layered-architecture/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java +++ b/layered-architecture/src/test/java/com/iluwatar/layers/view/CakeViewImplTest.java @@ -45,9 +45,9 @@ import view.CakeViewImpl; /** - * This class contains unit tests for the CakeViewImpl class. - * It tests the functionality of rendering cakes using the CakeViewImpl class. - * It also tests the logging functionality of the CakeViewImpl class. + * This class contains unit tests for the CakeViewImpl class. It tests the functionality of + * rendering cakes using the CakeViewImpl class. It also tests the logging functionality of the + * CakeViewImpl class. */ class CakeViewImplTest { @@ -63,14 +63,15 @@ void tearDown() { appender.stop(); } - /** - * Verify if the cake view renders the expected result. - */ + /** Verify if the cake view renders the expected result. */ @Test void testRender() { - final var layers = List.of(new CakeLayerInfo("layer1", 1000), new CakeLayerInfo("layer2", 2000), - new CakeLayerInfo("layer3", 3000)); + final var layers = + List.of( + new CakeLayerInfo("layer1", 1000), + new CakeLayerInfo("layer2", 2000), + new CakeLayerInfo("layer3", 3000)); final var cake = new CakeInfo(new CakeToppingInfo("topping", 1000), layers); final var cakes = List.of(cake); @@ -84,7 +85,6 @@ void testRender() { cakeView.render(); assertEquals(cake.toString(), appender.getLastMessage()); - } private static class InMemoryAppender extends AppenderBase { @@ -109,5 +109,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/lazy-loading/README.md b/lazy-loading/README.md index ea761250c929..2d448da2f034 100644 --- a/lazy-loading/README.md +++ b/lazy-loading/README.md @@ -35,6 +35,10 @@ Wikipedia says > Lazy loading (also known as asynchronous loading) is a technique used in computer programming, especially web design and web development, to defer initialization of an object until it is needed. It can contribute to efficiency in the program's operation if properly and appropriately used. This makes it ideal in use cases where network content is accessed and initialization times are to be kept at a minimum, such as in the case of web pages. For example, deferring loading of images on a web page until they are needed for viewing can make the initial display of the web page faster. The opposite of lazy loading is eager loading. +Sequence diagram + +![Lazy Loading Sequence Diagram](./etc/lazy-loading-sequence-diagram.png) + ## Programmatic Example of Lazy Loading Pattern in Java The Lazy Loading design pattern is a performance optimization technique that delays the initialization of an object or a costly computation until it's absolutely necessary. This pattern can significantly improve the performance of your application by avoiding unnecessary computation and reducing memory usage. diff --git a/lazy-loading/etc/lazy-loading-sequence-diagram.png b/lazy-loading/etc/lazy-loading-sequence-diagram.png new file mode 100644 index 000000000000..30dde481c691 Binary files /dev/null and b/lazy-loading/etc/lazy-loading-sequence-diagram.png differ diff --git a/lazy-loading/pom.xml b/lazy-loading/pom.xml index 8d777fc768f6..9b2061c2cc6a 100644 --- a/lazy-loading/pom.xml +++ b/lazy-loading/pom.xml @@ -34,6 +34,14 @@ lazy-loading + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java index 31f90b68f32a..0c96768b67a1 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/Heavy.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * Heavy objects are expensive to create. - */ +/** Heavy objects are expensive to create. */ @Slf4j public class Heavy { - /** - * Constructor. - */ + /** Constructor. */ public Heavy() { LOGGER.info("Creating Heavy ..."); try { diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java index 054ab6bccd7a..55281a7e666a 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderNaive.java @@ -26,24 +26,18 @@ import lombok.extern.slf4j.Slf4j; -/** - * Simple implementation of the lazy loading idiom. However, this is not thread safe. - */ +/** Simple implementation of the lazy loading idiom. However, this is not thread safe. */ @Slf4j public class HolderNaive { private Heavy heavy; - /** - * Constructor. - */ + /** Constructor. */ public HolderNaive() { LOGGER.info("HolderNaive created"); } - /** - * Get heavy object. - */ + /** Get heavy object. */ public Heavy getHeavy() { if (heavy == null) { heavy = new Heavy(); diff --git a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java index cb0f7a2a8b1d..f698a05791da 100644 --- a/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java +++ b/lazy-loading/src/main/java/com/iluwatar/lazy/loading/HolderThreadSafe.java @@ -35,16 +35,12 @@ public class HolderThreadSafe { private Heavy heavy; - /** - * Constructor. - */ + /** Constructor. */ public HolderThreadSafe() { LOGGER.info("HolderThreadSafe created"); } - /** - * Get heavy object. - */ + /** Get heavy object. */ public synchronized Heavy getHeavy() { if (heavy == null) { heavy = new Heavy(); diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java index 5c15a529f40e..72cdd9e478d5 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AbstractHolderTest.java @@ -32,10 +32,7 @@ import org.junit.jupiter.api.Test; -/** - * AbstractHolderTest - * - */ +/** AbstractHolderTest */ public abstract class AbstractHolderTest { /** @@ -57,12 +54,13 @@ public abstract class AbstractHolderTest { */ @Test void testGetHeavy() { - assertTimeout(ofMillis(3000), () -> { - assertNull(getInternalHeavyValue()); - assertNotNull(getHeavy()); - assertNotNull(getInternalHeavyValue()); - assertSame(getHeavy(), getInternalHeavyValue()); - }); + assertTimeout( + ofMillis(3000), + () -> { + assertNull(getInternalHeavyValue()); + assertNotNull(getHeavy()); + assertNotNull(getInternalHeavyValue()); + assertSame(getHeavy(), getInternalHeavyValue()); + }); } - } diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java index 3b45c27c0b34..3f3e5b28a063 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/AppTest.java @@ -24,19 +24,15 @@ */ package com.iluwatar.lazy.loading; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java index 52e79a3a1343..e165c4a9b6a6 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderNaiveTest.java @@ -24,10 +24,7 @@ */ package com.iluwatar.lazy.loading; -/** - * HolderNaiveTest - * - */ +/** HolderNaiveTest */ class HolderNaiveTest extends AbstractHolderTest { private final HolderNaive holder = new HolderNaive(); @@ -43,5 +40,4 @@ Heavy getInternalHeavyValue() throws Exception { Heavy getHeavy() { return holder.getHeavy(); } - -} \ No newline at end of file +} diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java index a879c935225f..e6c8efb4b370 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/HolderThreadSafeTest.java @@ -24,10 +24,7 @@ */ package com.iluwatar.lazy.loading; -/** - * HolderThreadSafeTest - * - */ +/** HolderThreadSafeTest */ class HolderThreadSafeTest extends AbstractHolderTest { private final HolderThreadSafe holder = new HolderThreadSafe(); @@ -43,5 +40,4 @@ Heavy getInternalHeavyValue() throws Exception { Heavy getHeavy() { return this.holder.getHeavy(); } - -} \ No newline at end of file +} diff --git a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java index a702cacca97d..41c1d4886fe2 100644 --- a/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java +++ b/lazy-loading/src/test/java/com/iluwatar/lazy/loading/Java8HolderTest.java @@ -26,15 +26,11 @@ import java.util.function.Supplier; -/** - * Java8HolderTest - * - */ +/** Java8HolderTest */ class Java8HolderTest extends AbstractHolderTest { private final Java8Holder holder = new Java8Holder(); - @Override Heavy getInternalHeavyValue() throws Exception { final var holderField = Java8Holder.class.getDeclaredField("heavy"); @@ -58,5 +54,4 @@ Heavy getInternalHeavyValue() throws Exception { Heavy getHeavy() { return holder.getHeavy(); } - -} \ No newline at end of file +} diff --git a/leader-election/README.md b/leader-election/README.md index 282ea9bac874..12c898e3cbc2 100644 --- a/leader-election/README.md +++ b/leader-election/README.md @@ -35,6 +35,10 @@ Wikipedia says > In distributed computing, leader election is the process of designating a single process as the organizer of some task distributed among several computers (nodes). Before the task has begun, all network nodes are either unaware which node will serve as the "leader" (or coordinator) of the task, or unable to communicate with the current coordinator. After a leader election algorithm has been run, however, each node throughout the network recognizes a particular, unique node as the task leader. +Sequence diagram + +![Leader Election Sequence Diagram](./etc/leader-election-sequence-diagram.png) + ## Programmatic Example of Leader Election Pattern in Java The Leader Election pattern is a design approach that enables a distributed system to select one node as the coordinator or leader to manage tasks and maintain order, while other nodes operate as followers. This pattern is particularly useful in distributed systems where one node needs to act as a central coordinator for a specific function or decision-making process. @@ -137,10 +141,6 @@ The `RingApp` class implements the Ring algorithm for leader election. In this a These examples demonstrate how the Leader Election pattern can be implemented in different ways to suit the specific requirements of a distributed system. -## Detailed Explanation of Leader Election Pattern with Real-World Examples - -![Leader Election](./etc/leader-election.urm.png "Leader Election pattern class diagram") - ## When to Use the Leader Election Pattern in Java Use the Leader Election pattern in Java applications where: diff --git a/leader-election/etc/leader-election-sequence-diagram.png b/leader-election/etc/leader-election-sequence-diagram.png new file mode 100644 index 000000000000..ab5243ee206a Binary files /dev/null and b/leader-election/etc/leader-election-sequence-diagram.png differ diff --git a/leader-election/pom.xml b/leader-election/pom.xml index 8cef075485cc..fbe1edbea0c6 100644 --- a/leader-election/pom.xml +++ b/leader-election/pom.xml @@ -34,6 +34,14 @@ leader-election + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java index 531c9869e3a9..398d0baf306c 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractInstance.java @@ -28,9 +28,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import lombok.extern.slf4j.Slf4j; -/** - * Abstract class of all the instance implementation classes. - */ +/** Abstract class of all the instance implementation classes. */ @Slf4j public abstract class AbstractInstance implements Instance, Runnable { @@ -43,9 +41,7 @@ public abstract class AbstractInstance implements Instance, Runnable { protected int leaderId; protected boolean alive; - /** - * Constructor of BullyInstance. - */ + /** Constructor of BullyInstance. */ public AbstractInstance(MessageManager messageManager, int localId, int leaderId) { this.messageManager = messageManager; this.messageQueue = new ConcurrentLinkedQueue<>(); @@ -54,9 +50,7 @@ public AbstractInstance(MessageManager messageManager, int localId, int leaderId this.alive = true; } - /** - * The instance will execute the message in its message queue periodically once it is alive. - */ + /** The instance will execute the message in its message queue periodically once it is alive. */ @Override @SuppressWarnings("squid:S2189") public void run() { @@ -129,8 +123,7 @@ private void processMessage(Message message) { LOGGER.info(INSTANCE + localId + " - Heartbeat Invoke Message handling..."); handleHeartbeatInvokeMessage(); } - default -> { - } + default -> {} } } @@ -149,5 +142,4 @@ private void processMessage(Message message) { protected abstract void handleHeartbeatMessage(Message message); protected abstract void handleHeartbeatInvokeMessage(); - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java index 934f2f83b716..d613182e5aa9 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/AbstractMessageManager.java @@ -26,19 +26,13 @@ import java.util.Map; -/** - * Abstract class of all the message manager classes. - */ +/** Abstract class of all the message manager classes. */ public abstract class AbstractMessageManager implements MessageManager { - /** - * Contain all the instances in the system. Key is its ID, and value is the instance itself. - */ + /** Contain all the instances in the system. Key is its ID, and value is the instance itself. */ protected Map instanceMap; - /** - * Constructor of AbstractMessageManager. - */ + /** Constructor of AbstractMessageManager. */ public AbstractMessageManager(Map instanceMap) { this.instanceMap = instanceMap; } @@ -50,18 +44,18 @@ public AbstractMessageManager(Map instanceMap) { */ protected Instance findNextInstance(int currentId) { Instance result = null; - var candidateList = instanceMap.keySet() - .stream() - .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) - .sorted() - .toList(); + var candidateList = + instanceMap.keySet().stream() + .filter((i) -> i > currentId && instanceMap.get(i).isAlive()) + .sorted() + .toList(); if (candidateList.isEmpty()) { - var index = instanceMap.keySet() - .stream() - .filter((i) -> instanceMap.get(i).isAlive()) - .sorted() - .toList() - .get(0); + var index = + instanceMap.keySet().stream() + .filter((i) -> instanceMap.get(i).isAlive()) + .sorted() + .toList() + .get(0); result = instanceMap.get(index); } else { var index = candidateList.get(0); @@ -69,5 +63,4 @@ protected Instance findNextInstance(int currentId) { } return result; } - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java index 08f76dfea599..a2a16f0f62f9 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Instance.java @@ -24,9 +24,7 @@ */ package com.iluwatar.leaderelection; -/** - * Instance interface. - */ +/** Instance interface. */ public interface Instance { /** @@ -49,5 +47,4 @@ public interface Instance { * @param message Message sent by other instances */ void onMessage(Message message); - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java index 6984481a68fe..af3788e954fe 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/Message.java @@ -30,9 +30,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; -/** - * Message used to transport data between instances. - */ +/** Message used to transport data between instances. */ @Setter @Getter @EqualsAndHashCode @@ -42,5 +40,4 @@ public class Message { private MessageType type; private String content; - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java index 58006dc00a24..96a030421c65 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageManager.java @@ -24,9 +24,7 @@ */ package com.iluwatar.leaderelection; -/** - * MessageManager interface. - */ +/** MessageManager interface. */ public interface MessageManager { /** @@ -41,7 +39,7 @@ public interface MessageManager { * Send election message to other instances. * * @param currentId Instance ID of which sends this message. - * @param content Election message content. + * @param content Election message content. * @return {@code true} if the message is accepted by the target instances. */ boolean sendElectionMessage(int currentId, String content); @@ -50,7 +48,7 @@ public interface MessageManager { * Send new leader notification message to other instances. * * @param currentId Instance ID of which sends this message. - * @param leaderId Leader message content. + * @param leaderId Leader message content. * @return {@code true} if the message is accepted by the target instances. */ boolean sendLeaderMessage(int currentId, int leaderId); @@ -61,5 +59,4 @@ public interface MessageManager { * @param currentId Instance ID of which sends this message. */ void sendHeartbeatInvokeMessage(int currentId); - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java index 9f3d3ea86743..d2d06cc21e4f 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/MessageType.java @@ -24,40 +24,24 @@ */ package com.iluwatar.leaderelection; -/** - * Message Type enum. - */ +/** Message Type enum. */ public enum MessageType { - /** - * Start the election. The content of the message stores ID(s) of the candidate instance(s). - */ + /** Start the election. The content of the message stores ID(s) of the candidate instance(s). */ ELECTION, - /** - * Nodify the new leader. The content of the message should be the leader ID. - */ + /** Nodify the new leader. The content of the message should be the leader ID. */ LEADER, - /** - * Check health of current leader instance. - */ + /** Check health of current leader instance. */ HEARTBEAT, - /** - * Inform target instance to start election. - */ + /** Inform target instance to start election. */ ELECTION_INVOKE, - /** - * Inform target instance to notify all the other instance that it is the new leader. - */ + /** Inform target instance to notify all the other instance that it is the new leader. */ LEADER_INVOKE, - /** - * Inform target instance to start heartbeat. - */ + /** Inform target instance to start heartbeat. */ HEARTBEAT_INVOKE - } - diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java index 51f9ec0a8c57..700d8ac6e13c 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyApp.java @@ -37,9 +37,7 @@ */ public class BullyApp { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { Map instanceMap = new HashMap<>(); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java index 1162ae37d797..3ee9629d469d 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyInstance.java @@ -42,9 +42,7 @@ public class BullyInstance extends AbstractInstance { private static final String INSTANCE = "Instance "; - /** - * Constructor of BullyInstance. - */ + /** Constructor of BullyInstance. */ public BullyInstance(MessageManager messageManager, int localId, int leaderId) { super(messageManager, localId, leaderId); } @@ -95,9 +93,7 @@ protected void handleElectionInvokeMessage() { } } - /** - * Process leader message. Update local leader information. - */ + /** Process leader message. Update local leader information. */ @Override protected void handleLeaderMessage(Message message) { leaderId = Integer.parseInt(message.getContent()); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java index 7a189c87cab3..e3bd158ed7db 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/bully/BullyMessageManager.java @@ -31,14 +31,10 @@ import java.util.List; import java.util.Map; -/** - * Implementation of BullyMessageManager. - */ +/** Implementation of BullyMessageManager. */ public class BullyMessageManager extends AbstractMessageManager { - /** - * Constructor of BullyMessageManager. - */ + /** Constructor of BullyMessageManager. */ public BullyMessageManager(Map instanceMap) { super(instanceMap); } @@ -59,7 +55,7 @@ public boolean sendHeartbeatMessage(int leaderId) { * Send election message to all the instances with smaller ID. * * @param currentId Instance ID of which sends this message. - * @param content Election message content. + * @param content Election message content. * @return {@code true} if no alive instance has smaller ID, so that the election is accepted. */ @Override @@ -78,14 +74,13 @@ public boolean sendElectionMessage(int currentId, String content) { * Send leader message to all the instances to notify the new leader. * * @param currentId Instance ID of which sends this message. - * @param leaderId Leader message content. + * @param leaderId Leader message content. * @return {@code true} if the message is accepted. */ @Override public boolean sendLeaderMessage(int currentId, int leaderId) { var leaderMessage = new Message(MessageType.LEADER, String.valueOf(leaderId)); - instanceMap.keySet() - .stream() + instanceMap.keySet().stream() .filter((i) -> i != currentId) .forEach((i) -> instanceMap.get(i).onMessage(leaderMessage)); return false; @@ -110,10 +105,8 @@ public void sendHeartbeatInvokeMessage(int currentId) { * @return ID list of all the candidate instance. */ private List findElectionCandidateInstanceList(int currentId) { - return instanceMap.keySet() - .stream() + return instanceMap.keySet().stream() .filter((i) -> i < currentId && instanceMap.get(i).isAlive()) .toList(); } - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java index feb3b97098c2..f72e37b01154 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingApp.java @@ -37,9 +37,7 @@ */ public class RingApp { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { Map instanceMap = new HashMap<>(); diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java index e19a882ef736..d4a8f0d38721 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingInstance.java @@ -45,9 +45,7 @@ public class RingInstance extends AbstractInstance { private static final String INSTANCE = "Instance "; - /** - * Constructor of RingInstance. - */ + /** Constructor of RingInstance. */ public RingInstance(MessageManager messageManager, int localId, int leaderId) { super(messageManager, localId, leaderId); } @@ -76,18 +74,16 @@ protected void handleHeartbeatInvokeMessage() { /** * Process election message. If the local ID is contained in the ID list, the instance will select - * the alive instance with the smallest ID to be the new leader, and send the leader inform message. - * If not, it will add its local ID to the list and send the message to the next instance in the - * ring. + * the alive instance with the smallest ID to be the new leader, and send the leader inform + * message. If not, it will add its local ID to the list and send the message to the next instance + * in the ring. */ @Override protected void handleElectionMessage(Message message) { var content = message.getContent(); LOGGER.info(INSTANCE + localId + " - Election Message: " + content); - var candidateList = Arrays.stream(content.trim().split(",")) - .map(Integer::valueOf) - .sorted() - .toList(); + var candidateList = + Arrays.stream(content.trim().split(",")).map(Integer::valueOf).sorted().toList(); if (candidateList.contains(localId)) { var newLeaderId = candidateList.get(0); LOGGER.info(INSTANCE + localId + " - New leader should be " + newLeaderId + "."); @@ -115,9 +111,7 @@ protected void handleLeaderMessage(Message message) { } } - /** - * Not used in Ring instance. - */ + /** Not used in Ring instance. */ @Override protected void handleLeaderInvokeMessage() { // Not used in Ring instance. @@ -132,5 +126,4 @@ protected void handleHeartbeatMessage(Message message) { protected void handleElectionInvokeMessage() { // Not used in Ring instance. } - } diff --git a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java index 98a1b571df30..7a055cf10331 100644 --- a/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java +++ b/leader-election/src/main/java/com/iluwatar/leaderelection/ring/RingMessageManager.java @@ -30,14 +30,10 @@ import com.iluwatar.leaderelection.MessageType; import java.util.Map; -/** - * Implementation of RingMessageManager. - */ +/** Implementation of RingMessageManager. */ public class RingMessageManager extends AbstractMessageManager { - /** - * Constructor of RingMessageManager. - */ + /** Constructor of RingMessageManager. */ public RingMessageManager(Map instanceMap) { super(instanceMap); } @@ -58,8 +54,8 @@ public boolean sendHeartbeatMessage(int leaderId) { * Send election message to the next instance. * * @param currentId currentID - * @param content list contains all the IDs of instances which have received this election - * message. + * @param content list contains all the IDs of instances which have received this election + * message. * @return {@code true} if the election message is accepted by the target instance. */ @Override @@ -74,7 +70,7 @@ public boolean sendElectionMessage(int currentId, String content) { * Send leader message to the next instance. * * @param currentId Instance ID of which sends this message. - * @param leaderId Leader message content. + * @param leaderId Leader message content. * @return {@code true} if the leader message is accepted by the target instance. */ @Override @@ -96,5 +92,4 @@ public void sendHeartbeatInvokeMessage(int currentId) { var heartbeatInvokeMessage = new Message(MessageType.HEARTBEAT_INVOKE, ""); nextInstance.onMessage(heartbeatInvokeMessage); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java index d54dcabd77b8..f8a4d1869fbf 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/MessageTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Message test case. - */ +/** Message test case. */ class MessageTest { @Test @@ -45,5 +43,4 @@ void testGetContent() { var message = new Message(MessageType.HEARTBEAT, content); assertEquals(content, message.getContent()); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java index b4da68fee8ba..32ba40c38f17 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyAppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.leaderelection.bully; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * BullyApp unit test. - */ +import org.junit.jupiter.api.Test; + +/** BullyApp unit test. */ class BullyAppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> BullyApp.main(new String[]{})); + assertDoesNotThrow(() -> BullyApp.main(new String[] {})); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java index 8c3cfbf72783..f1453202a39f 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyMessageManagerTest.java @@ -37,9 +37,7 @@ import java.util.Queue; import org.junit.jupiter.api.Test; -/** - * BullyMessageManager unit test. - */ +/** BullyMessageManager unit test. */ class BullyMessageManagerTest { @Test @@ -57,7 +55,8 @@ void testSendElectionMessageNotAccepted() { var instance2 = new BullyInstance(null, 1, 2); var instance3 = new BullyInstance(null, 1, 3); var instance4 = new BullyInstance(null, 1, 4); - Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); + Map instanceMap = + Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); instance1.setAlive(false); var messageManager = new BullyMessageManager(instanceMap); var result = messageManager.sendElectionMessage(3, "3"); @@ -81,7 +80,8 @@ void testElectionMessageAccepted() { var instance2 = new BullyInstance(null, 1, 2); var instance3 = new BullyInstance(null, 1, 3); var instance4 = new BullyInstance(null, 1, 4); - Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); + Map instanceMap = + Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); instance1.setAlive(false); var messageManager = new BullyMessageManager(instanceMap); var result = messageManager.sendElectionMessage(2, "2"); @@ -95,7 +95,8 @@ void testSendLeaderMessage() { var instance2 = new BullyInstance(null, 1, 2); var instance3 = new BullyInstance(null, 1, 3); var instance4 = new BullyInstance(null, 1, 4); - Map instanceMap = Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); + Map instanceMap = + Map.of(1, instance1, 2, instance2, 3, instance3, 4, instance4); instance1.setAlive(false); var messageManager = new BullyMessageManager(instanceMap); messageManager.sendLeaderMessage(2, 2); @@ -132,6 +133,4 @@ void testSendHeartbeatInvokeMessage() { fail("Error to access private field."); } } - - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java index e036b05a5a67..31dbad14b8ab 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/bully/BullyinstanceTest.java @@ -34,9 +34,7 @@ import java.util.Queue; import org.junit.jupiter.api.Test; -/** - * BullyInstance unit test. - */ +/** BullyInstance unit test. */ class BullyinstanceTest { @Test @@ -52,7 +50,6 @@ void testOnMessage() { } catch (IllegalAccessException | NoSuchFieldException e) { fail("fail to access messasge queue."); } - } @Test @@ -75,5 +72,4 @@ void testSetAlive() { bullyInstance.setAlive(false); assertFalse(bullyInstance.isAlive()); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java index 207b6ff240cd..839e481f4c81 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingAppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.leaderelection.ring; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * RingApp unit test. - */ +import org.junit.jupiter.api.Test; + +/** RingApp unit test. */ class RingAppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> RingApp.main(new String[]{})); + assertDoesNotThrow(() -> RingApp.main(new String[] {})); } - } diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java index 2c0634d71350..140432e54e1a 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingInstanceTest.java @@ -34,9 +34,7 @@ import java.util.Queue; import org.junit.jupiter.api.Test; -/** - * RingInstance unit test. - */ +/** RingInstance unit test. */ class RingInstanceTest { @Test diff --git a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java index 4dc8426581b4..ed13e4fb45f4 100644 --- a/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java +++ b/leader-election/src/test/java/com/iluwatar/leaderelection/ring/RingMessageManagerTest.java @@ -36,9 +36,7 @@ import java.util.Queue; import org.junit.jupiter.api.Test; -/** - * RingMessageManager unit test. - */ +/** RingMessageManager unit test. */ class RingMessageManagerTest { @Test @@ -112,5 +110,4 @@ void testSendHeartbeatInvokeMessage() { fail("Error to access private field."); } } - } diff --git a/leader-followers/README.md b/leader-followers/README.md index 0479ae232df5..c8c3d0d2a8ae 100644 --- a/leader-followers/README.md +++ b/leader-followers/README.md @@ -29,6 +29,10 @@ In plain words > Select one server in the cluster as a leader. The leader is responsible for taking decisions on behalf of the entire cluster and propagating the decisions to all the other servers. +Sequence diagram + +![Leader-Followers Sequence Diagram](./etc/leader-followers-sequence-diagram.png) + ## Programmatic Example of Leader-Followers Pattern in Java The Leader-Followers pattern is a concurrency design pattern where one thread (the leader) waits for work to arrive, de-multiplexes, dispatches, and processes the work, thereby enhancing CPU cache affinity and reducing event dispatching latency. Once the leader finishes processing the work, it promotes one of the follower threads to be the new leader. This pattern is useful for enhancing CPU cache affinity, minimizing locking overhead, and reducing event dispatching latency. diff --git a/leader-followers/etc/leader-followers-sequence-diagram.png b/leader-followers/etc/leader-followers-sequence-diagram.png new file mode 100644 index 000000000000..1c32d5c1f24f Binary files /dev/null and b/leader-followers/etc/leader-followers-sequence-diagram.png differ diff --git a/leader-followers/pom.xml b/leader-followers/pom.xml index d77cd803529f..12152cd3fc26 100644 --- a/leader-followers/pom.xml +++ b/leader-followers/pom.xml @@ -34,10 +34,37 @@ leader-followers + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.leaderfollowers.App + + + + + + + + diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java index ffd17bf246b1..88ff5c55fa56 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/App.java @@ -27,39 +27,11 @@ import java.security.SecureRandom; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; -/** - * Leader/Followers pattern is a concurrency pattern. This pattern behaves like a taxi stand where - * one of the threads acts as leader thread which listens for event from event sources, - * de-multiplexes, dispatches and handles the event. It promotes the follower to be the new leader. - * When processing completes the thread joins the followers queue, if there are no followers then it - * becomes the leader and cycle repeats again. - * - *

    In this example, one of the workers becomes Leader and listens on the {@link TaskSet} for - * work. {@link TaskSet} basically acts as the source of input events for the {@link Worker}, who - * are spawned and controlled by the {@link WorkCenter} . When {@link Task} arrives then the leader - * takes the work and calls the {@link TaskHandler}. It also calls the {@link WorkCenter} to - * promotes one of the followers to be the new leader, who can then process the next work and so - * on. - * - *

    The pros for this pattern are: - * It enhances CPU cache affinity and eliminates unbound allocation and data buffer sharing between - * threads by reading the request into buffer space allocated on the stack of the leader or by using - * the Thread-Specific Storage pattern [22] to allocate memory. It minimizes locking overhead by not - * exchanging data between threads, thereby reducing thread synchronization. In bound handle/thread - * associations, the leader thread dispatches the event based on the I/O handle. It can minimize - * priority inversion because no extra queuing is introduced in the server. It does not require a - * context switch to handle each event, reducing the event dispatching latency. Note that promoting - * a follower thread to fulfill the leader role requires a context switch. Programming simplicity: - * The Leader/Followers pattern simplifies the programming of concurrency models where multiple - * threads can receive requests, process responses, and de-multiplex connections using a shared - * handle set. - */ +@Slf4j public class App { - /** - * The main method for the leader followers pattern. - */ public static void main(String[] args) throws InterruptedException { var taskSet = new TaskSet(); var taskHandler = new TaskHandler(); @@ -68,22 +40,23 @@ public static void main(String[] args) throws InterruptedException { execute(workCenter, taskSet); } - /** - * Start the work, dispatch tasks and stop the thread pool at last. - */ private static void execute(WorkCenter workCenter, TaskSet taskSet) throws InterruptedException { var workers = workCenter.getWorkers(); var exec = Executors.newFixedThreadPool(workers.size()); - workers.forEach(exec::submit); - Thread.sleep(1000); - addTasks(taskSet); - exec.awaitTermination(2, TimeUnit.SECONDS); - exec.shutdownNow(); + + try { + workers.forEach(exec::submit); + Thread.sleep(1000); + addTasks(taskSet); + boolean terminated = exec.awaitTermination(2, TimeUnit.SECONDS); + if (!terminated) { + LOGGER.warn("Executor did not terminate in the given time."); + } + } finally { + exec.shutdownNow(); + } } - /** - * Add tasks. - */ private static void addTasks(TaskSet taskSet) throws InterruptedException { var rand = new SecureRandom(); for (var i = 0; i < 5; i++) { diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java index e5a405f43ac5..29374b931cc5 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Task.java @@ -27,17 +27,12 @@ import lombok.Getter; import lombok.Setter; -/** - * A unit of work to be processed by the Workers. - */ +/** A unit of work to be processed by the Workers. */ public class Task { - @Getter - private final int time; + @Getter private final int time; - @Getter - @Setter - private boolean finished; + @Getter @Setter private boolean finished; public Task(int time) { this.time = time; diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java index 60c102b65e2d..8689ce4421dd 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskHandler.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * The TaskHandler is used by the {@link Worker} to process the newly arrived task. - */ +/** The TaskHandler is used by the {@link Worker} to process the newly arrived task. */ @Slf4j public class TaskHandler { - /** - * This interface handles one task at a time. - */ + /** This interface handles one task at a time. */ public void handleTask(Task task) throws InterruptedException { var time = task.getTime(); Thread.sleep(time); diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java index 0c0ce4f30529..c54037024490 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/TaskSet.java @@ -27,9 +27,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -/** - * A TaskSet is a collection of the tasks, the leader receives task from here. - */ +/** A TaskSet is a collection of the tasks, the leader receives task from here. */ public class TaskSet { private final BlockingQueue queue = new ArrayBlockingQueue<>(100); diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java index bc5796e30ea1..c2fd32f3afef 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/WorkCenter.java @@ -35,13 +35,10 @@ */ public class WorkCenter { - @Getter - private Worker leader; + @Getter private Worker leader; private final List workers = new CopyOnWriteArrayList<>(); - /** - * Create workers and set leader. - */ + /** Create workers and set leader. */ public void createWorkers(int numberOfWorkers, TaskSet taskSet, TaskHandler taskHandler) { for (var id = 1; id <= numberOfWorkers; id++) { var worker = new Worker(id, this, taskSet, taskHandler); @@ -58,9 +55,7 @@ public void removeWorker(Worker worker) { workers.remove(worker); } - /** - * Promote a leader. - */ + /** Promote a leader. */ public void promoteLeader() { Worker leader = null; if (!workers.isEmpty()) { diff --git a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java index 5ad463a4a216..b079bc616da7 100644 --- a/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java +++ b/leader-followers/src/main/java/com/iluwatar/leaderfollowers/Worker.java @@ -27,22 +27,17 @@ import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; -/** - * Worker class that takes work from work center. - */ +/** Worker class that takes work from work center. */ @EqualsAndHashCode(onlyExplicitlyIncluded = true) @Slf4j public class Worker implements Runnable { - @EqualsAndHashCode.Include - private final long id; + @EqualsAndHashCode.Include private final long id; private final WorkCenter workCenter; private final TaskSet taskSet; private final TaskHandler taskHandler; - /** - * Constructor to create a worker which will take work from the work center. - */ + /** Constructor to create a worker which will take work from the work center. */ public Worker(long id, WorkCenter workCenter, TaskSet taskSet, TaskHandler taskHandler) { super(); this.id = id; @@ -83,5 +78,4 @@ public void run() { } } } - } diff --git a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/AppTest.java b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/AppTest.java index acd39e735396..dd8779141c01 100644 --- a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/AppTest.java +++ b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/AppTest.java @@ -28,14 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskHandlerTest.java b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskHandlerTest.java index 2d92e2e6bf29..a52aa9f79bbe 100644 --- a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskHandlerTest.java +++ b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskHandlerTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for TaskHandler - */ +/** Tests for TaskHandler */ class TaskHandlerTest { @Test @@ -40,5 +38,4 @@ void testHandleTask() throws InterruptedException { taskHandler.handleTask(handle); assertTrue(handle.isFinished()); } - } diff --git a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskSetTest.java b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskSetTest.java index 63559a938929..8b55ba41855d 100644 --- a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskSetTest.java +++ b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/TaskSetTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for TaskSet - */ +/** Tests for TaskSet */ class TaskSetTest { @Test @@ -48,5 +46,4 @@ void testGetTask() throws InterruptedException { assertEquals(100, task.getTime()); assertEquals(0, taskSet.getSize()); } - } diff --git a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/WorkCenterTest.java b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/WorkCenterTest.java index f37d7526e14c..425edc57632c 100644 --- a/leader-followers/src/test/java/com/iluwatar/leaderfollowers/WorkCenterTest.java +++ b/leader-followers/src/test/java/com/iluwatar/leaderfollowers/WorkCenterTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Tests for WorkCenter - */ +/** Tests for WorkCenter */ class WorkCenterTest { @Test diff --git a/localization/ar/abstract-document/README.md b/localization/ar/abstract-document/README.md new file mode 100644 index 000000000000..bf1599124f26 --- /dev/null +++ b/localization/ar/abstract-document/README.md @@ -0,0 +1,188 @@ +--- +title: Abstract Document +shortTitle: Abstract Document +category: Structural +language: ar +tag: + - Extensibility +--- + + +## الهدف + +استخدام الخصائص الديناميكية والحصول على مرونة اللغات غير المتغيرة مع الحفاظ على أمان الأنواع. + +## التوضيح + +يتيح استخدام نمط الوثيقة المجردة إدارة الخصائص غير الثابتة الإضافية. يستخدم هذا النمط مفهوم +السمات لتمكين أمان الأنواع وخصائص مفصولة من فئات مختلفة في مجموعة من الواجهات. + +مثال من العالم الحقيقي + +> خذ على سبيل المثال سيارة مكونة من العديد من الأجزاء. ومع ذلك، لا نعرف إذا كانت السيارة تحتوي على جميع الأجزاء أو جزء منها فقط. سياراتنا ديناميكية ومرنة للغاية. + +بصيغة أخرى + +> يسمح نمط الوثيقة المجردة بإضافة خصائص إلى الكائنات دون أن تكون هذه الكائنات على دراية بذلك. + +حسب ويكيبيديا + +> نمط تصميم هيكلي موجه للكائنات لتنظيم الكائنات في حاويات من نوع مفتاح-قيمة بشكل فضفاض مع نوعية غير محددة، وكشف البيانات باستخدام طرق عرض مهيكلة. الهدف من هذا النمط هو تحقيق درجة عالية من المرونة بين المكونات في لغة قوية النوع حيث يمكن إضافة خصائص جديدة إلى شجرة الكائنات أثناء العمل دون فقدان دعم أمان الأنواع. يستخدم النمط السمات لفصل خصائص مختلفة للفئة إلى واجهات متعددة. + +**مثال برمجي** + +أولاً، دعونا نعرف الفئات الأساسية `Document` و `AbstractDocument`. في الأساس، تجعل الكائن يحتوي على خريطة من الخصائص وأي عدد من الكائنات الفرعية. + + +```java +public interface Document { + + Void put(String key, Object value); + + Object get(String key); + + Stream children(String key, Function, T> constructor); +} + +public abstract class AbstractDocument implements Document { + + private final Map properties; + + protected AbstractDocument(Map properties) { + Objects.requireNonNull(properties, "properties map is required"); + this.properties = properties; + } + + @Override + public Void put(String key, Object value) { + properties.put(key, value); + return null; + } + + @Override + public Object get(String key) { + return properties.get(key); + } + + @Override + public Stream children(String key, Function, T> constructor) { + return Stream.ofNullable(get(key)) + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(constructor); + } + ... +} +``` + +بعد ذلك، نعرف `enum` لـ `Property` ومجموعة من الواجهات للنمط، السعر، النموذج، والأجزاء. هذا يتيح لنا إنشاء واجهات تظهر بشكل ثابت لفئة `Car`. + + +```java +public enum Property { + + PARTS, TYPE, PRICE, MODEL +} + +public interface HasType extends Document { + + default Optional getType() { + return Optional.ofNullable((String) get(Property.TYPE.toString())); + } +} + +public interface HasPrice extends Document { + + default Optional getPrice() { + return Optional.ofNullable((Number) get(Property.PRICE.toString())); + } +} +public interface HasModel extends Document { + + default Optional getModel() { + return Optional.ofNullable((String) get(Property.MODEL.toString())); + } +} + +public interface HasParts extends Document { + + default Stream getParts() { + return children(Property.PARTS.toString(), Part::new); + } +} +``` + +Ahora estamos listos para introducir el Coche `Car`. + +```java +public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { + + public Car(Map properties) { + super(properties); + } +} +``` + +وأخيرًا، هكذا نبني ونستخدم السيارة `Car` في مثال كامل. + + +```java + LOGGER.info("Constructing parts and car"); + + 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 carProperties = Map.of( + Property.MODEL.toString(), "300SL", + Property.PRICE.toString(), 10000L, + Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); + + var car = new Car(carProperties); + + LOGGER.info("Here is our car:"); + 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)) + ); + + // Constructing parts and car + // Here is our car: + // model: 300SL + // price: 10000 + // parts: + // wheel/15C/100 + // door/Lambo/300 +``` + +## Diagrama de clases + +![alt text](./etc/abstract-document.png "Abstract Document Traits and Domain") + +## التطبيق + +استخدم نمط الوثيقة المجردة عندما: + +* يوجد حاجة لإضافة خصائص أثناء العمل. +* ترغب في طريقة مرنة لتنظيم النطاق في هيكل مشابه لشجرة. +* ترغب في نظام أقل ترابطًا. + +## الحقوق + + +* [Wikipedia: Abstract Document Pattern](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) +* [Martin Fowler: Dealing with properties](http://martinfowler.com/apsupp/properties.pdf) +* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://www.amazon.com/gp/product/0470059028/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0470059028&linkId=e3aacaea7017258acf184f9f3283b492) \ No newline at end of file diff --git a/localization/ar/abstract-document/etc/abstract-document.png b/localization/ar/abstract-document/etc/abstract-document.png new file mode 100644 index 000000000000..6bc0b29a4e77 Binary files /dev/null and b/localization/ar/abstract-document/etc/abstract-document.png differ diff --git a/localization/ar/abstract-factory/README.md b/localization/ar/abstract-factory/README.md new file mode 100644 index 000000000000..230c9ae12b44 --- /dev/null +++ b/localization/ar/abstract-factory/README.md @@ -0,0 +1,234 @@ +--- +title: Abstract Factory +shortTitle: Abstract Factory +category: Creational +language: ar +tag: + - Gang of Four +--- +## يُعرف أيضًا باسم + +Kit + +## الهدف + +توفير واجهة لإنشاء عائلات من الكائنات المرتبطة المعتمدة دون تحديد فئتها المحددة. + +## التوضيح + +مثال من العالم الحقيقي + +> لإنشاء مملكة نحتاج إلى كائنات بموضوع مشترك. المملكة الإلفية تحتاج إلى ملك إلفي، وقلعة إلفية، وجيش إلفي، بينما المملكة الأوركية تحتاج إلى ملك أوركي، وقلعة أوركية، وجيش أوركي. هناك اعتماد بين كائنات المملكة. + +بصيغة أخرى + +> مصنع للمصانع؛ مصنع يجمع بين مصانع فردية ولكنها مرتبطة/معتمدة دون تحديد فئتها المحددة. + +حسب ويكيبيديا + +> يوفر نمط المصنع المجرد طريقة لتغليف مجموعة من المصانع الفردية التي لها موضوع مشترك دون تحديد فئاتها المحددة. + +**مثال برمجي** + +ترجمة المثال السابق حول الممالك. أولاً لدينا بعض الواجهات والتنفيذات لكائنات `Castle`. + + +```java +public interface Castle { + String getDescription(); +} + +public interface King { + String getDescription(); +} + +public interface Army { + String getDescription(); +} + +// Elven implementations -> +public class ElfCastle implements Castle { + static final String DESCRIPTION = "This is the elven castle!"; + @Override + public String getDescription() { + return DESCRIPTION; + } +} +public class ElfKing implements King { + static final String DESCRIPTION = "This is the elven king!"; + @Override + public String getDescription() { + return DESCRIPTION; + } +} +public class ElfArmy implements Army { + static final String DESCRIPTION = "This is the elven Army!"; + @Override + public String getDescription() { + return DESCRIPTION; + } +} + +// التنفيذات Orcish بطريقة مشابهة +-> ... + +``` + +ثم لدينا التجريد والتنفيذ لمصنع المملكة `KingdomFactory`. + + +```java +public interface KingdomFactory { + Castle createCastle(); + King createKing(); + Army createArmy(); +} + +public class ElfKingdomFactory implements KingdomFactory { + + @Override + public Castle createCastle() { + return new ElfCastle(); + } + + @Override + public King createKing() { + return new ElfKing(); + } + + @Override + public Army createArmy() { + return new ElfArmy(); + } +} + +public class OrcKingdomFactory implements KingdomFactory { + + @Override + public Castle createCastle() { + return new OrcCastle(); + } + + @Override + public King createKing() { + return new OrcKing(); + } + + @Override + public Army createArmy() { + return new OrcArmy(); + } +} +``` + +الآن لدينا المصنع المجرد الذي يسمح لنا بإنشاء عائلات من الكائنات المرتبطة. على سبيل المثال، مصنع المملكة الإلفية `ElfKingdomFactory` يقوم بإنشاء القلعة `castle`، الملك `king`، والجيش `army`، إلخ. + + + +```java +var factory = new ElfKingdomFactory(); +var castle = factory.createCastle(); +var king = factory.createKing(); +var army = factory.createArmy(); + +castle.getDescription(); +king.getDescription(); +army.getDescription(); +``` + +ناتج البرنامج: + + +```java +This is the elven castle! +This is the elven king! +This is the elven Army! +``` + +الآن يمكننا تصميم مصنع لمصانع الممالك الخاصة بنا. في هذا المثال، قمنا بإنشاء `FactoryMaker`، المسؤول عن إعادة نسخة من `ElfKingdomFactory` أو `OrcKingdomFactory`. +يمكن للعميل استخدام `FactoryMaker` لإنشاء مصنع محدد، والذي بدوره سينتج كائنات محددة مختلفة (مشتقة من `Army` و `King` و `Castle`). +في هذا المثال نستخدم أيضًا `enum` لتمرير نوع مصنع المملكة الذي سيطلبه العميل. + + +```java +public static class FactoryMaker { + + public enum KingdomType { + ELF, ORC + } + + public static KingdomFactory makeFactory(KingdomType type) { + return switch (type) { + case ELF -> new ElfKingdomFactory(); + case ORC -> new OrcKingdomFactory(); + default -> throw new IllegalArgumentException("KingdomType not supported."); + }; + } +} + + public static void main(String[] args) { + var app = new App(); + + LOGGER.info("Elf Kingdom"); + app.createKingdom(FactoryMaker.makeFactory(KingdomType.ELF)); + LOGGER.info(app.getArmy().getDescription()); + LOGGER.info(app.getCastle().getDescription()); + LOGGER.info(app.getKing().getDescription()); + + LOGGER.info("Orc Kingdom"); + app.createKingdom(FactoryMaker.makeFactory(KingdomType.ORC)); + --similar use of the orc factory + } +``` + +## مخطط الفئات + +![alt text](./etc/abstract-factory.urm.png "Diagrama de Clases de Abstract Factory") + + +## التطبيق + +استخدم نمط المصنع المجرد عندما: + +* يجب أن يكون النظام غير متحيز حول كيفية إنشاء وتركيب وتمثيل كائناته. +* يجب تكوين النظام مع إحدى عائلات المنتجات المتعددة. +* تم تصميم عائلة الكائنات المرتبطة لتستخدم معًا وتحتاج إلى فرض هذا الافتراض. +* ترغب في توفير مكتبة من المنتجات ولا تريد الكشف عن تنفيذاتها، بل واجهاتها فقط. +* العمر الافتراضي للاعتماد هو مفهومًا أقصر من عمر العميل. +* تحتاج إلى قيمة في وقت التشغيل لبناء الاعتماد. +* تريد تحديد أي منتج من العائلة يتم استدعاؤه في وقت التشغيل. +* تحتاج إلى توفير واحد أو أكثر من المعلمات المعروفة فقط في وقت التشغيل قبل أن تتمكن من حل الاعتماد. +* تحتاج إلى الاتساق بين المنتجات. +* لا تريد تغيير الكود الموجود عند إضافة منتجات أو عائلات جديدة من المنتجات إلى البرنامج. + +أمثلة على حالات الاستخدام + +* اختيار استدعاء التنفيذ الصحيح لـ FileSystemAcmeService أو DatabaseAcmeService أو NetworkAcmeService في وقت التشغيل. +* كتابة الاختبارات الوحدوية تصبح أسهل بكثير. +* أدوات واجهة المستخدم (UI) لأنظمة تشغيل مختلفة (SO). + +## العواقب + +* إخفاء حقن الاعتمادات في جافا داخل كائنات الخدمة قد يؤدي إلى أخطاء في وقت التشغيل كان يمكن تجنبها في وقت الترجمة. +* بينما يكون النمط جيدًا في إنشاء كائنات محددة مسبقًا، قد يكون من الصعب إضافة الجديدة. +* الكود يصبح أكثر تعقيدًا مما ينبغي لأنه يتم إضافة العديد من الواجهات والفئات الجديدة جنبًا إلى جنب مع النمط. + +## الدروس التعليمية + +* [Abstract Factory Pattern Tutorial](https://www.journaldev.com/1418/abstract-factory-design-pattern-in-java) + +## الاستخدامات المعروفة + +* [javax.xml.parsers.DocumentBuilderFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/parsers/DocumentBuilderFactory.html) +* [javax.xml.transform.TransformerFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/transform/TransformerFactory.html#newInstance--) +* [javax.xml.xpath.XPathFactory](http://docs.oracle.com/javase/8/docs/api/javax/xml/xpath/XPathFactory.html#newInstance--) + +## الأنماط المتعلقة + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) +* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/) + +## الحقوق + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/ar/abstract-factory/etc/abstract-factory.urm.png b/localization/ar/abstract-factory/etc/abstract-factory.urm.png new file mode 100644 index 000000000000..836858a2c652 Binary files /dev/null and b/localization/ar/abstract-factory/etc/abstract-factory.urm.png differ diff --git a/localization/ar/active-object/README.md b/localization/ar/active-object/README.md new file mode 100644 index 000000000000..842570a2604a --- /dev/null +++ b/localization/ar/active-object/README.md @@ -0,0 +1,126 @@ +--- +title: Active Object +shortTitle: Active Object +category: Concurrency +language: ar +tag: + - Performance +--- + + +## الهدف +يفصل نمط التصميم الكائن النشط بين تنفيذ الطريقة واستدعائها للكائنات التي تعمل في خيط تحكم خاص بها. الهدف هو إدخال التزامن باستخدام استدعاءات الطرق غير المتزامنة وجدولة لإدارة الطلبات. + +## التوضيح + +ستحتوي الفئة التي تنفذ نمط التصميم الكائن النشط على آلية مزامنة ذاتية دون استخدام الطرق المتزامنة (synchronized). + +مثال من العالم الحقيقي + +> الأورك معروفون بوحشيتهم وفلسفتهم في عدم العمل الجماعي. بناءً على هذا السلوك، يمكن القول إن لديهم خيط تحكم خاص بهم. + +يمكننا استخدام نمط الكائن النشط لتنفيذ مخلوق لديه خيط تحكم خاص به ويعرض واجهة برمجة التطبيقات (API) الخاصة به، ولكن ليس التنفيذ نفسه. + +**مثال برمجي** + + +```java +public abstract class ActiveCreature{ + private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName()); + + private BlockingQueue requests; + + private String name; + + private Thread thread; + + public ActiveCreature(String name) { + this.name = name; + this.requests = new LinkedBlockingQueue(); + thread = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + requests.take().run(); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } + } + } + ); + thread.start(); + } + + public void eat() throws InterruptedException { + requests.put(new Runnable() { + @Override + public void run() { + logger.info("{} is eating!",name()); + logger.info("{} has finished eating!",name()); + } + } + ); + } + + public void roam() throws InterruptedException { + requests.put(new Runnable() { + @Override + public void run() { + logger.info("{} has started to roam the wastelands.",name()); + } + } + ); + } + + public String name() { + return this.name; + } +} +``` +يمكننا أن نرى أن أي فئة تمتد من ActiveCreature سيكون لديها خيط تحكم خاص بها لاستدعاء وتنفيذ الطرق. + +على سبيل المثال، الفئة Orc: + +```java +public class Orc extends ActiveCreature { + + public Orc(String name) { + super(name); + } + +} +``` +الآن يمكننا إنشاء مخلوقات متعددة مثل الأورك، نطلب منهم الأكل والتجول، وسيفعلون ذلك في خيط التحكم الخاص بهم: + +```java + public static void main(String[] args) { + var app = new App(); + app.run(); + } + + @Override + public void run() { + ActiveCreature creature; + try { + for (int i = 0;i < creatures;i++) { + creature = new Orc(Orc.class.getSimpleName().toString() + i); + creature.eat(); + creature.roam(); + } + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + Runtime.getRuntime().exit(1); + } +``` + +## مخطط الفئات + +![alt text](./etc/active-object.urm.png "Active Object class diagram") + +## الدروس التعليمية + +* [Android and Java Concurrency: The Active Object Pattern](https://www.youtube.com/watch?v=Cd8t2u5Qmvc) \ No newline at end of file diff --git a/localization/ar/active-object/etc/active-object.urm.png b/localization/ar/active-object/etc/active-object.urm.png new file mode 100644 index 000000000000..c14f66144ee2 Binary files /dev/null and b/localization/ar/active-object/etc/active-object.urm.png differ diff --git a/localization/ar/acyclic-visitor/README.md b/localization/ar/acyclic-visitor/README.md new file mode 100644 index 000000000000..8bb8e01a154f --- /dev/null +++ b/localization/ar/acyclic-visitor/README.md @@ -0,0 +1,167 @@ +--- +title: Acyclic Visitor +shortTitle: Acyclic Visitor +category: Behavioral +language: ar +tag: + - Extensibility +--- +## الهدف + +السماح بإضافة وظائف جديدة إلى تسلسلات الفئات الموجودة دون التأثير عليها، ودون إنشاء الدوائر المعتمدة المزعجة التي هي جزء من نمط GoF (Gang of Four) للزائر (Visitor). + +## التوضيح + +مثال من العالم الحقيقي + +> لدينا تسلسل فئات مودم. يجب أن تتم زيارة المودمات في هذه التسلسلات بواسطة خوارزمية خارجية بناءً على بعض الفلاتر (هل المودم متوافق مع Unix أو DOS؟). + +بصيغة أخرى + +> يتيح نمط Acyclic Visitor إضافة وظائف إلى تسلسلات الفئات الموجودة دون تعديلها. + +[WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) يقول + +> يسمح نمط Acyclic Visitor بإضافة وظائف جديدة إلى تسلسلات الفئات الموجودة دون التأثير عليها، ودون إنشاء الدوائر المعتمدة التي هي جزء من نمط الزائر (Visitor Pattern) في GangOfFour. + +**مثال برمجي** + +هنا لدينا تسلسل `Modem`. + + +```java +public abstract class Modem { + public abstract void accept(ModemVisitor modemVisitor); +} + +public class Zoom extends Modem { + ... + @Override + public void accept(ModemVisitor modemVisitor) { + if (modemVisitor instanceof ZoomVisitor) { + ((ZoomVisitor) modemVisitor).visit(this); + } else { + LOGGER.info("Only ZoomVisitor is allowed to visit Zoom modem"); + } + } +} + +public class Hayes extends Modem { + ... + @Override + public void accept(ModemVisitor modemVisitor) { + if (modemVisitor instanceof HayesVisitor) { + ((HayesVisitor) modemVisitor).visit(this); + } else { + LOGGER.info("Only HayesVisitor is allowed to visit Hayes modem"); + } + } +} +``` + +بعد ذلك، لدينا تسلسل `ModemVisitor`. + + +```java +public interface ModemVisitor { +} + +public interface HayesVisitor extends ModemVisitor { + void visit(Hayes hayes); +} + +public interface ZoomVisitor extends ModemVisitor { + void visit(Zoom zoom); +} + +public interface AllModemVisitor extends ZoomVisitor, HayesVisitor { +} + +public class ConfigureForDosVisitor implements AllModemVisitor { + ... + @Override + public void visit(Hayes hayes) { + LOGGER.info(hayes + " used with Dos configurator."); + } + @Override + public void visit(Zoom zoom) { + LOGGER.info(zoom + " used with Dos configurator."); + } +} + +public class ConfigureForUnixVisitor implements ZoomVisitor { + ... + @Override + public void visit(Zoom zoom) { + LOGGER.info(zoom + " used with Unix configurator."); + } +} +``` + +وأخيرًا، هنا "الزوار" في العمل. + + +```java + var conUnix = new ConfigureForUnixVisitor(); + var conDos = new ConfigureForDosVisitor(); + var zoom = new Zoom(); + var hayes = new Hayes(); + hayes.accept(conDos); + zoom.accept(conDos); + hayes.accept(conUnix); + zoom.accept(conUnix); +``` + +ناتج البرنامج: + + +``` + // Hayes modem used with Dos configurator. + // Zoom modem used with Dos configurator. + // Only HayesVisitor is allowed to visit Hayes modem + // Zoom modem used with Unix configurator. +``` + + +## مخطط الفئات + +![alt text](./etc/acyclic-visitor.png "Acyclic Visitor") + +## التطبيق + +يمكن استخدام هذا النمط في الحالات التالية: + +* عندما تحتاج إلى إضافة وظيفة جديدة إلى تسلسل فئات دون التأثير عليها أو تعديلها. +* عندما توجد وظائف تعمل على التسلسل ولكنها لا تنتمي إلى التسلسل بحد ذاته. على سبيل المثال، الفئات `ConfigureForDOS` و `ConfigureForUnix` و `ConfigureForX`. +* عندما تحتاج إلى تنفيذ عمليات مختلفة تمامًا على كائن اعتمادًا على نوعه. +* عندما يكون من المتوقع توسيع التسلسل الزائر بشكل متكرر باستخدام مشتقات من فئة العنصر. +* عندما يكون عملية إعادة التجميع، الربط، الاختبار أو توزيع المشتقات من فئة العنصر مرهقة جدًا. + +## الدروس التعليمية + +* [Acyclic Visitor Pattern Example](https://codecrafter.blogspot.com/2012/12/the-acyclic-visitor-pattern.html) + +## العواقب + +الجوانب الجيدة: + +* لا توجد دوائر اعتماد بين التسلسلات. +* لا حاجة لإعادة تجميع جميع الزوار إذا تم إضافة زائر جديد. +* لا يسبب أخطاء في الترجمة في الزوار الموجودين إذا كانت التسلسل تحتوي على عضو جديد. + +الجوانب السيئة: + + +* ينتهك [مبدأ الاستبدال في ليسكوف](https://java-design-patterns.com/principles/#liskov-substitution-principle) من خلال إظهار أنه يمكن قبول جميع الزوار مع الاهتمام بزائر واحد فقط. +* يجب إنشاء تسلسل زوار موازٍ لجميع الأعضاء في التسلسل الذي يمكن زيارته. + +## الأنماط المتعلقة + +* [Visitor Pattern](https://java-design-patterns.com/patterns/visitor/) + + +## الحقوق + + +* [Acyclic Visitor by Robert C. Martin](http://condor.depaul.edu/dmumaugh/OOT/Design-Principles/acv.pdf) +* [Acyclic Visitor in WikiWikiWeb](https://wiki.c2.com/?AcyclicVisitor) diff --git a/localization/ar/acyclic-visitor/etc/acyclic-visitor.png b/localization/ar/acyclic-visitor/etc/acyclic-visitor.png new file mode 100644 index 000000000000..7b4df13d80f8 Binary files /dev/null and b/localization/ar/acyclic-visitor/etc/acyclic-visitor.png differ diff --git a/localization/ar/adapter/README.md b/localization/ar/adapter/README.md new file mode 100644 index 000000000000..06cb0a14ae6e --- /dev/null +++ b/localization/ar/adapter/README.md @@ -0,0 +1,141 @@ +--- +title: Adapter +shortTitle: Adapter +category: Structural +language: ar +tag: + - Gang of Four +--- + +## أيضًا معروف بـ +الغطاء (Wrapper) + +## الهدف +تحويل واجهة فئة إلى واجهة أخرى يتوقعها العميل. يتيح نمط المحول (Adapter) للفئات العمل مع فئات أخرى التي لا يمكنها العمل معها في الظروف العادية بسبب مشاكل التوافق. + +## التوضيح + +مثال من العالم الحقيقي + +> تخيل أنك تمتلك بعض الصور في بطاقة ذاكرة وتريد نقلها إلى جهاز الكمبيوتر الخاص بك. لنقل الصور، تحتاج إلى نوع من المحول الذي يتوافق مع منافذ جهاز الكمبيوتر الخاص بك ويسمح لك بإدخال البطاقة. في هذه الحالة، قارئ البطاقات هو محول (Adapter). +> مثال آخر هو محول التيار الكهربائي؛ إذا كان هناك قابس بثلاثة دبابيس ولا يمكن توصيله بمنفذ كهربائي به ثقبين، فإنه يحتاج إلى محول لجعله متوافقًا مع المنفذ. +> مثال آخر هو مترجم يترجم كلمات من شخص لآخر. + +بصيغة أخرى + +> يتيح نمط المحول (Adapter) تغليف كائن داخل محول لجعله متوافقًا مع فئة سيكون غير متوافق معها بطريقة أخرى. + +حسب ويكيبيديا + +> في هندسة البرمجيات، نمط المحول هو نمط تصميم برمجي يسمح باستخدام واجهة فئة موجودة كواجهة مختلفة. وغالبًا ما يُستخدم لجعل الفئات الموجودة تعمل مع فئات أخرى دون الحاجة إلى تعديل كود المصدر الخاص بها. + +**مثال برمجي** + +خذ على سبيل المثال قبطان يمكنه فقط استخدام القوارب التي تعمل بالمجاديف ولا يمكنه الإبحار على الإطلاق. + +أولاً، لدينا الواجهات `RowingBoat` (قارب المجاديف) و `FishingBoat` (قارب الصيد). + +```java +public interface RowingBoat { + void row(); +} + +@Slf4j +public class FishingBoat { + public void sail() { + LOGGER.info("The fishing boat is sailing"); + } +} +``` + +ويتوقع القبطان تنفيذ واجهة `RowingBoat` (قارب المجاديف) ليتمكن من التحرك. + + +```java +public class Captain { + + private final RowingBoat rowingBoat; + // default constructor and setter for rowingBoat + public Captain(RowingBoat rowingBoat) { + this.rowingBoat = rowingBoat; + } + + public void row() { + rowingBoat.row(); + } +} +``` + +الآن لنفترض أن مجموعة من القراصنة قد جاءت ويجب على قائدنا الهروب، ولكن هناك فقط قارب صيد. نحتاج إلى إنشاء محول يسمح للقائد باستخدام قارب الصيد مع مهاراته لاستخدام القوارب التي تعمل بالمجاديف. + + +```java +@Slf4j +public class FishingBoatAdapter implements RowingBoat { + + private final FishingBoat boat; + + public FishingBoatAdapter() { + boat = new FishingBoat(); + } + + @Override + public void row() { + boat.sail(); + } +} +``` + +والآن يمكن لـ `Captain` (القائد) استخدام `FishingBoat` (قارب الصيد) للهروب من القراصنة. + + +```java +var captain = new Captain(new FishingBoatAdapter()); +captain.row(); +``` + +## مخطط الفئات +![alt text](./etc/adapter.urm.png "Adapter class diagram") + +## التطبيق +استخدم نمط المحول (Adapter) عندما: + +* تريد استخدام فئة موجودة وواجهتها لا تتناسب مع ما تحتاجه. +* تريد إنشاء فئة قابلة لإعادة الاستخدام تتعاون مع فئات غير مرتبطة أو لم يكن من المخطط تعاونها معًا، أي فئات ليس لديها بالضرورة واجهات متوافقة. +* تحتاج إلى استخدام عدة فئات فرعية موجودة، ولكن من غير العملي تعديل واجهتها عن طريق إنشاء فئات فرعية لكل واحدة. يمكن للمحول تعديل واجهة الفئة الأصلية. +* العديد من التطبيقات التي تستخدم مكتبات طرف ثالث تستخدم المحولات كطبقات وسيطة بين التطبيق والمكتبة لفصل التطبيق عن المكتبة. إذا كان من الضروري استخدام مكتبة أخرى، يكفي إنشاء محول للمكتبة الجديدة دون الحاجة إلى تعديل كود التطبيق. + +## الدروس + + +* [Dzone](https://dzone.com/articles/adapter-design-pattern-in-java) +* [Refactoring Guru](https://refactoring.guru/design-patterns/adapter/java/example) +* [Baeldung](https://www.baeldung.com/java-adapter-pattern) + +## العواقب +المحولات بين الفئات والكائنات لها خصائص مختلفة. محول الفئات + +* يقوم بإجراء التكيف ويرتبط بفئة محددة. كنتيجة لذلك، لن يعمل محول الفئات عندما نريد تكيف فئة وفئاتها الفرعية. +* يسمح للمحول بتعديل سلوك الفئة المتكيفة لأن المحول هو فئة فرعية من الفئة المتكيفة. +* يستخدم كائنًا واحدًا ولا يحتاج إلى استخدام مؤشرات إضافية للإشارة إلى الفئة المتكيفة. + +محوّل الكائنات + +* يسمح للمحول الواحد بالعمل مع العديد من الفئات، أي مع الفئة المتكيفة وكل الفئات الفرعية لها (إذا كانت موجودة). يمكن للمحول أيضًا إضافة وظائف إلى جميع الفئات المتكيفة في نفس الوقت. +* يجعل من الصعب تعديل سلوك الفئة المتكيفة. سيكون من الضروري إنشاء فئة فرعية للفئة المتكيفة وجعل المحول يشير إلى الفئة الفرعية بدلاً من الفئة الأصلية. + +## أمثلة من العالم الواقعي + + +* [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29) +* [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-) +* [java.util.Collections#enumeration()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#enumeration-java.util.Collection-) +* [javax.xml.bind.annotation.adapters.XMLAdapter](http://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html#marshal-BoundType-) + + +## الشكر + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/ar/adapter/etc/adapter.urm.png b/localization/ar/adapter/etc/adapter.urm.png new file mode 100644 index 000000000000..341ad67699d9 Binary files /dev/null and b/localization/ar/adapter/etc/adapter.urm.png differ diff --git a/localization/ar/aggregator-microservices/README.md b/localization/ar/aggregator-microservices/README.md new file mode 100644 index 000000000000..40d3ff26f7a8 --- /dev/null +++ b/localization/ar/aggregator-microservices/README.md @@ -0,0 +1,111 @@ +--- +title: Aggregator Microservices +shortTitle: Aggregator Microservices +category: Architectural +language: ar +tag: +- Cloud distributed +- Decoupling +- Microservices +--- + +## الهدف + +يقوم المستخدم بإجراء مكالمة واحدة إلى خدمة المجمع، ومن ثم يقوم المجمع بالاتصال بكل خدمة ميكروسيرفيس ذات صلة. + +## الشرح + +مثال من العالم الواقعي + +> يحتاج سوقنا الإلكتروني إلى معلومات حول المنتجات والمخزون الحالي. يقوم بإجراء مكالمة إلى خدمة المجمع التي بدورها تتصل بخدمة ميكروسيرفيس المعلومات الخاصة بالمنتج وخدمة ميكروسيرفيس المخزون للمنتج التي تعيد المعلومات مجمعة. + +ببساطة + +> يقوم ميكروسيرفيس المجمع بجمع البيانات من عدة ميكروسيرفيسات ويعيد النتيجة مجمعة لمعالجتها. + +يقول StackOverflow + +> يقوم ميكروسيرفيس المجمع بالاتصال بعدة خدمات لتحقيق الوظيفة المطلوبة من التطبيق. + +**مثال برمجي** + +لنبدأ بنموذج البيانات. هنا هو `Product`. + + +```java +public class Product { + private String title; + private int productInventories; + // getters and setters -> + ... +} +``` + +بعد ذلك، يمكننا تقديم ميكروسيرفيسنا `Aggregator` (مجمع الميكروسيرفيسات). يحتوي على `ProductInformationClient` (عميل معلومات المنتج) و +`ProductInventoryClient` (عميل مخزون المنتج) للاتصال بالميكروسيرفيسات المعنية. + + +```java +@RestController +public class Aggregator { + + @Resource + private ProductInformationClient informationClient; + + @Resource + private ProductInventoryClient inventoryClient; + + @RequestMapping(path = "/product", method = RequestMethod.GET) + public Product getProduct() { + + var product = new Product(); + var productTitle = informationClient.getProductTitle(); + var productInventory = inventoryClient.getProductInventories(); + + //Fallback to error message + product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed")); + + //Fallback to default error inventory + product.setProductInventories(requireNonNullElse(productInventory, -1)); + + return product; + } +} +``` + +هذه هي جوهر تنفيذ الميكروسيرفيسات للمعلومات. ميكروسيرفيس المخزون مشابه، ببساطة يعيد +عدد المخزون. + + +```java +@RestController +public class InformationController { + @RequestMapping(value = "/information", method = RequestMethod.GET) + public String getProductTitle() { + return "The Product Title."; + } +} +``` + +الآن عند استدعاء واجهة برمجة التطبيقات REST الخاصة بنا `Aggregator` تُرجع معلومات المنتج. + + +```bash +curl http://localhost:50004/product +{"title":"The Product Title.","productInventories":5} +``` + +## مخطط الفئة + +![alt text](./aggregator-service/etc/aggregator-service.png "Aggregator Microservice") + +## قابلية التطبيق + +استخدم نمط تجميع الميكروسيرفيسات (Aggregator Microservices) عندما تحتاج إلى واجهة برمجة تطبيقات موحدة لعدة ميكروسيرفيسات، بغض النظر عن جهاز العميل. + + +## الشكر + +* [أنماط تصميم الميكروسيرفيس](http://web.archive.org/web/20190705163602/http://blog.arungupta.me/microservice-design-patterns/) +* [أنماط الميكروسيرفيس: مع أمثلة في Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=8b4e570267bc5fb8b8189917b461dc60) +* [أنماط العمارة: اكتشاف الأنماط الأساسية في أكثر المجالات ضرورة في العمارة المؤسسية](https://www.amazon.com/gp/product/B077T7V8RC/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=B077T7V8RC&linkId=c34d204bfe1b277914b420189f09c1a4) diff --git a/localization/ar/aggregator-microservices/aggregator-service/etc/aggregator-service.png b/localization/ar/aggregator-microservices/aggregator-service/etc/aggregator-service.png new file mode 100644 index 000000000000..75ee82328bbe Binary files /dev/null and b/localization/ar/aggregator-microservices/aggregator-service/etc/aggregator-service.png differ diff --git a/localization/ar/ambassador/README.md b/localization/ar/ambassador/README.md new file mode 100644 index 000000000000..b70fe071eb31 --- /dev/null +++ b/localization/ar/ambassador/README.md @@ -0,0 +1,210 @@ +--- +title: Ambassador +shortTitle: Ambassador +category: Structural +language: ar +tag: + - Decoupling + - Cloud distributed +--- + +## الهدف + +توفير مثيل من الخدمة المساعدة للعميل وتفويض الوظائف المشتركة لها من مصدر مشترك. + +## الشرح + +مثال واقعي + +> خدمة بعيدة تحتوي على العديد من العملاء الذين يصلون إلى وظيفة تقدمها هذه الخدمة. الخدمة هي تطبيق قديم ومن +> المستحيل تحديثها. عدد كبير من الطلبات من قبل المستخدمين تسبب مشاكل في الاتصال. يجب تنفيذ قواعد جديدة +> بشأن تكرار الطلبات جنبًا إلى جنب مع فحوصات التأخير والسجلات من جانب العميل. + +بكلمات أخرى + +> باستخدام نمط Ambassador، يمكننا تنفيذ تقليل تكرار الطلبات من العملاء جنبًا إلى جنب مع فحوصات التأخير +> والسجلات. + +وفقًا لوثائق مايكروسوفت + +> يمكن اعتبار خدمة Ambassador وكأنها وكيل خارج العملية يتواجد جنبًا إلى جنب مع العميل. +> +> يمكن أن يكون هذا النمط مفيدًا لتفريغ المهام المشتركة في الاتصال من العميل مثل المراقبة، السجلات، التوجيه، +> الأمان (مثل TLS) وأنماط المقاومة (*) بطريقة مستقلة عن اللغة. غالبًا ما يُستخدم مع التطبيقات القديمة، +> أو التطبيقات الأخرى التي يصعب تعديلها، بهدف توسيع قدراتها في الشبكات. يمكنه أيضًا +> تمكين فريق متخصص لتنفيذ هذه الميزات. + +**مثال على الكود** + +مع المقدمة السابقة في الاعتبار، سنقوم بمحاكاة وظيفتها في المثال التالي. لدينا واجهة يتم تنفيذها +من قبل الخدمة البعيدة وكذلك خدمة Ambassador: + +```java +interface RemoteServiceInterface { + long doRemoteFunction(int value) throws Exception; +} +``` + +## خدمة بعيدة ممثلة كـ Singleton (مثيل واحد). + +```java +@Slf4j +public class RemoteService implements RemoteServiceInterface { + private static RemoteService service = null; + + static synchronized RemoteService getRemoteService() { + if (service == null) { + service = new RemoteService(); + } + return service; + } + + private RemoteService() {} + + @Override + public long doRemoteFunction(int value) { + long waitTime = (long) Math.floor(Math.random() * 1000); + + try { + sleep(waitTime); + } catch (InterruptedException e) { + LOGGER.error("Thread sleep interrupted", e); + } + + return waitTime >= 200 ? value * 10 : -1; + } +} +``` + +## خدمة السفير مع إضافة وظائف إضافية مثل السجلات والتحقق من الكمون + +```java +@Slf4j +public class ServiceAmbassador implements RemoteServiceInterface { + private static final int RETRIES = 3; + private static final int DELAY_MS = 3000; + + ServiceAmbassador() { + } + + @Override + public long doRemoteFunction(int value) { + return safeCall(value); + } + + private long checkLatency(int value) { + var startTime = System.currentTimeMillis(); + var result = RemoteService.getRemoteService().doRemoteFunction(value); + var timeTaken = System.currentTimeMillis() - startTime; + + LOGGER.info("Time taken (ms): " + timeTaken); + return result; + } + + private long safeCall(int value) { + var retries = 0; + var result = (long) FAILURE; + + for (int i = 0; i < RETRIES; i++) { + if (retries >= RETRIES) { + return FAILURE; + } + + if ((result = checkLatency(value)) == FAILURE) { + LOGGER.info("Failed to reach remote: (" + (i + 1) + ")"); + retries++; + try { + sleep(DELAY_MS); + } catch (InterruptedException e) { + LOGGER.error("Thread sleep state interrupted", e); + } + } else { + break; + } + } + return result; + } +} +``` + +العميل لديه خدمة سفير محلية تستخدم للتفاعل مع الخدمة البعيدة: + + +```java +@Slf4j +public class Client { + private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador(); + + long useService(int value) { + var result = serviceAmbassador.doRemoteFunction(value); + LOGGER.info("Service result: " + result); + return result; + } +} +``` + +بعد ذلك، لدينا عميلان يستخدمان الخدمة: + + +```java +public class App { + public static void main(String[] args) { + var host1 = new Client(); + var host2 = new Client(); + host1.useService(12); + host2.useService(73); + } +} +``` + +هذه هي النتيجة التي سنحصل عليها بعد تنفيذ المثال: + + +```java +Time taken (ms): 111 +Service result: 120 +Time taken (ms): 931 +Failed to reach remote: (1) +Time taken (ms): 665 +Failed to reach remote: (2) +Time taken (ms): 538 +Failed to reach remote: (3) +Service result: -1 +``` + +## مخطط الفئات + +![alt text](./etc/ambassador.urm.png "مخطط فئات Ambassador") + +## التطبيقات + +يتم تطبيق نمط Ambassador عندما نعمل مع خدمة بعيدة قديمة لا يمكن تعديلها أو أنه سيكون من الصعب للغاية تعديلها. يمكن تنفيذ خصائص الاتصال في العميل دون الحاجة إلى إجراء تغييرات على الخدمة البعيدة. + +* يوفر Ambassador واجهة محلية لخدمة بعيدة. +* يوفر Ambassador سجلات، انقطاع الدائرة، إعادة المحاولات، والأمان في العميل. + +## حالات الاستخدام النموذجية + +* التحكم في الوصول إلى كائن آخر +* تنفيذ السجلات أو السجلات +* تنفيذ انقطاع الدائرة +* تفويض مهام الخدمات البعيدة +* تسهيل الاتصال بالشبكة + +## الاستخدامات المعروفة + +* [بوابة API المدمجة مع Kubernetes للخدمات الصغيرة](https://github.com/datawire/ambassador) + +## الأنماط ذات الصلة + +* [الوكيل (Proxy)](https://java-design-patterns.com/patterns/proxy/) + +## الشكر + +* [نمط Ambassador (وثائق Microsoft باللغة الإنجليزية)](https://docs.microsoft.com/en-us/azure/architecture/patterns/ambassador) +* [تصميم الأنظمة الموزعة: الأنماط والمفاهيم للخدمات القابلة للتوسع وموثوقة](https://www.amazon.com/s?k=designing+distributed+systems&sprefix=designing+distri%2Caps%2C156&linkCode=ll2&tag=javadesignpat-20&linkId=a12581e625462f9038557b01794e5341&language=en_US&ref_=as_li_ss_tl) + +## ملاحظات المترجم +(*) تشير النسخة الأصلية بالإنجليزية من وثائق Microsoft إلى مصطلح المرونة +وفي الترجمة الإسبانية يتم ترجمته إلى المقاومة، على الرغم من ربطه بالقسم الخاص بنمط الموثوقية. انظر: +* [نسخة الوثائق الخاصة بنمط Ambassador من Microsoft باللغة الإسبانية.](https://learn.microsoft.com/es-es/azure/architecture/patterns/ambassador) diff --git a/localization/ar/ambassador/etc/ambassador.urm.png b/localization/ar/ambassador/etc/ambassador.urm.png new file mode 100644 index 000000000000..9b50a02ad356 Binary files /dev/null and b/localization/ar/ambassador/etc/ambassador.urm.png differ diff --git a/localization/ar/api-gateway/README.md b/localization/ar/api-gateway/README.md new file mode 100644 index 000000000000..a3f33a1c40eb --- /dev/null +++ b/localization/ar/api-gateway/README.md @@ -0,0 +1,170 @@ +--- +title: API Gateway +shortTitle: API Gateway +category: Architectural +language: ar +tag: + - Cloud distributed + - Decoupling + - Microservices +--- + +## الهدف + +إضافة استدعاءات إلى الخدمات الصغيرة في نفس المكان، بوابة API (API Gateway). يقوم المستخدم +بإجراء استدعاء بسيط إلى بوابة API، وتقوم بوابة API بدورها باستدعاء كل خدمة صغيرة ذات صلة. + +## الشرح + +مع نمط الخدمات الصغيرة، قد يحتاج العميل إلى بيانات من عدة خدمات صغيرة. إذا كان +العميل سيستدعي كل خدمة صغيرة بشكل مباشر، فقد يؤدي ذلك إلى أوقات تحميل طويلة، حيث +يجب على العميل إجراء طلب شبكة لكل خدمة صغيرة يتم استدعاؤها. بالإضافة إلى ذلك، وجود +استدعاء العميل لكل خدمة صغيرة يربط العميل مباشرة بتلك الخدمة الصغيرة - إذا حدث تغيير داخلي +في الخدمات الصغيرة (على سبيل المثال، إذا تم دمج خدمتين صغيرتين في وقت ما في المستقبل) أو إذا تغير الموقع (الخادم والمنفذ) لأحد الخدمات الصغيرة، فإن كل +عميل يستخدم تلك الخدمات الصغيرة يجب تحديثه. + +الهدف من نمط بوابة API هو تخفيف بعض هذه المشاكل. في نمط بوابة API، يتم وضع كيان إضافي +(بوابة API) بين العميل والخدمات الصغيرة. +مهمة بوابة API هي إضافة استدعاءات إلى الخدمات الصغيرة. بدلاً من أن يقوم العميل +بإجراء استدعاء لكل خدمة صغيرة على حدة، يقوم العميل باستدعاء بوابة API مرة واحدة فقط. ثم تقوم بوابة +API باستدعاء كل واحدة من الخدمات الصغيرة التي يحتاجها العميل. + +مثال واقعي + +> نحن نطبق نظام خدمات صغيرة وبوابة API لموقع تجارة إلكترونية. في هذا +> النظام تقوم بوابة API بإجراء استدعاءات إلى خدمات Image و Price. (الصورة والسعر) + +بمعنى آخر + +> في نظام يتم تنفيذه باستخدام بنية خدمات صغيرة، تعتبر بوابة API هي النقطة +> الوحيدة للوصول التي تجمع استدعاءات الخدمات الصغيرة الفردية. + +تقول ويكيبيديا + +> بوابة API هي خادم يعمل كواجهة أمامية لـ API، يستقبل طلبات API، ويطبق +> حدودًا وسياسات الأمان، ويرسل الطلبات إلى الخدمة الخلفية ثم يعيد +> الاستجابة إلى الطالب. غالبًا ما تشمل بوابة API محركًا للتحويل لتنظيم +> وتعديل الطلبات والاستجابات أثناء سير العملية. يمكن أن توفر بوابة +> API أيضًا وظائف مثل جمع تحليلات البيانات والتخزين المؤقت. قد +> توفر بوابة API وظائف لدعم المصادقة، والتفويض، والأمان، +> والتدقيق، والامتثال. + +**كود المثال** + +يوضح هذا التنفيذ كيف قد يبدو نمط بوابة API لموقع تجارة إلكترونية. تقوم +`ApiGateway` بإجراء استدعاءات إلى خدمات Image و Price باستخدام +`ImageClientImpl` و `PriceClientImpl` على التوالي. العملاء الذين يشاهدون الموقع +على جهاز مكتبي يمكنهم مشاهدة معلومات الأسعار وصورة المنتج، لذلك تقوم `ApiGateway` +بإجراء استدعاء إلى الخدمات الصغيرة وجمع البيانات في نموذج `DesktopProduct`. +ومع ذلك، فإن مستخدمي الأجهزة المحمولة يرون فقط معلومات الأسعار، ولا يشاهدون صورة المنتج. +بالنسبة لمستخدمي الأجهزة المحمولة، تقوم `ApiGateway` فقط +بالحصول على معلومات الأسعار، والتي تستخدم لإكمال `MobileProduct`. + +إليك تنفيذ خدمة الصورة (Image). + + +```java +public interface ImageClient { + String getImagePath(); +} + +public class ImageClientImpl implements ImageClient { + @Override + public String getImagePath() { + var httpClient = HttpClient.newHttpClient(); + var httpGet = HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:50005/image-path")) + .build(); + + try { + var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + return httpResponse.body(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + return null; + } +} +``` + +إليك تنفيذ خدمة المصغرة للسعر (Price). + +```java +public interface PriceClient { + String getPrice(); +} + +public class PriceClientImpl implements PriceClient { + + @Override + public String getPrice() { + var httpClient = HttpClient.newHttpClient(); + var httpGet = HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:50006/price")) + .build(); + + try { + var httpResponse = httpClient.send(httpGet, BodyHandlers.ofString()); + return httpResponse.body(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + return null; + } +} +``` + +هنا يمكننا أن نرى كيف تقوم بوابة الـ API بتوجيه الطلبات إلى الخدمات المصغرة. + +```java +public class ApiGateway { + + @Resource + private ImageClient imageClient; + + @Resource + private PriceClient priceClient; + + @RequestMapping(path = "/desktop", method = RequestMethod.GET) + public DesktopProduct getProductDesktop() { + var desktopProduct = new DesktopProduct(); + desktopProduct.setImagePath(imageClient.getImagePath()); + desktopProduct.setPrice(priceClient.getPrice()); + return desktopProduct; + } + + @RequestMapping(path = "/mobile", method = RequestMethod.GET) + public MobileProduct getProductMobile() { + var mobileProduct = new MobileProduct(); + mobileProduct.setPrice(priceClient.getPrice()); + return mobileProduct; + } +} +``` + +## مخطط الفئة + +![alt text](./etc/api-gateway.png "API Gateway") + +## التطبيقات + +استخدم نمط API Gateway عندما + +* تكون تستخدم بنية ميكروسيرفيسز وتحتاج إلى نقطة تجميع واحدة لاستدعاءات الميكروسيرفيسز. + +## الدروس التعليمية + +* [Exploring the New Spring Cloud Gateway](https://www.baeldung.com/spring-cloud-gateway) +* [Spring Cloud - Gateway](https://www.tutorialspoint.com/spring_cloud/spring_cloud_gateway.htm) +* [Getting Started With Spring Cloud Gateway](https://dzone.com/articles/getting-started-with-spring-cloud-gateway) + +## الشكر + +* [microservices.io - API Gateway](http://microservices.io/patterns/apigateway.html) +* [NGINX - Building Microservices: Using an API Gateway](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/) +* [Microservices Patterns: With examples in Java](https://www.amazon.com/gp/product/1617294543/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617294543&linkId=ac7b6a57f866ac006a309d9086e8cfbd) +* [Building Microservices: Designing Fine-Grained Systems](https://www.amazon.com/gp/product/1491950358/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1491950358&linkId=4c95ca9831e05e3f0dadb08841d77bf1) diff --git a/localization/ar/api-gateway/etc/api-gateway.png b/localization/ar/api-gateway/etc/api-gateway.png new file mode 100644 index 000000000000..bb3ec2e2e1a8 Binary files /dev/null and b/localization/ar/api-gateway/etc/api-gateway.png differ diff --git a/localization/ar/arrange-act-assert/README.md b/localization/ar/arrange-act-assert/README.md new file mode 100644 index 000000000000..ed99bb686a7f --- /dev/null +++ b/localization/ar/arrange-act-assert/README.md @@ -0,0 +1,142 @@ +--- +title: Arrange/Act/Assert +shortTitle: Arrange/Act/Assert +category: Idiom +language: ar +tag: + - Testing +--- + +## أيضا معروف بـ + +Dado/Cuando/Entonces + +## الغرض + +Arrange/Act/Assert (AAA) هو نمط لتنظيم اختبارات الوحدة. +يقسم اختبارات الوحدة إلى ثلاث خطوات واضحة ومميزة: + +1. Arrange(تنظيم): قم بإعداد التهيئة والتهيئة اللازمة للاختبار. +2. Act(العمل): اتخذ التدابير اللازمة للاختبار. +3. Assert(التأكيد): تحقق من نتائج الاختبار. + +## الشرح + +لهذا النمط العديد من الفوائد الكبيرة. فهو يخلق فصلًا واضحًا بين إعدادات الاختبار، العمليات والنتائج. هذا الهيكل يجعل الكود أسهل في القراءة والفهم. إذا +وضعت الخطوات بالترتيب وصيغت الكود بطريقة تفصلها، يمكنك مسح الاختبار وفهمه بسرعة. + +كما أنه يفرض درجة معينة من الانضباط عند كتابة اختبارات الوحدة. يجب أن تكون قادرًا على تصور +بوضوح الخطوات الثلاث التي سيقوم بها اختبارك. هذا يجعل الاختبارات أكثر بديهية للكتابة مع الحفاظ على هيكل واضح. + +مثال يومي + +> نحتاج إلى كتابة مجموعة من اختبارات الوحدة كاملة وواضحة لفئة. + +بعبارة أخرى + +> Arrange/Act/Assert هو نمط اختبار ينظم الاختبارات إلى ثلاث خطوات واضحة لتسهيل +> الصيانة. + +WikiWikiWeb يقول + +> Arrange/Act/Assert هو نمط لتنظيم وتنسيق الكود في طرق اختبار الوحدة. + +**كود المثال** + +لنلقِ نظرة أولاً على فئتنا `Cash` التي سيتم اختبارها. + + +```java +public class Cash { + + private int amount; + + Cash(int amount) { + this.amount = amount; + } + + void plus(int addend) { + amount += addend; + } + + boolean minus(int subtrahend) { + if (amount >= subtrahend) { + amount -= subtrahend; + return true; + } else { + return false; + } + } + + int count() { + return amount; + } +} +``` + +ثم نكتب اختبارات الوحدة الخاصة بنا بناءً على نمط Arrange/Act/Assert. لاحظ بوضوح الفصل بين الخطوات لكل اختبار وحدة. + + +```java +class CashAAATest { + + @Test + void testPlus() { + //Arrange + var cash = new Cash(3); + //Act + cash.plus(4); + //Assert + assertEquals(7, cash.count()); + } + + @Test + void testMinus() { + //Arrange + var cash = new Cash(8); + //Act + var result = cash.minus(5); + //Assert + assertTrue(result); + assertEquals(3, cash.count()); + } + + @Test + void testInsufficientMinus() { + //Arrange + var cash = new Cash(1); + //Act + var result = cash.minus(6); + //Assert + assertFalse(result); + assertEquals(1, cash.count()); + } + + @Test + void testUpdate() { + //Arrange + var cash = new Cash(5); + //Act + cash.plus(6); + var result = cash.minus(3); + //Assert + assertTrue(result); + assertEquals(8, cash.count()); + } +} +``` + +## القابلية للتطبيق + +استخدم نمط Arrange/Act/Assert عندما + +* تحتاج إلى تنظيم اختبارات الوحدة الخاصة بك لتكون أسهل في القراءة والصيانة والتحسين. + +## الشكر + +* [Arrange, Act, Assert: ¿Qué son las pruebas AAA?](https://blog.ncrunch.net/post/arrange-act-assert-aaa-testing.aspx) +* [Bill Wake: 3A – Arrange, Act, Assert](https://xp123.com/articles/3a-arrange-act-assert/) +* [Martin Fowler: DadoCuandoEntonces](https://martinfowler.com/bliki/GivenWhenThen.html) +* [Patrones de prueba xUnit: Refactorizando Código de prueba](https://www.amazon.com/gp/product/0131495054/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0131495054&linkId=99701e8f4af2f63d0bcf50) +* [Principios, prácticas y patrones UnitTesting](https://www.amazon.com/gp/product/1617296279/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1617296279&linkId=74c75cfae3a5aaccae3a5a) +* [Desarrollo basado en pruebas: Ejemplo](https://www.amazon.com/gp/product/0321146530/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0321146530&linkId=5c63a93d8c1175b47caef50875) diff --git a/localization/ar/async-method-invocation/README.md b/localization/ar/async-method-invocation/README.md new file mode 100644 index 000000000000..05173d7d6104 --- /dev/null +++ b/localization/ar/async-method-invocation/README.md @@ -0,0 +1,178 @@ +--- +title: Async Method Invocation +shortTitle: Async Method Invocation +category: Concurrency +language: ar +tag: + - Reactive +--- + +## الغرض + +الاستدعاء غير المتزامن للطرق (invocación de método asincrónico) هو نمط حيث لا يتم حظر الخيط أو العملية التي تستدعي الطريقة أثناء انتظار النتائج. يوفر النمط معالجة متوازية للمهام المستقلة المتعددة ويسترجع النتائج عبر +التعريفات الراجعة (callbacks) أو بالانتظار حتى ينتهي الإجراء. + +## الشرح + +مثال يومي + +> إطلاق الصواريخ الفضائية هو عمل مثير. يعطي قائد المهمة أمر الإطلاق و +> بعد فترة غير محددة من الوقت، يتم إطلاق الصاروخ بنجاح أو يفشل بشكل كارثي. + +بعبارة أخرى + +> يستدعي الاستدعاء غير المتزامن للطرق الإجراء ويعود فورًا قبل أن تنتهي المهمة +> يتم إرجاع نتائج الإجراء لاحقًا إلى الاستدعاء (callback). + +حسب ويكيبيديا + +> في البرمجة متعددة العمليات، يعتبر استدعاء الطريقة غير المتزامن (AMI)، المعروف أيضًا بـ +> الاستدعاءات غير المتزامنة أو النمط غير المتزامن هو نمط تصميم حيث لا يتم حظر مكان الاستدعاء +> أثناء انتظار انتهاء الكود المستدعى. بدلاً من ذلك، يتم إخطار خيط الاستدعاء عندما تصل الاستجابة. الاستطلاع للحصول على إجابة هو خيار غير مرغوب فيه. + +**مثال برمجي** + +في هذا المثال، نحن نطلق صواريخ فضائية وننشر مركبات قمرية. + +تُظهر التطبيق ما يفعله نمط الاستدعاء غير المتزامن للطريقة. الأجزاء الرئيسية للنمط هي +`AsyncResult` الذي يعد حاوية وسيطة لقيمة تم تقييمها بشكل غير متزامن، +`AsyncCallback` الذي يمكن توفيره ليتم تنفيذه عند اكتمال المهمة و `AsyncExecutor` الذي +يدير تنفيذ المهام غير المتزامنة. + + +```java +public interface AsyncResult { + boolean isCompleted(); + T getValue() throws ExecutionException; + void await() throws InterruptedException; +} +``` + +```java +public interface AsyncCallback { + void onComplete(T value, Optional ex); +} +``` + +```java +public interface AsyncExecutor { + AsyncResult startProcess(Callable task); + AsyncResult startProcess(Callable task, AsyncCallback callback); + T endProcess(AsyncResult asyncResult) throws ExecutionException, InterruptedException; +} +``` + +`ThreadAsyncExecutor` هو تنفيذ لـ `AsyncExecutor`. يتم تسليط الضوء على بعض من أجزائه الرئيسية أدناه. + + +```java +public class ThreadAsyncExecutor implements AsyncExecutor { + + @Override + public AsyncResult startProcess(Callable task) { + return startProcess(task, null); + } + + @Override + public AsyncResult startProcess(Callable task, AsyncCallback callback) { + var result = new CompletableResult<>(callback); + new Thread( + () -> { + try { + result.setValue(task.call()); + } catch (Exception ex) { + result.setException(ex); + } + }, + "executor-" + idx.incrementAndGet()) + .start(); + return result; + } + + @Override + public T endProcess(AsyncResult asyncResult) + throws ExecutionException, InterruptedException { + if (!asyncResult.isCompleted()) { + asyncResult.await(); + } + return asyncResult.getValue(); + } +} +``` + +الآن كل شيء جاهز لإطلاق بعض الصواريخ لرؤية كيف يعمل كل شيء. + + +```java +public static void main(String[] args) throws Exception { + // الآن كل شيء جاهز لإطلاق بعض الصواريخ لرؤية كيف يعمل كل شيء. + + var executor = new ThreadAsyncExecutor(); + + // يبدأ بعض المهام غير المتزامنة بأوقات معالجة مختلفة، اثنان من الأخيرين مع محركات استرجاع (callback handlers) + + 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("Desplegando el rover lunar")); + final var asyncResult5 = + executor.startProcess(lazyval("devolución de llamada callback", 600), callback("Desplegando el rover lunar")); + + // يحاكي المعالجة في الخيط أو العملية الحالية بينما يتم تنفيذ المهام غير المتزامنة في خيوط أو عملياتها الخاصة + + Subproceso.dormir(350); // هيه، نحن نعمل بجد هنا + + log("قائد المهمة يشرب القهوة +"); + + // ينتظر حتى اكتمال المهام + + final var result1 = executor.endProcess(asyncResult1); + final var result2 = executor.endProcess(asyncResult2); + final var result3 = executor.endProcess(asyncResult3); + asyncResult4.await(); + asyncResult5.await(); + +// يسجل نتائج المهام، يتم تسجيل الاسترجاعات فور اكتمالها +log("الصاروخ الفضائي <" + resultado1 + "> أكمل إطلاقه"); +log("الصاروخ الفضائي <" + resultado2 + "> أكمل إطلاقه"); +log("الصاروخ الفضائي <" + result3 + "> أكمل إطلاقه"); + +} +``` + +Here is the output from the program console. + +```java +21:47:08.227 [executor-2] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي تم إطلاقه بنجاح +21:47:08.269 [main] INFO com.iluwatar.async.method.invocation.App - قائد المهمة يشرب القهوة +21:47:08.318 [executor-4] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي <20> تم إطلاقه بنجاح +21:47:08.335 [executor-4] INFO com.iluwatar.async.method.invocation.App - نشر الروفر القمري <20> +21:47:08.414 [executor-1] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي <10> تم إطلاقه بنجاح +21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي تم إطلاقه بنجاح +21:47:08.519 [executor-5] INFO com.iluwatar.async.method.invocation.App - تنفيذ المركبة القمرية +21:47:08.616 [executor-3] INFO com.iluwatar.async.method.invocation.App - الصاروخ الفضائي <50> تم إطلاقه بنجاح +21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - إطلاق الصاروخ الفضائي <10> تم +21:47:08.617 [main] INFO com.iluwatar.async.method.invocation.App - إطلاق الصاروخ الفضائي تم +21:47:08.618 [main] INFO com.iluwatar.async.method.invocation.App - إطلاق الصاروخ الفضائي <50> تم + +``` + +# مخطط الفئة + +![texto alternativo](./etc/async-method-invocation.png "Invocación de método asíncrono") + +## القابلية للتطبيق + +استخدم نمط استدعاء الطريقة غير المتزامنة عندما + +* يكون لديك مهام مستقلة متعددة يمكن تنفيذها بشكل متوازٍ +* تحتاج إلى تحسين أداء مجموعة من المهام المتسلسلة +* لديك قدرة معالجة محدودة أو مهام تنفيذ طويلة الأمد ولا ينبغي أن تنتظر الدعوة حتى تصبح المهام جاهزة + +## أمثلة يومية + +* [FutureTask](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/FutureTask.html) +* [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html) +* [ExecutorService](http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html) +* [Patrón asíncrono basado en tareas](https://msdn.microsoft.com/en-us/library/hh873175.aspx) diff --git a/localization/ar/async-method-invocation/etc/async-method-invocation.png b/localization/ar/async-method-invocation/etc/async-method-invocation.png new file mode 100644 index 000000000000..764895d7a217 Binary files /dev/null and b/localization/ar/async-method-invocation/etc/async-method-invocation.png differ diff --git a/localization/ar/balking/README.md b/localization/ar/balking/README.md new file mode 100644 index 000000000000..c43480d580bd --- /dev/null +++ b/localization/ar/balking/README.md @@ -0,0 +1,135 @@ +--- +title: Balking +shortTitle: Balking +category: Concurrency +language: ar +tag: + - Decoupling +--- + +## الغرض + +يتم استخدام نمط _Balking_ لمنع كائن من تنفيذ كود معين إذا كان في حالة غير مكتملة أو غير مناسبة. + +## الشرح + +مثال من الحياة الواقعية + +> في غسالة الملابس هناك زر بدء لتشغيل غسل الملابس. عندما تكون الغسالة غير نشطة، يعمل الزر كما هو متوقع، ولكن إذا كانت الغسالة تغسل بالفعل، فإن الزر لا يفعل شيئًا. + +بمعنى آخر + +> باستخدام نمط _Balking_، يتم تنفيذ كود معين فقط إذا كان الكائن في حالة معينة. + +تقول ويكيبيديا + +> نمط _Balking_ هو نمط تصميم برمجي ينفذ إجراء على كائن فقط عندما يكون الكائن في حالة معينة. على سبيل المثال، إذا كان الكائن يقرأ ملفات ZIP واستدعى أسلوب _get_ على الكائن عندما لا يكون الملف ZIP مفتوحًا، فإن الكائن "يرفض" (_balk_) الطلب. + +**مثال برمجي** + +في هذا المثال من التنفيذ، `WashingMachine` هو كائن له حالتان يمكن أن يكونا: _ENABLED_ و _WASHING_ (مفعل و يغسل على التوالي). إذا كانت الغسالة في حالة _ENABLED_، فإن الحالة تتغير إلى _WASHING_ باستخدام طريقة آمنة ضد الخيوط (thread-safe). من ناحية أخرى، إذا كانت الغسالة بالفعل تغسل وأي خيط آخر ينفذ `wash()`، فلن يتم تغيير الحالة وتنتهي تنفيذ الطريقة دون القيام بأي شيء. + +إليك الأجزاء ذات الصلة من فئة `WashingMachine`. + + +```java +@Slf4j +public class WashingMachine { + + private final DelayProvider delayProvider; + private WashingMachineState washingMachineState; + + public WashingMachine(DelayProvider delayProvider) { + this.delayProvider = delayProvider; + this.washingMachineState = WashingMachineState.ENABLED; + } + + public WashingMachineState getWashingMachineState() { + return washingMachineState; + } + + public void wash() { + synchronized (this) { + var machineState = getWashingMachineState(); + LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState); + if (this.washingMachineState == WashingMachineState.WASHING) { + LOGGER.error("Cannot wash if the machine has been already washing!"); + return; + } + this.washingMachineState = WashingMachineState.WASHING; + } + LOGGER.info("{}: Doing the washing", Thread.currentThread().getName()); + this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing); + } + + public synchronized void endOfWashing() { + washingMachineState = WashingMachineState.ENABLED; + LOGGER.info("{}: Washing completed.", Thread.currentThread().getId()); + } +} +``` + +هنا الواجهة البسيطة `DelayProvider` المستخدمة من قبل `WashingMachine`. + + +```java +public interface DelayProvider { + void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task); +} +``` + +الآن نقدم التطبيق باستخدام `WashingMachine`. + + +```java +public static void main(String... args) { + final var washingMachine = new WashingMachine(); + var executorService = Executors.newFixedThreadPool(3); + for (int i = 0; i < 3; i++) { + executorService.execute(washingMachine::wash); + } + executorService.shutdown(); + try { + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ie) { + LOGGER.error("ERROR: Waiting on executor service shutdown!"); + Thread.currentThread().interrupt(); + } +} +``` + +إليك مخرجات التطبيق في وحدة التحكم. + + +``` +14:02:52.268 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Actual machine state: ENABLED +14:02:52.272 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Doing the washing +14:02:52.272 [pool-1-thread-3] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-3: Actual machine state: WASHING +14:02:52.273 [pool-1-thread-3] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing! +14:02:52.273 [pool-1-thread-1] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-1: Actual machine state: WASHING +14:02:52.273 [pool-1-thread-1] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing! +14:02:52.324 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - 14: Washing completed. +``` + +## مخطط الفئات + + +![alt text](./etc/balking.png "Balking") + +## القابلية للتطبيق + +استخدم نمط _Balking_ عندما + +* يجب على كائن تنفيذ كود معين فقط عندما يكون في حالة معينة. +* الكائنات في حالة معرضة للتوقف مؤقتًا، ولكن لفترة زمنية غير محددة. + +## الأنماط ذات الصلة + + +* [Guarded Suspension Pattern](https://java-design-patterns.com/patterns/guarded-suspension/) +* [Double Checked Locking Pattern](https://java-design-patterns.com/patterns/double-checked-locking/) + +## المراجع + + +* [Patterns in Java: A Catalog of Reusable Design Patterns Illustrated with UML, 2nd Edition, Volume 1](https://www.amazon.com/gp/product/0471227293/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0471227293&linkId=0e39a59ffaab93fb476036fecb637b99) diff --git a/localization/ar/balking/etc/balking.png b/localization/ar/balking/etc/balking.png new file mode 100644 index 000000000000..f409eaacbb95 Binary files /dev/null and b/localization/ar/balking/etc/balking.png differ diff --git a/localization/ar/bridge/README.md b/localization/ar/bridge/README.md new file mode 100644 index 000000000000..2c47f65ea747 --- /dev/null +++ b/localization/ar/bridge/README.md @@ -0,0 +1,216 @@ +--- +title: Bridge +shortTitle: Bridge +category: Structural +language: ar +tag: + - Gang of Four +--- + +## أيضًا معروف باسم + +Handle/Body + +## الهدف + +فصل التجريد عن تنفيذه بحيث يمكن لكل منهما التغيير بشكل مستقل. + +## الشرح + +مثال من الحياة الواقعية + +> تخيل أن لديك سلاحًا مع تعاويذ مختلفة، ومن المفترض أن تسمح بخلط أسلحة مختلفة مع تعاويذ مختلفة. ماذا ستفعل؟ هل ستقوم بإنشاء نسخ متعددة من كل سلاح لكل تعويذة من التعاويذ أو ببساطة تقوم بإنشاء تعويذة منفصلة وتحددها للسلاح حسب الحاجة؟ نمط Bridge يتيح لك القيام بالأمر الثاني. + +ببساطة + +> يتعلق نمط Bridge بتفضيل التركيب على الوراثة. يتم دفع تفاصيل التنفيذ من هرم إلى كائن آخر مع هرم منفصل. + +تقول ويكيبيديا + +> نمط Bridge هو نمط تصميم يستخدم في هندسة البرمجيات يهدف إلى "فصل التجريد عن تنفيذه بحيث يمكن لكل منهما التغيير بشكل مستقل." + +**مثال برمجي** + +نقلًا لمثال السلاح المذكور أعلاه. هنا لدينا واجهة السلاح `Weapon`: + + +```java +public interface Weapon { + void wield(); + void swing(); + void unwield(); + Enchantment getEnchantment(); +} + +public class Sword implements Weapon { + + private final Enchantment enchantment; + + public Sword(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @Override + public void wield() { + LOGGER.info("The sword is wielded."); + enchantment.onActivate(); + } + + @Override + public void swing() { + LOGGER.info("The sword is swinged."); + enchantment.apply(); + } + + @Override + public void unwield() { + LOGGER.info("The sword is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } +} + +public class Hammer implements Weapon { + + private final Enchantment enchantment; + + public Hammer(Enchantment enchantment) { + this.enchantment = enchantment; + } + + @Override + public void wield() { + LOGGER.info("The hammer is wielded."); + enchantment.onActivate(); + } + + @Override + public void swing() { + LOGGER.info("The hammer is swinged."); + enchantment.apply(); + } + + @Override + public void unwield() { + LOGGER.info("The hammer is unwielded."); + enchantment.onDeactivate(); + } + + @Override + public Enchantment getEnchantment() { + return enchantment; + } +} +``` + +إليك واجهة التعاويذ `Enchantment` المنفصلة: + + +```java +public interface Enchantment { + void onActivate(); + void apply(); + void onDeactivate(); +} + +public class FlyingEnchantment implements Enchantment { + + @Override + public void onActivate() { + LOGGER.info("The item begins to glow faintly."); + } + + @Override + public void apply() { + LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand."); + } + + @Override + public void onDeactivate() { + LOGGER.info("The item's glow fades."); + } +} + +public class SoulEatingEnchantment implements Enchantment { + + @Override + public void onActivate() { + LOGGER.info("The item spreads bloodlust."); + } + + @Override + public void apply() { + LOGGER.info("The item eats the soul of enemies."); + } + + @Override + public void onDeactivate() { + LOGGER.info("Bloodlust slowly disappears."); + } +} +``` + +إليك كلا الواجهتين في العمل: + + +```java +LOGGER.info("The knight receives an enchanted sword."); +var enchantedSword = new Sword(new SoulEatingEnchantment()); +enchantedSword.wield(); +enchantedSword.swing(); +enchantedSword.unwield(); + +LOGGER.info("The valkyrie receives an enchanted hammer."); +var hammer = new Hammer(new FlyingEnchantment()); +hammer.wield(); +hammer.swing(); +hammer.unwield(); +``` + +إليك مخرجات التطبيق في وحدة التحكم. + + +``` +The knight receives an enchanted sword. +The sword is wielded. +The item spreads bloodlust. +The sword is swung. +The item eats the soul of enemies. +The sword is unwielded. +Bloodlust slowly disappears. +The valkyrie receives an enchanted hammer. +The hammer is wielded. +The item begins to glow faintly. +The hammer is swung. +The item flies and strikes the enemies finally returning to owner's hand. +The hammer is unwielded. +The item's glow fades. +``` + +## مخطط الفئات + + +![alt text](./etc/bridge.urm.png "Bridge diagrama de clases") + +## القابلية للتطبيق + +استخدم نمط Bridge عندما + +* ترغب في تجنب الربط الدائم بين التجريد وتنفيذه. قد يكون هذا هو الحال، على سبيل المثال، عندما يجب اختيار أو تغيير التنفيذ في وقت التشغيل. +* يجب أن تكون كل من التجريدات وتنفيذاتها قابلة للتوسيع عبر الوراثة. في هذه الحالة، يتيح لك نمط Bridge دمج التجريدات والتنفيذات المختلفة وتوسيعها بشكل مستقل. +* يجب ألا تؤثر التغييرات في تنفيذ التجريد على العملاء؛ أي أنه لا يجب أن يحتاج الكود الخاص بهم إلى إعادة تجميع. +* لديك تكاثر في الفئات. تشير مثل هذه الهرميات إلى الحاجة إلى تقسيم كائن إلى جزئين. يستخدم Rumbaugh مصطلح "التعميمات المتداخلة" للإشارة إلى مثل هذه الهرميات من الفئات. +* ترغب في مشاركة تنفيذ بين عدة كائنات (ربما باستخدام عد مرجعي)، ويجب إخفاء هذه الحقيقة عن العميل. مثال بسيط هو فئة String لـ Coplien، حيث يمكن لعدة كائنات مشاركة نفس تمثيل السلسلة. + +## الدروس التعليمية + +* [Bridge Pattern Tutorial](https://www.journaldev.com/1491/bridge-design-pattern-java) + +## الاعتمادات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/ar/bridge/etc/bridge.urm.png b/localization/ar/bridge/etc/bridge.urm.png new file mode 100644 index 000000000000..785585bf8163 Binary files /dev/null and b/localization/ar/bridge/etc/bridge.urm.png differ diff --git a/localization/ar/builder/README.md b/localization/ar/builder/README.md new file mode 100644 index 000000000000..1edfb4b826db --- /dev/null +++ b/localization/ar/builder/README.md @@ -0,0 +1,147 @@ +--- +title: Builder +shortTitle: Builder +category: Creational +language: ar +tag: + - Gang of Four +--- + +## الهدف + +فصل بناء كائن معقد عن تمثيله بحيث يمكن لنفس عملية البناء إنشاء تمثيلات مختلفة. + +## الشرح + +مثال من الحياة الواقعية + +> تخيل مولد شخصيات للعبة تقمص أدوار. الخيار الأسهل هو السماح للكمبيوتر بإنشاء الشخصية نيابة عنك. إذا أردت تحديد تفاصيل الشخصية يدويًا مثل المهنة، الجنس، لون الشعر، إلخ، فإن إنشاء الشخصية يصبح عملية خطوة بخطوة تكتمل عندما تكون جميع الاختيارات جاهزة. + +ببساطة + +> يسمح بإنشاء نكهات مختلفة من كائن دون تلويث الباني. مفيد عندما يمكن أن يكون هناك عدة نكهات لكائن ما، أو عندما تكون هناك العديد من الخطوات المعنية في إنشاء الكائن. + +تقول ويكيبيديا + +> نمط البناء هو نمط تصميم برمجي لإنشاء الكائنات بهدف إيجاد حل لمضاد النمط الخاص بالباني المنطقي. + +مع ذلك، دعني أضيف بعض المعلومات حول ما هو مضاد النمط للباني المنطقي. في وقت ما أو آخر، رأينا جميعًا بانيًا مثل التالي: + + +```java +public Hero(Profession profession, String name, HairType hairType, HairColor hairColor, Armor armor, Weapon weapon) { +} +``` + +كما ترى، قد يخرج عدد معلمات الباني عن السيطرة بسرعة، وقد يصبح من الصعب فهم ترتيب المعلمات. بالإضافة إلى ذلك، قد تستمر هذه القائمة في النمو إذا أردت إضافة المزيد من الخيارات في المستقبل. يسمى هذا مضاد النمط للباني المنطقي. + +**مثال برمجي** + +البديل الحكيم هو استخدام نمط Builder. أولاً، لدينا بطلنا `Hero` الذي نريد إنشائه: + + +```java +public final class Hero { + private final Profession profession; + private final String name; + private final HairType hairType; + private final HairColor hairColor; + private final Armor armor; + private final Weapon weapon; + + private Hero(Builder builder) { + this.profession = builder.profession; + this.name = builder.name; + this.hairColor = builder.hairColor; + this.hairType = builder.hairType; + this.weapon = builder.weapon; + this.armor = builder.armor; + } +} +``` + +ثم لدينا الباني: + + +```java + public static class Builder { + private final Profession profession; + private final String name; + private HairType hairType; + private HairColor hairColor; + private Armor armor; + private Weapon weapon; + + public Builder(Profession profession, String name) { + if (profession == null || name == null) { + throw new IllegalArgumentException("profession and name can not be null"); + } + this.profession = profession; + this.name = name; + } + + public Builder withHairType(HairType hairType) { + this.hairType = hairType; + return this; + } + + public Builder withHairColor(HairColor hairColor) { + this.hairColor = hairColor; + return this; + } + + public Builder withArmor(Armor armor) { + this.armor = armor; + return this; + } + + public Builder withWeapon(Weapon weapon) { + this.weapon = weapon; + return this; + } + + public Hero build() { + return new Hero(this); + } + } +``` + +إذن يمكن استخدامه كما يلي: + + +```java +var mage = new Hero.Builder(Profession.MAGE, "Riobard").withHairColor(HairColor.BLACK).withWeapon(Weapon.DAGGER).build(); +``` + +## مخطط الفئات + +![alt text](./etc/builder.urm.png "مخطط فئات Builder") + +## القابلية للتطبيق + +استخدم نمط Builder عندما + +* يجب أن يكون الخوارزمية لإنشاء كائن معقدة مستقلة عن الأجزاء التي تتكون منها الكائن وكيفية تجميعها. +* يجب أن تسمح عملية البناء بتمثيلات مختلفة للكائن الذي يتم بناؤه. + +## الدروس التعليمية + +* [Refactoring Guru](https://refactoring.guru/design-patterns/builder) +* [مدونة Oracle](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java) +* [Journal Dev](https://www.journaldev.com/1425/builder-design-pattern-in-java) + +## الاستخدامات في العالم الواقعي + +* [java.lang.StringBuilder](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuilder.html) +* [java.nio.ByteBuffer](http://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html#put-byte-) بالإضافة إلى غيرها من المخازن المؤقتة مثل FloatBuffer و IntBuffer، إلخ. +* [java.lang.StringBuffer](http://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html#append-boolean-) +* جميع التطبيقات من [java.lang.Appendable](http://docs.oracle.com/javase/8/docs/api/java/lang/Appendable.html) +* [بناة Apache Camel](https://github.com/apache/camel/tree/0e195428ee04531be27a0b659005e3aa8d159d23/camel-core/src/main/java/org/apache/camel/builder) +* [Apache Commons Option.Builder](https://commons.apache.org/proper/commons-cli/apidocs/org/apache/commons/cli/Option.Builder.html) + +## الاعتمادات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) diff --git a/localization/ar/builder/etc/builder.urm.png b/localization/ar/builder/etc/builder.urm.png new file mode 100644 index 000000000000..d77808d36097 Binary files /dev/null and b/localization/ar/builder/etc/builder.urm.png differ diff --git a/localization/ar/business-delegate/README.md b/localization/ar/business-delegate/README.md new file mode 100644 index 000000000000..13fe05b32db3 --- /dev/null +++ b/localization/ar/business-delegate/README.md @@ -0,0 +1,194 @@ +--- +title: Business Delegate +shortTitle: Business Delegate +category: Structural +language: ar +tag: + - Decoupling +--- + +## الغرض + +نمط **Business Delegate** يضيف طبقة من التجريد بين مستويات العرض والأعمال. باستخدام هذا النمط، نحقق ارتباطًا مرنًا بين المستويات ونعزل المعرفة حول كيفية تحديد المواقع والاتصال والتفاعل مع الكائنات التجارية التي تشكل التطبيق. + +## أيضًا معروف باسم + +مُمثل الخدمة + +## الشرح + +مثال من العالم الحقيقي + +> تطبيق للهواتف المحمولة يعد ببث أي فيلم موجود إلى جهازك. يقوم التطبيق بالتقاط سلسلة البحث من المستخدم ويمررها إلى **Business Delegate**. يقوم **Business Delegate** باختيار خدمة البث الأكثر ملاءمة ويبدأ في تشغيل الفيديو. + +بكلمات أبسط + +> يضيف **Business Delegate** طبقة من التجريد بين مستويات العرض والأعمال. + +تقول ويكيبيديا + +> **Business Delegate** هو نمط تصميم في Java EE. هذا النمط يهدف إلى تقليل الترابط بين خدمات الأعمال ومستوى العرض المتصل، وإخفاء تفاصيل التنفيذ الخاصة بالخدمات (بما في ذلك البحث والوصول إلى بنية EJB). يعمل **Business Delegate** كـ **مهايئ** لاستدعاء كائنات الأعمال من طبقة العرض. + +**مثال برمجي** + +أولاً، لدينا تجريد لخدمات البث عبر الفيديو `VideoStreamingService` مع زوج من التطبيقات `NetflixService` و `YouTubeService`. + + +```java +public interface VideoStreamingService { + void doProcessing(); +} + +@Slf4j +public class NetflixService implements VideoStreamingService { + @Override + public void doProcessing() { + LOGGER.info("NetflixService is now processing"); + } +} + +@Slf4j +public class YouTubeService implements VideoStreamingService { + @Override + public void doProcessing() { + LOGGER.info("YouTubeService is now processing"); + } +} +``` + +التالي، لدينا خدمة البحث `BusinessLookup` التي تقرر أي خدمة بث الفيديو يجب استخدامها. + + +```java + +@Setter +public class BusinessLookup { + + private NetflixService netflixService; + private YouTubeService youTubeService; + + public VideoStreamingService getBusinessService(String movie) { + if (movie.toLowerCase(Locale.ROOT).contains("die hard")) { + return netflixService; + } else { + return youTubeService; + } + } +} +``` + +يستخدم **Delegado de Negocio** `BusinessDelegate` بحث الأعمال لتوجيه طلبات تشغيل الأفلام إلى خدمة بث الفيديو المناسبة. + + +```java + +@Setter +public class BusinessDelegate { + + private BusinessLookup lookupService; + + public void playbackMovie(String movie) { + VideoStreamingService videoStreamingService = lookupService.getBusinessService(movie); + videoStreamingService.doProcessing(); + } +} +``` + +العميل المحمول `MobileClient` يستخدم **Business Delegate** لاستدعاء مستوى الأعمال. + + +```java +public class MobileClient { + + private final BusinessDelegate businessDelegate; + + public MobileClient(BusinessDelegate businessDelegate) { + this.businessDelegate = businessDelegate; + } + + public void playbackMovie(String movie) { + businessDelegate.playbackMovie(movie); + } +} +``` + +أخيرًا، يمكننا عرض المثال الكامل أثناء التنفيذ. + + +```java + public static void main(String[]args){ + + // preparar los objetos + var businessDelegate=new BusinessDelegate(); + var businessLookup=new BusinessLookup(); + businessLookup.setNetflixService(new NetflixService()); + businessLookup.setYouTubeService(new YouTubeService()); + businessDelegate.setLookupService(businessLookup); + + // crear el cliente y utilizar el Business Delegate + var client=new MobileClient(businessDelegate); + client.playbackMovie("Die Hard 2"); + client.playbackMovie("Maradona: The Greatest Ever"); + } +``` + +إليك مخرجات وحدة التحكم. + + +``` +21:15:33.790 [main] INFO com.iluwatar.business.delegate.NetflixService - NetflixService is now processing +21:15:33.794 [main] INFO com.iluwatar.business.delegate.YouTubeService - YouTubeService is now processing +``` + +## مخطط الفئات + +![مخطط الفئات](./etc/business-delegate.urm.png "Business Delegate") + +## الأنماط ذات الصلة + +* [نمط تحديد الموقع للخدمات](https://java-design-patterns.com/patterns/service-locator/) + +## القابلية للتطبيق + +استخدم نمط Business Delegate عندما + +* ترغب في تقليل الترابط بين مستويات العرض والأعمال. +* ترغب في تنسيق المكالمات إلى خدمات أعمال متعددة. +* ترغب في تجميع عمليات البحث والمكالمات إلى الخدمات. +* من الضروري تجريد وتغليف الاتصال بين طبقة العميل وخدمات الأعمال. + +## دروس + +* [نمط Business Delegate في TutorialsPoint](https://www.tutorialspoint.com/design_pattern/business_delegate_pattern.htm) + +## الاستخدامات المعروفة + +* التطبيقات المؤسسية التي تستخدم Java EE (Java Platform, Enterprise Edition) +* التطبيقات التي تتطلب الوصول عن بُعد إلى خدمات الأعمال + +## العواقب + +الفوائد: + +* فك الترابط بين مستويات العرض والأعمال: يسمح بمواصلة تطور مستوى العميل والخدمات المؤسسية بشكل مستقل. +* شفافية الموقع: لا يتأثر العملاء بتغييرات الموقع أو التهيئة لخدمات الأعمال. +* إعادة الاستخدام وقابلية التوسع: يمكن إعادة استخدام كائنات Business Delegate بواسطة عملاء متعددين، ويدعم النمط التوازن في الحمل وقابلية التوسع. + +العيوب: + +* التعقيد: يضيف طبقات وتجريدات إضافية قد تزيد من التعقيد. +* تحميل الأداء: قد يؤدي الإشارة الإضافية إلى خفض طفيف في الأداء. + +## الأنماط ذات الصلة + +* [محدد خدمات](https://java-design-patterns.com/patterns/service-locator/): يستخدم Delegado de Negocio ( + Business Delegate) محدد خدمات (Service Locator) للعثور على خدمات الأعمال. +* [واجهة الجلسة](https://java-design-patterns.com/patterns/session-facade/): يمكن لـ Delegado de Negocio (Business + Delegate) استخدام واجهة الجلسة (Session Facade) لتوفير واجهة موحدة لمجموعة من خدمات الأعمال. +* [كائن مركب](https://java-design-patterns.com/patterns/composite-entity/): يمكن لـ Delegado de Negocio (Business Delegate) + استخدام الكائن المركب (Composite Entity) لإدارة حالة خدمات الأعمال. + +## الشكر + +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [Core J2EE Patterns: Best Practices and Design Strategies](https://www.amazon.com/gp/product/0130648841/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=0130648841&linkId=a0100de2b28c71ede8db1757fb2b5947) diff --git a/localization/ar/business-delegate/etc/business-delegate.urm.png b/localization/ar/business-delegate/etc/business-delegate.urm.png new file mode 100644 index 000000000000..4dca6c263b99 Binary files /dev/null and b/localization/ar/business-delegate/etc/business-delegate.urm.png differ diff --git a/localization/ar/bytecode/README.md b/localization/ar/bytecode/README.md new file mode 100644 index 000000000000..d5c046b4fccf --- /dev/null +++ b/localization/ar/bytecode/README.md @@ -0,0 +1,264 @@ +--- +title: Bytecode +shortTitle: Bytecode +category: Behavioral +language: ar +tag: + - Game programming +--- + +## الغرض + +يسمح بترميز السلوك كتعليمات لجهاز افتراضي. + +## الشرح + +مثال من العالم الواقعي + +> فريق يعمل على لعبة جديدة حيث يتقاتل السحرة مع بعضهم البعض. يحتاج سلوك السحرة إلى تعديل دقيق وتجربة مئات المرات من خلال اختبارات اللعبة. ليس من المثالي أن يطلب من المبرمج إجراء تغييرات في كل مرة يريد فيها مصمم اللعبة تعديل السلوك، لذلك يتم تنفيذ سلوك الساحر كجهاز افتراضي يعتمد على البيانات. + +بكلمات بسيطة + +> نمط Bytecode يسمح بسلوك موجه بالبيانات بدلاً من الكود. + +[Gameprogrammingpatterns.com](https://gameprogrammingpatterns.com/bytecode.html) يوضح الوثائق: + +> مجموعة من التعليمات تحدد العمليات منخفضة المستوى التي يمكن تنفيذها. يتم ترميز سلسلة من التعليمات كدورة من البايتات. يقوم الجهاز الافتراضي بتنفيذ هذه التعليمات واحدة تلو الأخرى، باستخدام مكدس للقيم الوسيطة. يسمح الجمع بين التعليمات بتعريف سلوكيات معقدة وعالية المستوى. + +**مثال برمجي** + +أحد الكائنات الأكثر أهمية في اللعبة هو فئة ماغو `Wizard`. + + +```java + +@AllArgsConstructor +@Setter +@Getter +@Slf4j +public class Wizard { + + private int health; + private int agility; + private int wisdom; + private int numberOfPlayedSounds; + private int numberOfSpawnedParticles; + + public void playSound() { + LOGGER.info("Playing sound"); + numberOfPlayedSounds++; + } + + public void spawnParticles() { + LOGGER.info("Spawning particles"); + numberOfSpawnedParticles++; + } +} +``` + +بعد ذلك، نعرض التعليمات المتاحة لجهازنا الافتراضي. لكل تعليمات دلالتها الخاصة حول كيفية التعامل مع بيانات المكدس. على سبيل المثال، تقوم التعليمة ADD بأخذ العنصرين العلويين من المكدس، وتجمعهما، ثم تضع النتيجة في المكدس. + + +```java + +@AllArgsConstructor +@Getter +public enum Instruction { + + LITERAL(1), // e.g. "LITERAL 0", push 0 to stack + SET_HEALTH(2), // e.g. "SET_HEALTH", pop health and wizard number, call set health + SET_WISDOM(3), // e.g. "SET_WISDOM", pop wisdom and wizard number, call set wisdom + SET_AGILITY(4), // e.g. "SET_AGILITY", pop agility and wizard number, call set agility + PLAY_SOUND(5), // e.g. "PLAY_SOUND", pop value as wizard number, call play sound + SPAWN_PARTICLES(6), // e.g. "SPAWN_PARTICLES", pop value as wizard number, call spawn particles + GET_HEALTH(7), // e.g. "GET_HEALTH", pop value as wizard number, push wizard's health + GET_AGILITY(8), // e.g. "GET_AGILITY", pop value as wizard number, push wizard's agility + GET_WISDOM(9), // e.g. "GET_WISDOM", pop value as wizard number, push wizard's wisdom + ADD(10), // e.g. "ADD", pop 2 values, push their sum + DIVIDE(11); // e.g. "DIVIDE", pop 2 values, push their division + // ... +} +``` + +في قلب مثالنا توجد فئة `VirtualMachine`. تأخذ التعليمات كمدخلات وتنفذها لتوفير سلوك كائن اللعبة. + + +```java + +@Getter +@Slf4j +public class VirtualMachine { + + private final Stack stack = new Stack<>(); + + private final Wizard[] wizards = new Wizard[2]; + + public VirtualMachine() { + wizards[0] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), + 0, 0); + wizards[1] = new Wizard(randomInt(3, 32), randomInt(3, 32), randomInt(3, 32), + 0, 0); + } + + public VirtualMachine(Wizard wizard1, Wizard wizard2) { + wizards[0] = wizard1; + wizards[1] = wizard2; + } + + public void execute(int[] bytecode) { + for (var i = 0; i < bytecode.length; i++) { + Instruction instruction = Instruction.getInstruction(bytecode[i]); + switch (instruction) { + case LITERAL: + // Read the next byte from the bytecode. + int value = bytecode[++i]; + // Push the next value to stack + stack.push(value); + break; + case SET_AGILITY: + var amount = stack.pop(); + var wizard = stack.pop(); + setAgility(wizard, amount); + break; + case SET_WISDOM: + amount = stack.pop(); + wizard = stack.pop(); + setWisdom(wizard, amount); + break; + case SET_HEALTH: + amount = stack.pop(); + wizard = stack.pop(); + setHealth(wizard, amount); + break; + case GET_HEALTH: + wizard = stack.pop(); + stack.push(getHealth(wizard)); + break; + case GET_AGILITY: + wizard = stack.pop(); + stack.push(getAgility(wizard)); + break; + case GET_WISDOM: + wizard = stack.pop(); + stack.push(getWisdom(wizard)); + break; + case ADD: + var a = stack.pop(); + var b = stack.pop(); + stack.push(a + b); + break; + case DIVIDE: + a = stack.pop(); + b = stack.pop(); + stack.push(b / a); + break; + case PLAY_SOUND: + wizard = stack.pop(); + getWizards()[wizard].playSound(); + break; + case SPAWN_PARTICLES: + wizard = stack.pop(); + getWizards()[wizard].spawnParticles(); + break; + default: + throw new IllegalArgumentException("Invalid instruction value"); + } + LOGGER.info("Executed " + instruction.name() + ", Stack contains " + getStack()); + } + } + + public void setHealth(int wizard, int amount) { + wizards[wizard].setHealth(amount); + } + // other setters -> + // ... +} +``` + +الآن يمكننا عرض المثال الكامل باستخدام الآلة الافتراضية. + +```java + public static void main(String[]args){ + + var vm=new VirtualMachine( + new Wizard(45,7,11,0,0), + new Wizard(36,18,8,0,0)); + + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_HEALTH")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_AGILITY")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 0")); + vm.execute(InstructionConverterUtil.convertToByteCode("GET_WISDOM")); + vm.execute(InstructionConverterUtil.convertToByteCode("ADD")); + vm.execute(InstructionConverterUtil.convertToByteCode("LITERAL 2")); + vm.execute(InstructionConverterUtil.convertToByteCode("DIVIDE")); + vm.execute(InstructionConverterUtil.convertToByteCode("ADD")); + vm.execute(InstructionConverterUtil.convertToByteCode("SET_HEALTH")); + } +``` + +إليك مخرجات وحدة التحكم. + + +``` +16:20:10.193 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0] +16:20:10.196 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_HEALTH, Stack contains [0, 45] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_AGILITY, Stack contains [0, 45, 7] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 7, 0] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed GET_WISDOM, Stack contains [0, 45, 7, 11] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 45, 18] +16:20:10.197 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed LITERAL, Stack contains [0, 45, 18, 2] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed DIVIDE, Stack contains [0, 45, 9] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed ADD, Stack contains [0, 54] +16:20:10.198 [main] INFO com.iluwatar.bytecode.VirtualMachine - Executed SET_HEALTH, Stack contains [] +``` + +## مخطط الفئات + + +![alt text](./etc/bytecode.urm.png "Bytecode class diagram") + +## القابلية للتطبيق + + +## القابلية للتطبيق + +استخدم نمط Bytecode عندما تحتاج إلى تعريف العديد من السلوكيات ولغة تنفيذ لعبتك ليست مناسبة لأن: + +* إنها منخفضة المستوى جدًا، مما يجعل البرمجة مملة أو عرضة للأخطاء. +* التكرار فيها يستغرق وقتًا طويلًا بسبب بطء وقت الترجمة أو مشاكل أخرى في الأدوات. +* إنها تحتوي على ثقة مفرطة. إذا كنت ترغب في التأكد من أن السلوك المحدد لا يمكن أن يتسبب في كسر اللعبة، يجب أن تفصله عن باقي قاعدة الكود. + +## الاستخدامات المعروفة + +* تستخدم Java Virtual Machine (JVM) bytecode لتمكين برامج Java من العمل على أي جهاز يحتوي على JVM. +* تقوم Python بترجمة سكربتاتها إلى bytecode، الذي يتم تفسيره بعد ذلك بواسطة آلة افتراضية Python. +* يستخدم .NET Framework نوعًا من bytecode يسمى Microsoft Intermediate Language (MSIL). + +## العواقب + +### المزايا: + +* القابلية للنقل: يمكن تنفيذ البرامج على أي منصة تحتوي على آلة افتراضية متوافقة. +* الأمان: يمكن للآلة الافتراضية تطبيق ضوابط أمان على كود البايت. +* الأداء: يمكن للمترجمات JIT تحسين كود البايت في وقت التشغيل، مما يحسن الأداء المحتمل مقارنة بالكود المفسر. + +### العيوب: + +* الحمل الزائد: تنفيذ bytecode يتضمن عادةً مزيدًا من الحمل الزائد مقارنةً بتنفيذ الكود الأصلي، مما قد يؤثر على الأداء. +* التعقيد: تنفيذ وصيانة آلة افتراضية يضيف تعقيدًا للنظام. + +## الأنماط المرتبطة + +* [مترجم](https://java-design-patterns.com/patterns/interpreter/) يستخدم غالبًا داخل تنفيذ آلات افتراضية لتفسير تعليمات bytecode. +* [أمر](https://java-design-patterns.com/patterns/command/): يمكن اعتبار تعليمات bytecode أوامر يتم تنفيذها بواسطة الآلة الافتراضية. +* [طريقة المصنع](https://java-design-patterns.com/patterns/factory-method/): قد تستخدم الآلات الافتراضية طرق المصنع لإنشاء العمليات أو التعليمات المحددة في bytecode. + +## الشكر + +* [أنماط برمجة الألعاب](http://gameprogrammingpatterns.com/bytecode.html) +* [برمجة لغات البرمجة](https://amzn.to/49Tusnn) diff --git a/localization/ar/bytecode/etc/bytecode.urm.png b/localization/ar/bytecode/etc/bytecode.urm.png new file mode 100644 index 000000000000..51335fa0a4b4 Binary files /dev/null and b/localization/ar/bytecode/etc/bytecode.urm.png differ diff --git a/localization/ar/chain-of-responsibility/README.md b/localization/ar/chain-of-responsibility/README.md new file mode 100644 index 000000000000..f3cc1a2f3b20 --- /dev/null +++ b/localization/ar/chain-of-responsibility/README.md @@ -0,0 +1,207 @@ +--- +title: Chain of responsibility +shortTitle: Chain of responsibility +category: Behavioral +language: ar +tag: + - Gang of Four + - Decoupling +--- + +## أيضًا معروف بـ + +* سلسلة الأوامر +* سلسلة الكائنات +* سلسلة المسؤولية + +## الغرض + +يمنع ربط مُرسل الطلب بمستقبله من خلال إعطاء أكثر من كائن الفرصة لإدارة الطلب. يربط الكائنات المستقبلية معًا ويمرر الطلب عبر السلسلة حتى يتمكن أحد الكائنات من معالجته. + +## الشرح + +مثال من الحياة الواقعية + +> الملك الأورك يعطي أوامر بصوت عالٍ لجيشه. أقرب شخص للرد هو القائد، ثم الضابط، ثم الجندي. القائد، الضابط، والجندي يشكلون سلسلة من المسؤولية. + +بكلمات بسيطة + +> يساعد في بناء سلسلة من الكائنات. يدخل الطلب من طرف ويتنقل عبر كائنات متعددة حتى يجد مديرًا مناسبًا. + +تقول ويكيبيديا + +> في التصميم الموجه للكائنات، نمط سلسلة المسؤولية هو نمط تصميم يتكون من مصدر لأوامر الكائنات وسلسلة من كائنات المعالجة. يحتوي كل كائن معالجة على منطق يحدد أنواع أوامر الكائنات التي يمكنه التعامل معها؛ يتم تمرير البقية إلى كائن المعالجة التالي في السلسلة. + +**مثال برمجي** + +ترجمة لمثالنا مع الأورك أعلاه. أولًا، لدينا الكلاس `Request`: + + +```java +import lombok.Getter; + +@Getter +public class Request { + + private final RequestType requestType; + private final String requestDescription; + private boolean handled; + + public Request(final RequestType requestType, final String requestDescription) { + this.requestType = Objects.requireNonNull(requestType); + this.requestDescription = Objects.requireNonNull(requestDescription); + } + + public void markHandled() { + this.handled = true; + } + + @Override + public String toString() { + return getRequestDescription(); + } +} + +public enum RequestType { + DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX +} +``` + +أدناه، نعرض تسلسل هرم مدير الطلبات. + + +```java +public interface RequestHandler { + + boolean canHandleRequest(Request req); + + int getPriority(); + + void handle(Request req); + + String name(); +} + +@Slf4j +public class OrcCommander implements RequestHandler { + @Override + public boolean canHandleRequest(Request req) { + return req.getRequestType() == RequestType.DEFEND_CASTLE; + } + + @Override + public int getPriority() { + return 2; + } + + @Override + public void handle(Request req) { + req.markHandled(); + LOGGER.info("{} handling request \"{}\"", name(), req); + } + + @Override + public String name() { + return "Orc commander"; + } +} + +// يتم تعريف OrcOfficer و OrcSoldier بطريقة مشابهة لـ OrcCommander + + +``` + +الملك أورك يعطي الأوامر ويشكل السلسلة. + + +```java +public class OrcKing { + + private List handlers; + + public OrcKing() { + buildChain(); + } + + private void buildChain() { + handlers = Arrays.asList(new OrcCommander(), new OrcOfficer(), new OrcSoldier()); + } + + public void makeRequest(Request req) { + handlers + .stream() + .sorted(Comparator.comparing(RequestHandler::getPriority)) + .filter(handler -> handler.canHandleRequest(req)) + .findFirst() + .ifPresent(handler -> handler.handle(req)); + } +} +``` + +سلسلة المسؤولية في العمل. + + +```java +var king=new OrcKing(); + king.makeRequest(new Request(RequestType.DEFEND_CASTLE,"defend castle")); + king.makeRequest(new Request(RequestType.TORTURE_PRISONER,"torture prisoner")); + king.makeRequest(new Request(RequestType.COLLECT_TAX,"collect tax")); +``` + +إخراج وحدة التحكم. + + +``` +Orc commander handling request "defend castle" +Orc officer handling request "torture prisoner" +Orc soldier handling request "collect tax" +``` + +## مخطط الفئات + +![alt text](./etc/chain-of-responsibility.urm.png "مخطط الفئات لسلسلة المسؤولية") + +## التطبيقية + +استخدم سلسلة المسؤولية عندما + +* يمكن لعدة كائنات معالجة الطلب، ولا يتم التعرف على المعالج مسبقًا. يجب تحديد المعالج تلقائيًا. +* ترغب في إرسال طلب إلى أحد الكائنات دون تحديد المستقبل بشكل صريح. +* يجب تحديد مجموعة الكائنات التي يمكنها معالجة الطلب ديناميكيًا. + +## الاستخدامات المعروفة + +* التفاعل مع الأحداث في إطارات واجهات المستخدم الرسومية حيث يمكن معالجة الحدث في عدة مستويات من تسلسل مكونات واجهة المستخدم. +* إطارات عمل الوسطاء حيث يمر الطلب عبر سلسلة من كائنات المعالجة. +* أنظمة السجلات حيث يمكن أن تمر الرسائل عبر سلسلة من المسجلين، مع إمكانية معالجتها بطرق مختلفة. +* [java.util.logging.Logger#log()](http://docs.oracle.com/javase/8/docs/api/java/util/logging/Logger.html#log%28java.util.logging.Level,%20java.lang.String%29) +* [Apache Commons Chain](https://commons.apache.org/proper/commons-chain/index.html) +* [javax.servlet.Filter#doFilter()](http://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html#doFilter-javax.servlet.ServletRequest-javax.servlet.ServletResponse-javax.servlet.FilterChain-) + +## العواقب + +المزايا: + +* تقليل الترابط. لا يحتاج مُرسل الطلب إلى معرفة المعالج المحدد الذي سيعالج الطلب. +* مرونة أكبر في تخصيص المسؤوليات للكائنات. يمكن إضافة أو تغيير المسؤوليات لإدارة الطلب عن طريق تغيير أعضاء وترتيب السلسلة. +* يتيح تعيين معالج افتراضي إذا لم يكن هناك معالج محدد يمكنه معالجة الطلب. + +العيوب: + +* قد يكون من الصعب تصحيح الأخطاء وفهم التدفق، خاصة إذا كانت السلسلة طويلة ومعقدة. +* قد يبقى الطلب دون معالجة إذا كانت السلسلة لا تحتوي على معالج "التقاط الكل". +* قد تنشأ مشكلات في الأداء بسبب إمكانية مرور الطلب عبر عدة معالجات قبل العثور على المعالج المناسب، أو عدم العثور عليه على الإطلاق. + +## الأنماط ذات الصلة + +* [الأمر](https://java-design-patterns.com/patterns/command/): يمكن استخدامه لتغليف طلب ككائن، يمكن تمريره عبر السلسلة. +* [التركيب](https://java-design-patterns.com/patterns/composite/): غالبًا ما يتم تطبيق نمط سلسلة المسؤولية مع نمط التركيب. +* [الزخرفة](https://java-design-patterns.com/patterns/decorator/): يمكن ربط الزخارف بشكل مشابه للمسؤوليات في نمط سلسلة المسؤولية. + +## الاعتمادات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PAJUg5) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) +* [Pattern languages of program design 3](https://amzn.to/4a4NxTH) diff --git a/localization/ar/chain-of-responsibility/etc/chain-of-responsibility.urm.png b/localization/ar/chain-of-responsibility/etc/chain-of-responsibility.urm.png new file mode 100644 index 000000000000..af1bd105455b Binary files /dev/null and b/localization/ar/chain-of-responsibility/etc/chain-of-responsibility.urm.png differ diff --git a/localization/ar/client-session/README.md b/localization/ar/client-session/README.md new file mode 100644 index 000000000000..a6892e80b239 --- /dev/null +++ b/localization/ar/client-session/README.md @@ -0,0 +1,118 @@ +--- +title: Client Session +shortTitle: Client Session +category: Behavioral +language: ar +tags: + - Session management + - Web development +--- + +## أيضًا معروف باسم + +* جلسة المستخدم + +## الهدف + +يهدف نمط التصميم "جلسة العميل" إلى الحفاظ على حالة وبيانات المستخدم عبر طلبات متعددة ضمن تطبيق ويب، مما يضمن تجربة مستخدم مستمرة وشخصية. + +## الشرح + +مثال واقعي + +> ترغب في إنشاء تطبيق لإدارة البيانات يسمح للمستخدمين بإرسال طلبات إلى الخادم لتعديل وإجراء تغييرات على البيانات المخزنة على أجهزتهم. هذه الطلبات صغيرة والبيانات فردية لكل مستخدم، مما يلغي الحاجة إلى تنفيذ قاعدة بيانات واسعة النطاق. باستخدام نمط جلسة العميل، يمكن إدارة عدة طلبات في نفس الوقت، مع تحقيق توازن في تحميل العملاء عبر خوادم مختلفة بسهولة لأن الخوادم تظل بدون حالة. كما يتم القضاء على الحاجة إلى تخزين معرفات الجلسة على الخادم لأن العملاء يقدمون كل المعلومات التي يحتاجها الخادم لمعالجة طلباتهم. + +بإيجاز + +> بدلاً من تخزين معلومات عن العميل الحالي والمعلومات التي يتم الوصول إليها على الخادم، يتم الاحتفاظ بها فقط على جانب العميل. يجب على العميل إرسال بيانات الجلسة مع كل طلب إلى الخادم ويجب عليه إرسال حالة محدثة مرة أخرى إلى العميل، التي يتم تخزينها على جهاز العميل. لا يتعين على الخادم تخزين معلومات العميل. ([مرجع](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client)) + +**مثال برمجي** + +إليك كود مثال لوصف نمط جلسة العميل. في الكود التالي، نقوم أولاً بإنشاء مثيل للخادم. سيتم استخدام هذا المثيل بعد ذلك للحصول على كائنات الجلسة لعميلين. كما ترى في الكود التالي، يمكن استخدام كائن الجلسة لتخزين أي معلومات ذات صلة يحتاجها الخادم لمعالجة طلب العميل. سيتم تمرير هذه الكائنات مع كل طلب إلى الخادم. سيتضمن الطلب كائن الجلسة الذي يخزن التفاصيل ذات الصلة بالعميل مع البيانات المطلوبة لمعالجة الطلب. تساعد معلومات الجلسة في كل طلب الخادم في التعرف على العميل ومعالجة الطلب بناءً على ذلك. + + +```java +public class App { + + public static void main(String[] args) { + var server = new Server("localhost", 8080); + var session1 = server.getSession("Session1"); + var session2 = server.getSession("Session2"); + var request1 = new Request("Data1", session1); + var request2 = new Request("Data2", session2); + server.process(request1); + server.process(request2); + } +} + +@Data +@AllArgsConstructor +public class Session { + + /** + * Session id. + */ + private String id; + + /** + * Client name. + */ + private String clientName; + +} + +@Data +@AllArgsConstructor +public class Request { + + private String data; + + private Session session; + +} +``` + +## مخطط الهيكلية + +![alt text](./etc/session_state_pattern.png "نمط حالة الجلسة") + +## قابلية التطبيق + +استخدم نمط حالة الجلسة عندما: + +* التطبيقات الويب التي تتطلب مصادقة وتفويض المستخدم. +* التطبيقات التي تحتاج إلى تتبع أنشطة وتفضيلات المستخدم عبر طلبات أو زيارات متعددة. +* الأنظمة التي يحتاج فيها موارد الخادم إلى التحسين عن طريق تحميل إدارة الحالة إلى جانب العميل. + +## الاستخدامات المعروفة + +* مواقع التجارة الإلكترونية لتتبع محتويات سلة التسوق عبر الجلسات. +* المنصات عبر الإنترنت التي تقدم محتوى مخصص بناءً على تفضيلات وسجل المستخدم. +* تطبيقات الويب التي تتطلب تسجيل دخول المستخدم للوصول إلى المحتوى المخصص أو الآمن. + +## العواقب + +الفوائد: + +* تحسين أداء الخادم من خلال تقليل الحاجة لتخزين حالة المستخدم على الخادم. +* تحسين تجربة المستخدم من خلال المحتوى المخصص والتنقل السلس عبر أجزاء التطبيق المختلفة. +* مرونة في إدارة الجلسات من خلال عدة آليات تخزين على جانب العميل (مثل الكوكيز، Web Storage API). + +العيوب: + +* مخاطر محتملة للأمان إذا تم تخزين معلومات حساسة في جلسات العميل دون التشفير والتحقق المناسب. +* الاعتماد على قدرات وضبط العميل، مثل سياسات الكوكيز التي قد تختلف حسب المتصفح وإعدادات المستخدم. +* زيادة التعقيد في منطق إدارة الجلسات، خاصة في إدارة انتهاء الصلاحية، التجديد ومزامنة الجلسات عبر الأجهزة أو النوافذ المتعددة. + +## الأنماط ذات الصلة + +* جلسة الخادم: غالباً ما يُستخدم جنباً إلى جنب مع نمط جلسة العميل لتوفير توازن بين كفاءة جانب العميل والتحكم في جانب الخادم. +* [سينجلتون](https://java-design-patterns.com/patterns/singleton/): ضمان وجود مثيل واحد فقط من جلسة المستخدم في التطبيق بأكمله. +* [حالة](https://java-design-patterns.com/patterns/state/): إدارة تحولات الحالة في الجلسة مثل الحالات المصادق عليها، الضيف أو المنتهية. + +## الفضل + +* [DZone - Practical PHP patterns](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-client) +* [نمط حالة جلسة العميل - Ram N Java](https://www.youtube.com/watch?v=ycOSj9g41pc) +* [Java الاحترافي لتطبيقات الويب](https://amzn.to/4aazY59) +* [تأمين تطبيقات الويب باستخدام Spring Security](https://amzn.to/3PCCEA1) diff --git a/localization/ar/client-session/etc/session_state_pattern.png b/localization/ar/client-session/etc/session_state_pattern.png new file mode 100644 index 000000000000..f1e23be95766 Binary files /dev/null and b/localization/ar/client-session/etc/session_state_pattern.png differ diff --git a/localization/ar/collecting-parameter/README.md b/localization/ar/collecting-parameter/README.md new file mode 100644 index 000000000000..c799bb0223de --- /dev/null +++ b/localization/ar/collecting-parameter/README.md @@ -0,0 +1,222 @@ +--- +title: Collecting Parameter +shortTitle: Collecting Parameter +category: Behavioral +language: ar +tag: + - Accumulation + - Generic +--- + +## أيضًا يُعرف بـ + +* جامع +* مُجمّع + +## الهدف + +يهدف إلى تبسيط الطرق التي تجمع المعلومات من خلال تمرير كائن جمع واحد عبر عدة استدعاءات للطرق، مما يسمح لها بإضافة النتائج إلى هذه المجموعة بدلاً من أن يقوم كل طريقة بإنشاء مجموعة خاصة بها. + +## الشرح + +### مثال من العالم الحقيقي + +داخل مبنى تجاري كبير، توجد طابعات مُشتركة تعد مجموعة من جميع وظائف الطباعة المعلقة حاليًا. تحتوي الطوابق المختلفة على طرز مختلفة من الطابعات، ولكل منها سياسة طباعة مختلفة. يجب علينا بناء برنامج يمكنه إضافة مهام الطباعة المناسبة بشكل مستمر إلى مجموعة تسمى *معامل الجمع*. + +### ببساطة + +بدلاً من وجود دالة ضخمة تحتوي على العديد من السياسات لجمع المعلومات في متغير، يمكننا إنشاء العديد من الدوال الصغيرة التي تأخذ كل معلم، وتضيف معلومات جديدة. يمكننا تمرير المعامل إلى كل من هذه الدوال الصغيرة وفي النهاية، سيكون لدينا ما أردناه في الأصل. في هذه المرة، يكون الكود أنظف وأسهل في الفهم. نظرًا لأن الدالة الكبيرة قد تم تقسيمها، فإن الكود أيضًا أسهل في التعديل حيث يتم تحديد التغييرات في الدوال الصغيرة. + +### تقول ويكيبيديا + +في اصطلاح المعاملات الجامعية، يتم تمرير مجموعة (قائمة، خريطة، إلخ) بشكل متكرر كمعامل إلى دالة تضيف العناصر إلى المجموعة. + +### مثال برمجي + +بتشفير مثالنا السابق، يمكننا استخدام مجموعة `النتيجة` كمعامل جامع. يتم تنفيذ القيود التالية: + +- إذا كان ورق A4 ملونًا، يجب أن يكون أيضًا من جهة واحدة. يتم قبول جميع الأوراق غير الملونة الأخرى. +- لا يجب أن يكون ورق A3 ملونًا ويجب أن يكون من جهة واحدة. +- يجب أن يكون ورق A2 من صفحة واحدة، من جهة واحدة وغير ملون. + + +```java +package com.iluwatar.collectingparameter; + +import java.util.LinkedList; +import java.util.Queue; + +public class App { + static PrinterQueue printerQueue = PrinterQueue.getInstance(); + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + /* + Initialising the printer queue with jobs + */ + printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A4, 5, false, false)); + printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A3, 2, false, false)); + printerQueue.addPrinterItem(new PrinterItem(PaperSizes.A2, 5, false, false)); + + /* + This variable is the collecting parameter. + */ + var result = new LinkedList(); + + /* + * Using numerous sub-methods to collaboratively add information to the result collecting parameter + */ + addA4Papers(result); + addA3Papers(result); + addA2Papers(result); + } +} +``` + +نستخدم الطرق `addA4Paper` و `addA3Paper` و `addA2Paper` لملء معامل الجمع `النتيجة` بالوظائف المناسبة للطباعة وفقًا للسياسة الموصوفة سابقًا. يتم ترميز السياسات الثلاث كما يلي: + + +```java +public class App { + static PrinterQueue printerQueue = PrinterQueue.getInstance(); + + /** + * Adds A4 document jobs to the collecting parameter according to some policy that can be whatever the client + * (the print center) wants. + * + * @param printerItemsCollection the collecting parameter + */ + public static void addA4Papers(Queue printerItemsCollection) { + /* + Iterate through the printer queue, and add A4 papers according to the correct policy to the collecting parameter, + which is 'printerItemsCollection' in this case. + */ + for (PrinterItem nextItem : printerQueue.getPrinterQueue()) { + if (nextItem.paperSize.equals(PaperSizes.A4)) { + var isColouredAndSingleSided = + nextItem.isColour && !nextItem.isDoubleSided; + if (isColouredAndSingleSided) { + printerItemsCollection.add(nextItem); + } else if (!nextItem.isColour) { + printerItemsCollection.add(nextItem); + } + } + } + } + + /** + * Adds A3 document jobs to the collecting parameter according to some policy that can be whatever the client + * (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate + * the wants of the client. + * + * @param printerItemsCollection the collecting parameter + */ + public static void addA3Papers(Queue printerItemsCollection) { + for (PrinterItem nextItem : printerQueue.getPrinterQueue()) { + if (nextItem.paperSize.equals(PaperSizes.A3)) { + + // Encoding the policy into a Boolean: the A3 paper cannot be coloured and double-sided at the same time + var isNotColouredAndSingleSided = + !nextItem.isColour && !nextItem.isDoubleSided; + if (isNotColouredAndSingleSided) { + printerItemsCollection.add(nextItem); + } + } + } + } + + /** + * Adds A2 document jobs to the collecting parameter according to some policy that can be whatever the client + * (the print center) wants. The code is similar to the 'addA4Papers' method. The code can be changed to accommodate + * the wants of the client. + * + * @param printerItemsCollection the collecting parameter + */ + public static void addA2Papers(Queue printerItemsCollection) { + for (PrinterItem nextItem : printerQueue.getPrinterQueue()) { + if (nextItem.paperSize.equals(PaperSizes.A2)) { + + // Encoding the policy into a Boolean: the A2 paper must be single page, single-sided, and non-coloured. + var isNotColouredSingleSidedAndOnePage = + nextItem.pageCount == 1 && + !nextItem.isDoubleSided + && !nextItem.isColour; + if (isNotColouredSingleSidedAndOnePage) { + printerItemsCollection.add(nextItem); + } + } + } + } +} +``` + +كل طريقة تأخذ كمعامل معلمة جمع. بعد ذلك، تضيف العناصر، المأخوذة من متغير عالمي، إلى هذه المعلمة إذا كانت كل عنصر يفي بمعيار معين. يمكن أن تحتوي هذه الطرق على السياسة التي يرغب فيها العميل. + +في هذا المثال البرمجي، يتم إضافة ثلاث مهام طباعة إلى الطابور. فقط أول مهمتين للطباعة يجب إضافتهما إلى معلمة الجمع وفقًا للسياسة. العناصر في متغير `النتيجة` بعد التنفيذ هي: + +| حجم الورق | عدد الصفحات | مزدوج الوجه | ملون | +|-----------|-------------|-------------|------| +| A4 | 5 | false | false | +| A3 | 2 | false | false | + +وهذا هو ما توقعناه. + +## مخطط الفئات + +![alt text](./etc/collecting-parameter.urm.png "معامل الجمع") + +## قابلية التطبيق + +استخدم نمط التصميم جمع المعاملات عندما: + +- عندما تنتج عدة طرق مجموعة من النتائج وتريد إضافة هذه النتائج بطريقة موحدة. +- في السيناريوهات حيث يمكن أن يحسن تقليل عدد المجموعات التي يتم إنشاؤها بواسطة الطرق من كفاءة الذاكرة والأداء. +- عند إعادة هيكلة الطرق الكبيرة التي تقوم بعدة مهام، بما في ذلك جمع النتائج من عمليات متعددة. + +## الدروس التعليمية + +الدروس التعليمية لهذه الطريقة موجودة في: + +- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) بواسطة Joshua Kerivsky +- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) بواسطة Kent Beck + +## الاستخدامات المعروفة + +يوضح Joshua Kerivsky مثالًا واقعيًا في كتابه 'Refactoring to Patterns'. يقدم مثالًا لاستخدام نمط التصميم "جمع المعاملات" لإنشاء طريقة `toString()` لشجرة XML. بدون استخدام هذا النمط، سيحتاج ذلك إلى وظيفة ضخمة تحتوي على شروط ودمج النصوص مما سيزيد من صعوبة قراءة الشيفرة. يمكن تقسيم مثل هذه الطريقة إلى طرق أصغر، حيث يضيف كل منها مجموعة خاصة من المعلومات إلى معلمة الجمع. انظر إلى هذا في [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf). + +أمثلة أخرى هي: + +- إضافة رسائل الخطأ أو فشل التحقق في عملية تحقق معقدة. +- جمع العناصر أو المعلومات أثناء التنقل في هيكل بيانات معقد. +- إعادة هيكلة الوظائف المعقدة للتقارير حيث يتم إنشاء أجزاء متعددة من التقرير باستخدام طرق مختلفة. + +## العواقب + +المزايا: + +- يقلل من تكرار الشيفرة من خلال تجميع معالجة المجموعات في مكان واحد. +- يحسن الوضوح وقابلية الصيانة من خلال توضيح مكان وكيفية جمع النتائج. +- يحسن الأداء عن طريق تقليل إنشاء وإدارة كائنات جمع متعددة. + +العيوب: + +- يزيد من الترابط بين المنادي والطرق المنادى عليها، حيث يجب أن يتفقوا على المجموعة المستخدمة. +- قد يقدم آثار جانبية في الطرق إذا لم تتم إدارتها بعناية، حيث لم تعد الطرق مستقلة في إدارة النتائج. + +## الأنماط ذات الصلة + +- [Composite](https://java-design-patterns.com/patterns/composite/): يمكن استخدامه مع جمع المعاملات عند العمل مع الهياكل الهرمية، مما يسمح بجمع النتائج عبر هيكل مركب. +- [Visitor](https://java-design-patterns.com/patterns/visitor/): يستخدم غالبًا معًا، حيث يتولى Visitor المرور وإجراء العمليات في هيكل، بينما يقوم جمع المعاملات بتراكم النتائج. +- [Command](https://java-design-patterns.com/patterns/command/): يمكن للأوامر استخدام معلمة الجمع لإضافة نتائج عدة عمليات يتم تنفيذها بواسطة كائنات الأمر. + +## الشكر + +- [Refactoring To Patterns](http://www.tarrani.net/RefactoringToPatterns.pdf) بواسطة Joshua Kerivsky +- [Smalltalk Best Practice Patterns](https://ptgmedia.pearsoncmg.com/images/9780134769042/samplepages/013476904X.pdf) بواسطة Kent Beck +- [Wiki](https://wiki.c2.com/?CollectingParameter) +- [Refactoring: Improving the Design of Existing Code](https://amzn.to/3TVEgaB) +- [Clean Code: A Handbook of Agile Software Craftsmanship](https://amzn.to/4aApLP0) diff --git a/localization/ar/collecting-parameter/etc/collecting-parameter.urm.png b/localization/ar/collecting-parameter/etc/collecting-parameter.urm.png new file mode 100644 index 000000000000..785d6ecc2da1 Binary files /dev/null and b/localization/ar/collecting-parameter/etc/collecting-parameter.urm.png differ diff --git a/localization/ar/command/README.md b/localization/ar/command/README.md new file mode 100644 index 000000000000..af20644ab502 --- /dev/null +++ b/localization/ar/command/README.md @@ -0,0 +1,245 @@ +--- +title: Command +shortTitle: Command +category: Behavioral +language: ar +tag: + - Gang of Four +--- + +## أيضًا يعرف بـ + +* إجراء +* معاملة + +## الهدف + +يُغلف نمط التصميم Command الطلب ككائن، مما يسمح بتمرير العملاء مع قوائم الانتظار، الطلبات، والعمليات. كما يدعم أيضًا التراجع عن العمليات. + +## الشرح + +### مثال واقعي + +> يوجد ساحر يلقي تعويذات على عفريت. يتم تنفيذ التعويذات على العفريت واحدة تلو الأخرى. التعويذة الأولى تصغر العفريت والتعويذة الثانية تجعله غير مرئي. بعد ذلك، يقوم الساحر بالتراجع عن التعويذات واحدة تلو الأخرى. كل تعويذة هي كائن أمر يمكن التراجع عنها. + +### بكلمات بسيطة: + +> تخزين الطلبات ككائنات أمر يسمح بتنفيذ الإجراء أو التراجع عنه في وقت لاحق. + +### تقول ويكيبيديا: + +> في البرمجة الكائنية التوجه، نمط الأمر هو نمط تصميم سلوكي حيث يتم استخدام كائن لتغليف كافة المعلومات اللازمة لتنفيذ إجراء أو تحفيز حدث في وقت لاحق. + +### مثال برمجي + +إليك الكود البرمجي مع الساحر `Wizard` والعفريت `Goblin`. دعونا نبدأ بفئة الساحر `Wizard`. + + +```java + +@Slf4j +public class Wizard { + + private final Deque undoStack = new LinkedList<>(); + private final Deque redoStack = new LinkedList<>(); + + public Wizard() { + } + + public void castSpell(Runnable runnable) { + runnable.run(); + undoStack.offerLast(runnable); + } + + public void undoLastSpell() { + if (!undoStack.isEmpty()) { + var previousSpell = undoStack.pollLast(); + redoStack.offerLast(previousSpell); + previousSpell.run(); + } + } + + public void redoLastSpell() { + if (!redoStack.isEmpty()) { + var previousSpell = redoStack.pollLast(); + undoStack.offerLast(previousSpell); + previousSpell.run(); + } + } + + @Override + public String toString() { + return "Wizard"; + } +} +``` + +### التالي، لدينا العفريت `Goblin` الذي هو الهدف `Target` للتعويذات. + + +```java + +@Slf4j +public abstract class Target { + + private Size size; + + private Visibility visibility; + + public Size getSize() { + return size; + } + + public void setSize(Size size) { + this.size = size; + } + + public Visibility getVisibility() { + return visibility; + } + + public void setVisibility(Visibility visibility) { + this.visibility = visibility; + } + + @Override + public abstract String toString(); + + public void printStatus() { + LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility()); + } +} + +public class Goblin extends Target { + + public Goblin() { + setSize(Size.NORMAL); + setVisibility(Visibility.VISIBLE); + } + + @Override + public String toString() { + return "Goblin"; + } + + public void changeSize() { + var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL; + setSize(oldSize); + } + + public void changeVisibility() { + var visible = getVisibility() == Visibility.INVISIBLE + ? Visibility.VISIBLE : Visibility.INVISIBLE; + setVisibility(visible); + } +} +``` + +### أخيرًا، لدينا الساحر في الدالة الرئيسية وهو يلقي التعويذات. + + +```java +public static void main(String[]args){ + var wizard=new Wizard(); + var goblin=new Goblin(); + + // casts shrink/unshrink spell + wizard.castSpell(goblin::changeSize); + + // casts visible/invisible spell + wizard.castSpell(goblin::changeVisibility); + + // undo and redo casts + wizard.undoLastSpell(); + wizard.redoLastSpell(); +``` + +### هذا هو المثال قيد التنفيذ. + + +```java +var wizard=new Wizard(); + var goblin=new Goblin(); + + goblin.printStatus(); + wizard.castSpell(goblin::changeSize); + goblin.printStatus(); + + wizard.castSpell(goblin::changeVisibility); + goblin.printStatus(); + + wizard.undoLastSpell(); + goblin.printStatus(); + + wizard.undoLastSpell(); + goblin.printStatus(); + + wizard.redoLastSpell(); + goblin.printStatus(); + + wizard.redoLastSpell(); + goblin.printStatus(); +``` + +### إليك مخرجات البرنامج: + + +```java +Goblin,[size=normal][visibility=visible] + Goblin,[size=small][visibility=visible] + Goblin,[size=small][visibility=invisible] + Goblin,[size=small][visibility=visible] + Goblin,[size=normal][visibility=visible] + Goblin,[size=small][visibility=visible] + Goblin,[size=small][visibility=invisible] +``` + +## تطبيق + +استخدم نمط الأمر (Command) في الحالات التالية: + +* لتحديد كائنات باستخدام إجراء لتنفيذه. يمكنك التعبير عن هذه التحديدات باستخدام لغة إجراء مع دالة رد اتصال، أي دالة يتم تسجيلها في مكان ما ليتم استدعاؤها في وقت لاحق. الأوامر هي بديل موجه للكائنات لردود الاتصال. +* لتحديد، وضع في طابور وتنفيذ الطلبات في أوقات مختلفة. يمكن أن يكون لكائن الأمر حياة مستقلة عن الطلب الأصلي. إذا كان يمكن تمثيل مستلم الطلب بطريقة مستقلة عن مساحة العناوين، فيمكنك نقل كائن الأمر للطلب إلى عملية مختلفة وتنفيذ الطلب هناك. +* دعم الإلغاء. يمكن لعملية تنفيذ الأمر تخزين الحالة لإلغاء تأثيراتها في نفس الأمر. يجب أن تحتوي واجهة الأمر على عملية إضافية لإلغاء التنفيذ التي تعيد تأثيرات استدعاء سابق لتنفيذ. يتم تخزين الأوامر التي تم تنفيذها في قائمة تاريخ. يمكن تحقيق وظيفة التراجع وإعادة التنفيذ بشكل غير محدود من خلال استعراض هذه القائمة للأمام والخلف عن طريق استدعاء إلغاء التنفيذ والتنفيذ، على التوالي. +* دعم تسجيل التغييرات بحيث يمكن تطبيقها مرة أخرى في حال حدوث عطل في النظام. من خلال إضافة عمليات تحميل وتخزين إلى واجهة الأوامر، يمكنك الاحتفاظ بسجل مستمر للتغييرات. يتطلب استعادة العطل إعادة تحميل الأوامر المسجلة من القرص وتنفيذها مرة أخرى باستخدام عملية التنفيذ. +* هيكلة النظام حول عمليات عالية المستوى مبنية على عمليات بدائية. هذه الهيكلة شائعة في أنظمة المعلومات التي تدعم المعاملات. المعاملة تحتوي على مجموعة من التغييرات في البيانات. يوفر نمط الأمر طريقة لنمذجة المعاملات. تحتوي الأوامر على واجهة مشتركة تسمح باستدعاء جميع المعاملات بنفس الطريقة. كما يسهل النمط توسيع النظام مع معاملات جديدة. +* الحفاظ على سجل من الطلبات. +* تنفيذ وظيفة رد الاتصال. +* تنفيذ وظيفة التراجع. + +## الاستخدامات المعروفة + +* الأزرار في واجهة المستخدم الرسومية وعناصر القائمة في تطبيقات سطح المكتب. +* العمليات في أنظمة قواعد البيانات والأنظمة المعاملاتية التي تدعم التراجع (rollback). +* تسجيل الماكرو في التطبيقات مثل محرري النصوص وجداول البيانات. +* [java.lang.Runnable](http://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html) +* [org.junit.runners.model.Statement](https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runners/model/Statement.java) +* [Netflix Hystrix](https://github.com/Netflix/Hystrix/wiki) +* [javax.swing.Action](http://docs.oracle.com/javase/8/docs/api/javax/swing/Action.html) + +## العواقب + +المزايا: + +* يفصل الكائن الذي يستدعي العملية عن الكائن الذي يعرف كيفية تنفيذها. +* من السهل إضافة أوامر جديدة، لأنه لا يتعين عليك تغيير الفئات الموجودة. +* يمكنك تجميع مجموعة من الأوامر في أمر مركب. + +العيوب: + +* يزيد عدد الفئات لكل أمر فردي. +* قد يعقد التصميم عند إضافة طبقات متعددة بين المرسلين والمستلمين. + +## الأنماط ذات الصلة + +* [Composite](https://java-design-patterns.com/patterns/composite/): يمكن تجميع الأوامر باستخدام نمط المركب لإنشاء أوامر كبيرة. +* [Memento](https://java-design-patterns.com/patterns/memento/): يمكن استخدامه لتنفيذ آليات التراجع. +* [Observer](https://java-design-patterns.com/patterns/observer/): يمكن ملاحظة النمط لتغييرات التي تفعّل الأوامر. + +## المصادر + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3PFUqSY) diff --git a/localization/ar/command/etc/command.png b/localization/ar/command/etc/command.png new file mode 100644 index 000000000000..0f026464ecc4 Binary files /dev/null and b/localization/ar/command/etc/command.png differ diff --git a/localization/ar/commander/README.md b/localization/ar/commander/README.md new file mode 100644 index 000000000000..2cb0b72e17f1 --- /dev/null +++ b/localization/ar/commander/README.md @@ -0,0 +1,136 @@ +--- +title: Commander +shortTitle: Commander +category: Behavioral +language: ar +tag: + - Cloud distributed + - Microservices + - Transactions +--- + +## أيضا يُعرف باسم + +* منسق المعاملات الموزعة +* منسق المعاملات + +## الهدف + +الهدف من نمط "المنسق" في سياق المعاملات الموزعة هو إدارة وتنسيق المعاملات المعقدة عبر العديد من المكونات أو الخدمات الموزعة، مما يضمن التناسق والنزاهة للمعاملة العالمية. يقوم بتغليف أوامر المعاملات ومنطق التنسيق، مما يسهل تنفيذ بروتوكولات المعاملات الموزعة مثل الالتزام ذو المرحلتين أو ساغا. + +## الشرح + +مثال حقيقي + +> تخيل أنك تنظم مهرجان موسيقي دولي كبير، حيث من المقرر أن تؤدي عدة فرق من جميع أنحاء العالم. وصول كل فرقة، واختبار الصوت، والأداء، كل منها يعد معاملة فردية في نظام موزع. يتصرف منظم المهرجان كـ "منسق"، منسقًا هذه المعاملات لضمان أنه إذا تأخر طيران إحدى الفرق (مثل فشل المعاملة)، فهناك خطة احتياطية مثل إعادة جدولة أو تبادل الفترات الزمنية مع فرقة أخرى (إجراءات تعويضية)، للحفاظ على سير البرنامج العام. تعكس هذه الإعدادات نمط المنسق في المعاملات الموزعة، حيث يجب تنسيق العديد من المكونات لتحقيق نتيجة مرضية رغم الفشل الفردي. + +بكلمات بسيطة + +> يقوم نمط "المنسق" بتحويل الطلب إلى كائن مستقل، مما يسمح بمعلمة الأوامر، ووضع الإجراءات في طابور، وتنفيذ عمليات التراجع. + +**مثال برمجي** + +إدارة المعاملات عبر خدمات مختلفة في نظام موزع، مثل منصة للتجارة الإلكترونية تحتوي على خدمات منفصلة للدفع والشحن، تتطلب تنسيقًا دقيقًا لتجنب المشكلات. عندما يقدم المستخدم طلبًا ولكن خدمة واحدة (مثل الدفع) غير متوفرة بينما الخدمة الأخرى (مثل الشحن) جاهزة، نحتاج إلى حل قوي للتعامل مع هذا التفاوت. + +إحدى الاستراتيجيات لحل هذه المشكلة هي استخدام مكون منسق لتنظيم العملية. في البداية، تتم معالجة الطلب من قبل الخدمة المتاحة (الشحن في هذه الحالة). ثم يحاول المنسق مزامنة الطلب مع الخدمة غير المتوفرة في ذلك الوقت (الدفع) من خلال تخزين تفاصيل الطلب في قاعدة بيانات أو وضعه في طابور للمعالجة في المستقبل. يجب أن يأخذ هذا النظام في الحسبان الفشل المحتمل عند إضافة الطلبات إلى الطابور. + +يحاول المنسق بشكل متكرر معالجة الطلبات في الطابور لضمان أن تعكس جميع الخدمات أخيرًا نفس بيانات المعاملة. يتضمن هذا العملية ضمان التكرارية، مما يعني أنه حتى إذا تم إجراء نفس طلب مزامنة الطلبات عدة مرات، فسيتم تنفيذه مرة واحدة فقط، مما يمنع المعاملات المكررة. الهدف هو تحقيق التناسق النهائي بين الخدمات، حيث تتزامن جميع الأنظمة بمرور الوقت على الرغم من الفشل أو التأخير الأولي. + +في الكود المقدم، يُستخدم نمط المنسق لإدارة المعاملات الموزعة عبر العديد من الخدمات (خدمة الدفع، خدمة الشحن، خدمة الرسائل، إدارة الموظفين). كل خدمة تحتوي على قاعدة بيانات خاصة بها ويمكن أن تُطلق استثناءات لمحاكاة الفشل. + +فئة المنسق هي الجزء المركزي من هذا النمط. تأخذ الفئة المنسق مثيلات لجميع الخدمات وقواعد بياناتها، جنبًا إلى جنب مع بعض معلمات التكوين. تُستخدم دالة placeOrder في فئة المنسق لتنفيذ الطلب، مما يتطلب التفاعل مع جميع الخدمات. + + +```java +public class Commander { + // ... constructor and other methods ... + + public void placeOrder(Order order) { + // ... implementation ... + } +} +``` + +تمثل الفئات "المستخدم" و "الطلب" مستخدمًا وطلبًا على التوالي. يتم إجراء الطلب بواسطة المستخدم. + + +```java +public class User { + // ... constructor and other methods ... +} + +public class Order { + // ... constructor and other methods ... +} +``` + +كل خدمة (على سبيل المثال، خدمة الدفع، خدمة الشحن، خدمة الرسائل، إدارة الموظفين) لديها قاعدة بيانات خاصة بها ويمكن أن تُطلق استثناءات لمحاكاة الأعطال. على سبيل المثال، قد تقوم خدمة الدفع بإطلاق استثناء DatabaseUnavailableException إذا كانت قاعدة بياناتها غير متاحة. + + +```java +public class PaymentService { + // ... constructor and other methods ... +} +``` + +تمثل الفئات DatabaseUnavailableException و ItemUnavailableException و ShippingNotPossibleException أنواعًا مختلفة من الاستثناءات التي قد تحدث. + + +```java +public class DatabaseUnavailableException extends Exception { + // ... constructor and other methods ... +} + +public class ItemUnavailableException extends Exception { + // ... constructor and other methods ... +} + +public class ShippingNotPossibleException extends Exception { + // ... constructor and other methods ... +} +``` + +في الطريقة الرئيسية لكل فئة (AppQueueFailCases و AppShippingFailCases)، يتم محاكاة سيناريوهات مختلفة عن طريق إنشاء مثيلات من فئة Commander مع تكوينات مختلفة واستدعاء طريقة placeOrder. + +## مخطط الفئات + +![alt text](./etc/commander.urm.png "مخطط فئة Commander") + +## قابلية التطبيق + +استخدم نمط Commander للمعاملات الموزعة عندما: + +* تحتاج إلى ضمان اتساق البيانات بين الخدمات الموزعة في حالة حدوث فشل جزئي في النظام. +* تشمل المعاملات عدة خدمات ميكروسيرفيس أو مكونات موزعة تتطلب commit أو rollback منسق. +* تقوم بتنفيذ معاملات طويلة الأجل تتطلب إجراءات تعويضية للإلغاء. + +## الاستخدامات المعروفة + +* بروتوكولات Two-Phase Commit (2PC): التنسيق بين commit أو rollback عبر قواعد البيانات أو الخدمات الموزعة. +* تنفيذات نمط Saga: إدارة عمليات الأعمال طويلة الأجل التي تشمل العديد من الميكروسيرفيس، مع وجود إجراء تعويضي لكل خطوة للإلغاء. +* المعاملات الموزعة في بنية الميكروسيرفيس: تنسيق العمليات المعقدة بين الميكروسيرفيس مع الحفاظ على تكامل البيانات واتساقها. + +## العواقب + +الفوائد: + +* يوفر آلية واضحة لإدارة المعاملات الموزعة المعقدة، مما يحسن موثوقية النظام. +* يسمح بتنفيذ المعاملات التعويضية، وهي ضرورية للحفاظ على التناسق في المعاملات طويلة الأجل. +* يسهل دمج الأنظمة المتجانسة في سياق المعاملات. + +العيوب: + +* يزيد من التعقيد، خاصة في حالات الفشل، بسبب الحاجة إلى آليات التراجع المنسقة. +* قد يؤثر على الأداء بسبب الحمل الزائد للتنسيق وفحوصات التناسق. +* قد تؤدي التنفيذات المعتمدة على Saga إلى زيادة التعقيد في فهم سير العملية التجارية العامة. + +## الأنماط المرتبطة + +[Nمط Saga](https://java-design-patterns.com/patterns/saga/): غالبًا ما يتم مناقشته مع نمط Commander للمعاملات الموزعة، مع التركيز على المعاملات طويلة الأجل مع إجراءات تعويضية. + +## الشكر + +* [المعاملات الموزعة: جبال الجليد في الميكروسيرفيس](https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/) +* [أنماط الميكروسيرفيس: مع أمثلة في جافا](https://amzn.to/4axjnYW) +* [تصميم التطبيقات المعتمدة على البيانات: الأفكار الكبيرة وراء الأنظمة القابلة للاعتماد، القابلة للتوسع، والقابلة للصيانة](https://amzn.to/4axHwOV) +* [أنماط تكامل المؤسسات: تصميم وبناء ونشر حلول الرسائل](https://amzn.to/4aATcRe) diff --git a/localization/ar/commander/etc/commander.urm.png b/localization/ar/commander/etc/commander.urm.png new file mode 100644 index 000000000000..6b5ebba75bd6 Binary files /dev/null and b/localization/ar/commander/etc/commander.urm.png differ diff --git a/localization/ar/composite-entity/README.md b/localization/ar/composite-entity/README.md new file mode 100644 index 000000000000..0e04ae3efe03 --- /dev/null +++ b/localization/ar/composite-entity/README.md @@ -0,0 +1,159 @@ +--- +title: Composite Entity +shortTitle: Composite Entity +category: Structural +language: ar +tag: + - Client-server + - Data access + - Enterprise patterns +--- + +## أيضا يُعرف بـ + +* الكيان ذو الحبيبات الخشنة + +## الهدف + +هدف نمط التصميم **الكيان المركب** هو إدارة مجموعة من الكائنات المستمرة المترابطة كما لو كانت كيانًا واحدًا. يتم استخدامه عادة في سياق **Enterprise JavaBeans (EJB)** وأطر العمل التجارية المماثلة لتمثيل الهياكل البيانية للبيانات ضمن نماذج الأعمال، مما يتيح للعملاء التعامل معها كوحدة واحدة. + +## الشرح + +مثال واقعي + +> في وحدة التحكم، قد يكون هناك العديد من الواجهات التي تحتاج إلى إدارة ومراقبة. باستخدام نمط الكيان المركب، يمكن دمج الكائنات المعتمدة مثل الرسائل والإشارات والسيطرة عليها باستخدام كائن واحد. + +بكلمات بسيطة + +> نمط الكيان المركب يسمح بتمثيل وإدارة مجموعة من الكائنات المرتبطة من خلال كائن موحد. + +**مثال برمجي** + +نحتاج إلى حل عام للمشكلة. لذلك، سنقدم نمطًا عامًا للكيان المركب. + + +```java +public abstract class DependentObject { + + T data; + + public void setData(T message) { + this.data = message; + } + + public T getData() { + return data; + } +} + +public abstract class CoarseGrainedObject { + + DependentObject[] dependentObjects; + + public void setData(T... data) { + IntStream.range(0, data.length).forEach(i -> dependentObjects[i].setData(data[i])); + } + + public T[] getData() { + return (T[]) Arrays.stream(dependentObjects).map(DependentObject::getData).toArray(); + } +} + +``` + +الكائن المركب المتخصص `consola` يرث من هذه الفئة الأساسية بالطريقة التالية. + + +```java +public class MessageDependentObject extends DependentObject { + +} + +public class SignalDependentObject extends DependentObject { + +} + +public class ConsoleCoarseGrainedObject extends CoarseGrainedObject { + + @Override + public String[] getData() { + super.getData(); + return new String[] { + dependentObjects[0].getData(), dependentObjects[1].getData() + }; + } + + public void init() { + dependentObjects = new DependentObject[] { + new MessageDependentObject(), new SignalDependentObject()}; + } +} + +public class CompositeEntity { + + private final ConsoleCoarseGrainedObject console = new ConsoleCoarseGrainedObject(); + + public void setData(String message, String signal) { + console.setData(message, signal); + } + + public String[] getData() { + return console.getData(); + } +} +``` + +إدارة الآن تخصيص كائنات الرسالة والإشارة مع الكائن المركب `consola`. + + +```java +var console=new CompositeEntity(); + console.init(); + console.setData("No Danger","Green Light"); + Arrays.stream(console.getData()).forEach(LOGGER::info); + console.setData("Danger","Red Light"); + Arrays.stream(console.getData()).forEach(LOGGER::info); +``` + +## مخطط الفئات + +![alt text](./etc/composite_entity.urm.png "نمط الكائن المركب") + +## القابلية للتطبيق + +* مفيد في التطبيقات التجارية حيث تكون الكائنات التجارية معقدة وتنطوي على عدة كائنات مترابطة. +* مثالي للسيناريوهات التي يحتاج فيها العملاء للعمل مع واجهة موحدة لمجموعة من الكائنات بدلاً من الكائنات الفردية. +* قابل للتطبيق في الأنظمة التي تتطلب عرضًا مبسطًا لنموذج بيانات معقد للعملاء أو الخدمات الخارجية. + +## الاستخدامات المعروفة + +* التطبيقات التجارية ذات النماذج التجارية المعقدة، وخاصة تلك التي تستخدم EJB أو أطر عمل تجارية مشابهة. +* الأنظمة التي تتطلب تجريدًا فوق مخططات قواعد بيانات معقدة لتبسيط التفاعلات مع العملاء. +* التطبيقات التي تحتاج إلى تعزيز التناسق أو المعاملات عبر عدة كائنات في كائن تجاري واحد. + +## العواقب + +الفوائد: + +* يبسط تفاعلات العميل مع النماذج الكائنية المعقدة من خلال توفير واجهة موحدة. +* يعزز إعادة الاستخدام والصيانة في طبقة الأعمال عن طريق فصل كود العميل عن المكونات الداخلية المعقدة للكائنات التجارية. +* يسهل إدارة المعاملات وتطبيق التناسق في مجموعة من الكائنات المترابطة. + +السلبيات: + +* قد يقدم مستوى من الاستدلال الذي قد يؤثر على الأداء. +* قد يؤدي إلى واجهات ذات حبوب خشنة جدًا قد لا تكون مرنة لجميع احتياجات العملاء. +* يتطلب تصميمًا دقيقًا لتجنب الكائنات المركبة المتضخمة التي يصعب إدارتها. + +## الأنماط ذات الصلة + +* [الزخرفة](https://java-design-patterns.com/patterns/decorator/): لإضافة سلوك ديناميكي للكائنات الفردية داخل الكائن المركب دون التأثير على الهيكل. +* [الواجهة](https://java-design-patterns.com/patterns/facade/): يوفر واجهة مبسطة لنظام فرعي معقد، بشكل مشابه لكيفية تبسيط الكائن المركب الوصول إلى مجموعة من الكائنات. +* [الوزن الخفيف](https://java-design-patterns.com/patterns/flyweight/): مفيد لإدارة الكائنات المشتركة داخل الكائن المركب لتقليل بصمة الذاكرة. + +## الاعتمادات + +* [نمط الكائن المركب في ويكيبيديا](https://en.wikipedia.org/wiki/Composite_entity_pattern) +* [أفضل الممارسات واستراتيجيات التصميم في الأنماط الأساسية لـ J2EE](https://amzn.to/4cAbDap) +* [أنماط المؤسسة و MDA: بناء البرمجيات الأفضل باستخدام أنماط الأركيتايب و UML](https://amzn.to/49mslqS) +* [أنماط بنية التطبيقات المؤسسية](https://amzn.to/3xjKdpe) diff --git a/localization/ar/composite-entity/etc/composite_entity.urm.png b/localization/ar/composite-entity/etc/composite_entity.urm.png new file mode 100644 index 000000000000..d6c29a718837 Binary files /dev/null and b/localization/ar/composite-entity/etc/composite_entity.urm.png differ diff --git a/localization/ar/composite-view/README.md b/localization/ar/composite-view/README.md new file mode 100644 index 000000000000..dcd543c43a2f --- /dev/null +++ b/localization/ar/composite-view/README.md @@ -0,0 +1,353 @@ +--- +title: Composite View +shortTitle: Composite View +category: Structural +language: ar +tag: + - Enterprise patterns + - Presentation +--- + +## الغرض + +الهدف الرئيسي من نمط التصميم "عرض مركب" هو تكوين الكائنات في هياكل شجرية لتمثيل الهيراركية جزء-كامل. هذا يتيح للعملاء التعامل مع الكائنات الفردية وتركيبات الكائنات بشكل موحد، مما يبسط إدارة الهياكل المعقدة. + +## التفسير + +مثال من العالم الحقيقي + +> موقع إخباري يريد عرض التاريخ الحالي والأخبار لعدة مستخدمين بناءً على تفضيلات كل مستخدم. سيستبدل الموقع في مكونات تغذية الأخبار المختلفة حسب اهتمامات المستخدم، مع الأخبار المحلية كافتراضي. + +بإيجاز + +> نمط العرض المركب يتكون من عرض رئيسي مكون من عروض فرعية أصغر. يعتمد تصميم هذا العرض المركب على قالب. ثم يقرر مدير العرض أي العروض الفرعية يجب تضمينها في هذا القالب. + +تقول ويكيبيديا + +> العروض المركبة التي تتكون من العديد من العروض الفرعية الذرية. يمكن تضمين كل مكون من القالب ديناميكيًا في المجموعة ويمكن إدارة تصميم الصفحة بشكل مستقل عن المحتوى. يتيح هذا الحل إنشاء عرض مركب استنادًا إلى تضمين واستبدال أجزاء قابلة لإعادة الاستخدام من القوالب الديناميكية والثابتة. يعزز التصميم المعياري من خلال تشجيع إعادة استخدام أجزاء الذرة من العرض. + +**مثال برمجي** + +نظرًا لأن هذا نمط تطوير ويب، فإن الخادم مطلوب لعرضه. يستخدم هذا المثال Tomcat 10.0.13 لتشغيل السيرفلت، ولن يعمل هذا المثال البرمجي إلا مع Tomcat 10+. + +أولاً، يوجد `AppServlet` الذي هو `HttpServlet` يعمل في Tomcat 10+. + + +```java +public class AppServlet extends HttpServlet { + private String msgPartOne = "

    This Server Doesn't Support"; + private String msgPartTwo = "Requests

    \n" + + "

    Use a GET request with boolean values for the following parameters

    \n" + + "

    'name'

    \n

    'bus'

    \n

    'sports'

    \n

    'sci'

    \n

    'world'

    "; + + private String destination = "newsDisplay.jsp"; + + public AppServlet() { + + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + RequestDispatcher requestDispatcher = req.getRequestDispatcher(destination); + ClientPropertiesBean reqParams = new ClientPropertiesBean(req); + req.setAttribute("properties", reqParams); + requestDispatcher.forward(req, resp); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Post " + msgPartTwo); + + } + + @Override + public void doDelete(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Delete " + msgPartTwo); + + } + + @Override + public void doPut(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println(msgPartOne + " Put " + msgPartTwo); + + } +} + +``` + +هذا السيرفلت لا يشكل جزءًا من النمط، ويقوم ببساطة بإعادة توجيه طلبات GET إلى JSP الصحيح. الطلبات PUT و POST و DELETE غير مدعومة وستعرض ببساطة رسالة خطأ. + +إدارة العرض في هذا المثال تتم من خلال فئة javabean: `ClientPropertiesBean`، التي تخزن تفضيلات المستخدم. + + +```java +public class ClientPropertiesBean implements Serializable { + + private static final String WORLD_PARAM = "world"; + private static final String SCIENCE_PARAM = "sci"; + private static final String SPORTS_PARAM = "sport"; + private static final String BUSINESS_PARAM = "bus"; + private static final String NAME_PARAM = "name"; + + private static final String DEFAULT_NAME = "DEFAULT_NAME"; + private boolean worldNewsInterest; + private boolean sportsInterest; + private boolean businessInterest; + private boolean scienceNewsInterest; + private String name; + + public ClientPropertiesBean() { + worldNewsInterest = true; + sportsInterest = true; + businessInterest = true; + scienceNewsInterest = true; + name = DEFAULT_NAME; + + } + + public ClientPropertiesBean(HttpServletRequest req) { + worldNewsInterest = Boolean.parseBoolean(req.getParameter(WORLD_PARAM)); + sportsInterest = Boolean.parseBoolean(req.getParameter(SPORTS_PARAM)); + businessInterest = Boolean.parseBoolean(req.getParameter(BUSINESS_PARAM)); + scienceNewsInterest = Boolean.parseBoolean(req.getParameter(SCIENCE_PARAM)); + String tempName = req.getParameter(NAME_PARAM); + if (tempName == null || tempName == "") { + tempName = DEFAULT_NAME; + } + name = tempName; + } + // getters and setters generated by Lombok +} +``` + +هذا السيرفلت لا يشكل جزءًا من النمط، ويقوم ببساطة بإعادة توجيه طلبات GET إلى JSP الصحيح. الطلبات PUT و POST و DELETE غير مدعومة وستعرض ببساطة رسالة خطأ. + +إدارة العرض في هذا المثال تتم من خلال فئة javabean: `ClientPropertiesBean`، التي تخزن تفضيلات المستخدم. + + +```html + + + + + + +<%ClientPropertiesBean propertiesBean = (ClientPropertiesBean) request.getAttribute("properties");%> +

    Welcome <%= propertiesBean.getName()%>

    + + + + + + <% if(propertiesBean.isWorldNewsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isBusinessInterest()) { %> + + <% } else { %> + + <% } %> + + <% if(propertiesBean.isSportsInterest()) { %> + + <% } else { %> + + <% } %> + + + + <% if(propertiesBean.isScienceNewsInterest()) { %> + + <% } else { %> + + <% } %> + + +
    <%@include file="worldNews.jsp"%><%@include file="localNews.jsp"%>
    <%@include file="businessNews.jsp"%><%@include file="localNews.jsp"%><%@include file="sportsNews.jsp"%><%@include file="localNews.jsp"%>
    <%@include file="scienceNews.jsp"%><%@include file="localNews.jsp"%>
    + + +``` + +هذه الصفحة JSP هي القالب. تقوم بإعلان جدول يحتوي على ثلاث صفوف، مع مكون في الصف الأول، واثنين من المكونات في الصف الثاني، ومكون واحد في الصف الثالث. + +تعتبر السكربتات في الملف جزءًا من استراتيجية إدارة العرض التي تتضمن مختلف العناصر الفرعية الذرية بناءً على تفضيلات المستخدم في الجافابين. + +فيما يلي مثالان على العناصر الفرعية الذرية المُحاكاة المستخدمة في التكوين: `businessNews.jsp`. + + +```html + + + + + + +

    + Generic Business News +

    + + + + + + + + + +
    Stock prices up across the worldNew tech companies to invest in
    Industry leaders unveil new projectPrice fluctuations and what they mean
    + + +``` + +`localNews.jsp` + +```html + + + +
    +

    + Generic Local News +

    +
      +
    • + Mayoral elections coming up in 2 weeks +
    • +
    • + New parking meter rates downtown coming tomorrow +
    • +
    • + Park renovations to finish by the next year +
    • +
    • + Annual marathon sign ups available online +
    • +
    +
    + + +``` + +النتائج هي كما يلي: + +1) وضع المستخدم اسمه كـ `Tammy` في معلمات الطلب ولم يضع أي تفضيلات: ![alt text](./etc/images/noparam.png) +2) وضع المستخدم اسمه كـ `Johnny` في معلمات الطلب ولديه تفضيل للأخبار عن العالم، الأعمال والعلوم: ![alt text](./etc/images/threeparams.png) + +يتم تضمين العناصر الفرعية المختلفة مثل `worldNews.jsp`، `businessNews.jsp`، وغيرها بشكل مشروط بناءً على معلمات الطلب. + +**كيفية الاستخدام** + +لاختبار هذا المثال، تأكد من أن لديك Tomcat 10+ مثبتًا. قم بتكوين IDE الخاص بك لإنشاء ملف WAR من الوحدة ونشر هذا الملف على الخادم. + +IntelliJ: + +في `تشغيل` و `تحرير التكوينات` تأكد من أن خادم Tomcat هو أحد تكوينات التنفيذ. اذهب إلى تبويب النشر وتأكد من أنه يتم بناء artifact يسمى `composite-view:war exploded`. إذا لم يكن موجودًا، أضف واحدًا. + +تأكد من أن artifact يتم بناؤه من محتويات مجلد `web` ونتائج تجميع الوحدة. وجه مخرجات artifact إلى مكان مناسب. نفذ التكوين وشاهد الصفحة المستهدفة، واتبع التعليمات في تلك الصفحة للمتابعة. + +## مخطط الفئات + +![alt text](./etc/composite_view.png) + +يُظهر مخطط الفئات هنا الجافابين الذي يُعتبر مدير العرض. العروض هي ملفات JSP داخل مجلد الويب. + +## القابلية للتطبيق: + +استخدم نمط التصميم Composite View عندما: + +## تريد تمثيل الهياكل الجزئية للأشياء. + +* تتوقع أن الهياكل المركبة قد تتضمن مكونات جديدة في المستقبل. +* ترغب في أن يتمكن العملاء من تجاهل الفرق بين تكوينات الكائنات والكائنات الفردية. سيتعامل العملاء مع جميع الكائنات في الهيكل المركب بشكل موحد. + +## الاستخدامات المعروفة + +* واجهات المستخدم الرسومية (GUI) التي يمكن أن تحتوي فيها الأدوات على أدوات أخرى (على سبيل المثال، نافذة تحتوي على لوحات وأزرار وحقول نصية). +* هياكل الوثائق، مثل تمثيل الجداول التي تحتوي على صفوف، تحتوي هذه الصفوف بدورها على خلايا، والتي يمكن معالجتها جميعًا كعناصر في تسلسل هرمي موحد. + +## العواقب + +الفوائد: + +* مرونة كبيرة في إضافة مكونات جديدة: بما أن المكونات المركبة والعقد الورقية يتم التعامل معها بشكل موحد، يكون من الأسهل إضافة أنواع جديدة من المكونات. +* تبسيط الشيفرة البرمجية للعملاء: يمكن للعملاء التعامل مع الهياكل المركبة والعناصر الفردية بشكل موحد، مما يقلل من تعقيد الشيفرة البرمجية للعملاء. + +العيوب: + +* التعميم المفرط: قد يصبح تصميم النظام أكثر تعقيدًا إذا جعلت كل شيء مركبًا، خاصة إذا كانت تطبيقك لا يتطلب ذلك. +* صعوبة تطبيق القيود: قد يكون من الأصعب تقييد مكونات المركب لتكون من أنواع معينة فقط. + +## الأنماط ذات الصلة + +* [المزخرف](https://java-design-patterns.com/patterns/decorator/): بينما يستخدم مزخرف لإضافة مسؤوليات إلى الكائنات، يتم تصميم Composite لبناء هياكل كائنات. +* [الوزن الخفيف](https://java-design-patterns.com/patterns/flyweight/): يمكن دمج Composite غالبًا مع Flyweight لتنفيذ العقد الورقية المشتركة في هيكل مركب، مما يقلل من بصمة الذاكرة. +* [سلسلة المسؤولية](https://java-design-patterns.com/patterns/chain-of-responsibility/): يمكن استخدامها مع Composite للسماح للمكونات بتمرير الطلبات عبر التسلسل الهرمي. +* [المركب](https://java-design-patterns.com/patterns/composite/) +* [مساعد العرض](https://www.oracle.com/java/technologies/viewhelper.html) + +## الاعتمادات + +* [Core J2EE Patterns - Composite View](https://www.oracle.com/java/technologies/composite-view.html) +* [Composite View Design Pattern – Core J2EE Patterns](https://www.dineshonjava.com/composite-view-design-pattern/) +* [Patterns of Enterprise Application Architecture](https://amzn.to/49jpQG3) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3xfntGJ) diff --git a/localization/ar/composite-view/etc/composite_view.png b/localization/ar/composite-view/etc/composite_view.png new file mode 100644 index 000000000000..66215d9416b8 Binary files /dev/null and b/localization/ar/composite-view/etc/composite_view.png differ diff --git a/localization/ar/composite-view/etc/images/noparam.png b/localization/ar/composite-view/etc/images/noparam.png new file mode 100644 index 000000000000..2979cf2bc657 Binary files /dev/null and b/localization/ar/composite-view/etc/images/noparam.png differ diff --git a/localization/ar/composite-view/etc/images/threeparams.png b/localization/ar/composite-view/etc/images/threeparams.png new file mode 100644 index 000000000000..9338dedb8885 Binary files /dev/null and b/localization/ar/composite-view/etc/images/threeparams.png differ diff --git a/localization/ar/composite/README.md b/localization/ar/composite/README.md new file mode 100644 index 000000000000..a90040cc02ab --- /dev/null +++ b/localization/ar/composite/README.md @@ -0,0 +1,222 @@ +--- +title: Composite +shortTitle: Composite +category: Structural +language: ar +tag: + - Gang of Four + - Object composition + - Recursion +--- + +## أيضًا يُعرف بـ + +* شجرة الكائنات +* الهيكل المركب + +## الهدف + +تركيب الكائنات في هياكل شجرية لتمثيل التسلسل الهرمي "جزء-كامل". يسمح نمط المركب للعملاء بمعاملة الكائنات الفردية وتركيبات الكائنات بطريقة موحدة. + +## الشرح + +مثال واقعي + +> تتكون كل جملة من كلمات، وكل كلمة تتكون بدورها من أحرف. كل واحد من هذه الكائنات قابل للطباعة ويمكن أن يكون له شيء مطبوع قبله أو بعده، مثلًا الجملة دائمًا تنتهي بنقطة، والكلمة دائمًا تملك مسافة قبلها. + +بكلمات بسيطة + +> يسمح نمط المركب للعملاء بمعاملة الكائنات الفردية بطريقة موحدة. + +تقول ويكيبيديا + +> في هندسة البرمجيات، نمط المركب هو نمط تصميم تقسيمي. يصف هذا النمط أنه يجب معاملة مجموعة من الكائنات بنفس الطريقة التي يتم بها معاملة كائن واحد. الهدف من المركب هو "تركيب" الكائنات في هياكل شجرية لتمثيل التسلسل الهرمي "جزء-كامل". يسمح تنفيذ نمط التركيب للعملاء بمعاملة الكائنات الفردية وتركيبات الكائنات بطريقة موحدة. + +**مثال برمجي** + +باستخدام مثالنا السابق، هنا لدينا الفئة الأساسية `LetterComposite` وأنواع مختلفة من الكائنات القابلة للطباعة مثل `Letter`, `Word`, و `Sentence`. + + +```java +public abstract class LetterComposite { + + private final List children = new ArrayList<>(); + + public void add(LetterComposite letter) { + children.add(letter); + } + + public int count() { + return children.size(); + } + + protected void printThisBefore() { + } + + protected void printThisAfter() { + } + + public void print() { + printThisBefore(); + children.forEach(LetterComposite::print); + printThisAfter(); + } +} + +public class Letter extends LetterComposite { + + private final char character; + + public Letter(char c) { + this.character = c; + } + + @Override + protected void printThisBefore() { + System.out.print(character); + } +} + +public class Word extends LetterComposite { + + public Word(List letters) { + letters.forEach(this::add); + } + + public Word(char... letters) { + for (char letter : letters) { + this.add(new Letter(letter)); + } + } + + @Override + protected void printThisBefore() { + System.out.print(" "); + } +} + +public class Sentence extends LetterComposite { + + public Sentence(List words) { + words.forEach(this::add); + } + + @Override + protected void printThisAfter() { + System.out.print("."); + } +} +``` + +## لدينا الآن مرسل لنقل الرسائل: + +```java +public class Messenger { + + LetterComposite messageFromOrcs() { + + var words = List.of( + new Word('W', 'h', 'e', 'r', 'e'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'h', 'i', 'p'), + new Word('t', 'h', 'e', 'r', 'e'), + new Word('i', 's'), + new Word('a'), + new Word('w', 'a', 'y') + ); + + return new Sentence(words); + + } + + LetterComposite messageFromElves() { + + var words = List.of( + new Word('M', 'u', 'c', 'h'), + new Word('w', 'i', 'n', 'd'), + new Word('p', 'o', 'u', 'r', 's'), + new Word('f', 'r', 'o', 'm'), + new Word('y', 'o', 'u', 'r'), + new Word('m', 'o', 'u', 't', 'h') + ); + + return new Sentence(words); + + } + +} +``` + +## وبالتالي يمكن استخدامه كالتالي: + +```java +var messenger=new Messenger(); + + LOGGER.info("Message from the orcs: "); + messenger.messageFromOrcs().print(); + + LOGGER.info("Message from the elves: "); + messenger.messageFromElves().print(); +``` + +## مخرجات وحدة التحكم: + + + +``` +Message from the orcs: + Where there is a whip there is a way. +Message from the elves: + Much wind pours from your mouth. +``` + +## Diagrama de clases + +![alt text](./etc/composite.urm.png "Diagrama de clases compuestas") + +## Applicabilidad + +استخدم نمط **Composite** عندما: + +* ترغب في تمثيل الهياكل الجزئية للأشياء. +* ترغب في أن يتجاهل العملاء الفرق بين تراكيب الأشياء والأشياء الفردية. سيعالج العملاء جميع الكائنات في الهيكل المركب بشكل موحد. + +## الاستخدامات المعروفة + +* واجهات المستخدم الرسومية حيث يمكن للمكونات أن تحتوي على مكونات أخرى (مثل الألواح التي تحتوي على أزرار، تسميات، وألواح أخرى). +* تمثيلات أنظمة الملفات حيث يمكن للأدلة أن تحتوي على ملفات وأدلة أخرى. +* الهياكل التنظيمية حيث يمكن للقسم أن يحتوي على أقسام فرعية وموظفين. +* [java.awt.Container](http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html) + و [java.awt.Component](http://docs.oracle.com/javase/8/docs/api/java/awt/Component.html) +* شجرة المكونات [Apache Wicket](https://github.com/apache/wicket)، + انظر [Component](https://github.com/apache/wicket/blob/91e154702ab1ff3481ef6cbb04c6044814b7e130/wicket-core/src/main/java/org/apache/wicket/Component.java) + و [MarkupContainer](https://github.com/apache/wicket/blob/b60ec64d0b50a611a9549809c9ab216f0ffa3ae3/wicket-core/src/main/java/org/apache/wicket/MarkupContainer.java) + +## العواقب + +### الفوائد: + +* يبسط الكود الخاص بالعميل، حيث يمكنه التعامل مع الهياكل المركبة والأشياء الفردية بشكل موحد. +* يسهل إضافة أنواع جديدة من المكونات، حيث لا يتعين تعديل الكود الموجود. + +### العيوب: + +* قد يجعل التصميم عامًا جدًا. قد يكون من الصعب تقييد مكونات المركب. +* قد يصعب تحديد أنواع المكونات في المركب. + +## الأنماط المتعلقة + +* [Flyweight](https://java-design-patterns.com/patterns/flyweight/): يمكن لـ Composite استخدام Flyweight لمشاركة + مثيلات المكونات بين عدة مركبات. +* [Iterador](https://java-design-patterns.com/patterns/iterator/): يمكن استخدامه لتصفح الهياكل المركبة. +* [Visitante](https://java-design-patterns.com/patterns/visitor/): يمكن تطبيق عملية على الهيكل المركب. + +## الائتمانات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [Refactoring to Patterns](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [Pattern-Oriented Software Architecture, Volume 1: A System of Patterns](https://amzn.to/3xoLAmi) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3vBKXWb) diff --git a/localization/ar/composite/etc/composite.urm.png b/localization/ar/composite/etc/composite.urm.png new file mode 100644 index 000000000000..93c160f6450a Binary files /dev/null and b/localization/ar/composite/etc/composite.urm.png differ diff --git a/localization/ar/context-object/README.md b/localization/ar/context-object/README.md new file mode 100644 index 000000000000..77fd8d8b754c --- /dev/null +++ b/localization/ar/context-object/README.md @@ -0,0 +1,188 @@ +--- +title: Context object +shortTitle: Context object +category: Creational +language: ar +tags: + - Data access +--- + +## الاسم / التصنيف + +كائن السياق + +## يعرف أيضًا باسم + +السياق، تجميع السياق + +## الغرض + +فصل البيانات عن الفئات الخاصة بالبروتوكول وتخزين بيانات السياق في كائن مستقل عن التكنولوجيا الخاصة بالبروتوكول الأساسي. + +## الشرح + +مثال من العالم الواقعي + +> يحتوي هذا التطبيق على طبقات مختلفة موسومة مثل A وB وC، كل واحدة منها تستخرج معلومات محددة من سياق مشابه لاستخدامها لاحقًا في البرنامج. تمرير كل قطعة من المعلومات بشكل فردي سيكون غير فعال، لذا هناك حاجة إلى طريقة لتخزين وتمرير المعلومات بشكل فعال. +> بكلمات بسيطة + +> أنشئ كائنًا وخزن البيانات هناك، ثم مرر هذا الكائن حيثما كان مطلوبًا. + +[Core J2EE Patterns](http://corej2eepatterns.com/ContextObject.htm) يقول + +> استخدم كائن السياق (Context Object) لتغليف الحالة بطريقة مستقلة عن البروتوكول ليتم مشاركتها عبر تطبيقك. + +**مثال برمجي** + +نحدد البيانات التي يحتوي عليها كائن السياق (Context Object) للخدمة `ServiceContext`. + + +```Java +public class ServiceContext { + + String ACCOUNT_SERVICE, SESSION_SERVICE, SEARCH_SERVICE; + + public void setACCOUNT_SERVICE(String ACCOUNT_SERVICE) { + this.ACCOUNT_SERVICE = ACCOUNT_SERVICE; + } + + public void setSESSION_SERVICE(String SESSION_SERVICE) { + this.SESSION_SERVICE = SESSION_SERVICE; + } + + public void setSEARCH_SERVICE(String SEARCH_SERVICE) { + this.SEARCH_SERVICE = SEARCH_SERVICE; + } + + public String getACCOUNT_SERVICE() { + return ACCOUNT_SERVICE; + } + + public String getSESSION_SERVICE() { + return SESSION_SERVICE; + } + + public String getSEARCH_SERVICE() { + return SEARCH_SERVICE; + } + + public String toString() { return ACCOUNT_SERVICE + " " + SESSION_SERVICE + " " + SEARCH_SERVICE;} +} +``` + +يتم إنشاء واجهة `ServiceContextFactory` تُستخدم في أجزاء من التطبيق لإنشاء كائنات السياق. + + +```Java +public class ServiceContextFactory { + + public static ServiceContext createContext() { + return new ServiceContext(); + } +} +``` + +إنشاء كائن السياق في الطبقة الأولى `LayerA` والطبقة المجاورة `LayerB` حتى يتم استدعاء السياق في الطبقة الحالية `LayerC`، التي تقوم بترتيب الكائن. + + +```Java +public class LayerA { + + private static ServiceContext context; + + public LayerA() { + context = ServiceContextFactory.createContext(); + } + + public static ServiceContext getContext() { + return context; + } + + public void addAccountInfo(String accountService) { + context.setACCOUNT_SERVICE(accountService); + } +} + +public class LayerB { + + private static ServiceContext context; + + public LayerB(LayerA layerA) { + this.context = layerA.getContext(); + } + + public static ServiceContext getContext() { + return context; + } + + public void addSessionInfo(String sessionService) { + context.setSESSION_SERVICE(sessionService); + } +} + +public class LayerC { + + public static ServiceContext context; + + public LayerC(LayerB layerB) { + this.context = layerB.getContext(); + } + + public static ServiceContext getContext() { + return context; + } + + public void addSearchInfo(String searchService) { + context.setSEARCH_SERVICE(searchService); + } +} +``` + +إليك كائن السياق والطبقات قيد التنفيذ. + + +```Java +var layerA = new LayerA(); +layerA.addAccountInfo(SERVICE); +LOGGER.info("Context = {}",layerA.getContext()); +var layerB = new LayerB(layerA); +layerB.addSessionInfo(SERVICE); +LOGGER.info("Context = {}",layerB.getContext()); +var layerC = new LayerC(layerB); +layerC.addSearchInfo(SERVICE); +LOGGER.info("Context = {}",layerC.getContext()); +``` + +إخراج البرنامج: + + +```Java +Context = SERVICE null null +Context = SERVICE SERVICE null +Context = SERVICE SERVICE SERVICE +``` + +## رسم توضيحي للفئات + +![alt text](./etc/context-object.png "كائن السياق") + +## القابلية للتطبيق + +استخدم نمط كائن السياق (Context Object) لـ: + +* مشاركة المعلومات بين الطبقات المختلفة للنظام. +* فصل البيانات عن السياقات المحددة للبروتوكولات. +* عرض واجهات البرمجة ذات الصلة فقط ضمن السياق. + +## الاستخدامات المعروفة + +* [Spring: ApplicationContext](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html) +* [Oracle: SecurityContext](https://docs.oracle.com/javaee/7/api/javax/ws/rs/core/SecurityContext.html) +* [Oracle: ServletContext](https://docs.oracle.com/javaee/6/api/javax/servlet/ServletContext.html) + +## الفضل + +* [Core J2EE Design Patterns](https://amzn.to/3IhcY9w) +* [موقع Core J2EE Design Patterns - كائن السياق](http://corej2eepatterns.com/ContextObject.htm) +* [Allan Kelly - نمط تجميع السياق](https://accu.org/journals/overload/12/63/kelly_246/) +* [Arvid S. Krishna وآخرون - كائن السياق](https://www.dre.vanderbilt.edu/~schmidt/PDF/Context-Object-Pattern.pdf) diff --git a/localization/ar/context-object/etc/context-object.png b/localization/ar/context-object/etc/context-object.png new file mode 100644 index 000000000000..a1f670812f7b Binary files /dev/null and b/localization/ar/context-object/etc/context-object.png differ diff --git a/localization/ar/converter/README.md b/localization/ar/converter/README.md new file mode 100644 index 000000000000..cef553e1da32 --- /dev/null +++ b/localization/ar/converter/README.md @@ -0,0 +1,102 @@ +--- +title: Converter +shortTitle: Converter +category: Creational +language: ar +tag: + - Decoupling +--- + +## الهدف + +الهدف من نمط المحول (Converter) هو توفير وسيلة عامة وشائعة للتحويل الثنائي الاتجاه بين الأنواع المقابلة، مما يتيح تنفيذًا نظيفًا حيث لا يحتاج الأنواع إلى معرفة بعضها البعض. علاوة على ذلك، يقدم نمط المحول تعيينًا ثنائي الاتجاه للمجموعات الثنائية الاتجاه، مما يقلل من الكود المكرر إلى الحد الأدنى. + +## الشرح + +مثال من الحياة الواقعية + +> في التطبيقات الواقعية، غالبًا ما يحدث أن يتكون طبقة قاعدة البيانات من كيانات تحتاج إلى أن يتم تحويلها إلى DTO لاستخدامها في طبقة منطق الأعمال. يتم إجراء تحويل مشابه لعدد potentially ضخم من الفئات ونحتاج إلى طريقة عامة لتحقيق ذلك. + +ببساطة + +> يسهل نمط التحويل (Converter) تعيين الكائنات من فئة إلى كائنات من فئة أخرى. + +**مثال برمجي** + +نحتاج إلى حل عام لمشكلة التعيين. لذلك، دعونا نقدم محولًا عامًا. + + +```java +public class Converter { + + private final Function fromDto; + private final Function fromEntity; + + public Converter(final Function fromDto, final Function fromEntity) { + this.fromDto = fromDto; + this.fromEntity = fromEntity; + } + + public final U convertFromDto(final T dto) { + return fromDto.apply(dto); + } + + public final T convertFromEntity(final U entity) { + return fromEntity.apply(entity); + } + + public final List createFromDtos(final Collection dtos) { + return dtos.stream().map(this::convertFromDto).collect(Collectors.toList()); + } + + public final List createFromEntities(final Collection entities) { + return entities.stream().map(this::convertFromEntity).collect(Collectors.toList()); + } +} +``` + +## المحولات المتخصصة ترث من هذه الفئة الأساسية كما يلي. + + +```java +public class UserConverter extends Converter { + + public UserConverter() { + super(UserConverter::convertToEntity, UserConverter::convertToDto); + } + + private static UserDto convertToDto(User user) { + return new UserDto(user.getFirstName(), user.getLastName(), user.isActive(), user.getUserId()); + } + + private static User convertToEntity(UserDto dto) { + return new User(dto.getFirstName(), dto.getLastName(), dto.isActive(), dto.getEmail()); + } + +} +``` + +الآن يصبح التحويل بين `User` و `UserDto` أمرًا تافهًا. + + +```java +var userConverter = new UserConverter(); +var dtoUser = new UserDto("John", "Doe", true, "whatever[at]wherever.com"); +var user = userConverter.convertFromDto(dtoUser); +``` + +## مخطط الفئات + +![alt text](./etc/converter.png "نمط المحول") + +## القابلية للتطبيق + +استخدم نمط المحول في الحالات التالية: + +* عندما يكون لديك أنواع تتطابق منطقيًا مع بعضها البعض وتحتاج إلى تحويل الكيانات بينهما. +* عندما ترغب في توفير أشكال مختلفة لتحويل الأنواع اعتمادًا على السياق. +* كلما قدمت كائن نقل البيانات (DTO)، من المحتمل أن تحتاج إلى تحويله إلى معادله في المجال. + +## الاعتمادات + +* [نمط المحول في Java 8](http://www.xsolve.pl/blog/converter-pattern-in-java-8/) diff --git a/localization/ar/converter/etc/converter.png b/localization/ar/converter/etc/converter.png new file mode 100644 index 000000000000..01435ef5ae29 Binary files /dev/null and b/localization/ar/converter/etc/converter.png differ diff --git a/localization/ar/crtp/README.md b/localization/ar/crtp/README.md new file mode 100644 index 000000000000..f7ab6de3a210 --- /dev/null +++ b/localization/ar/crtp/README.md @@ -0,0 +1,139 @@ +--- +title: Curiously Recurring Template Pattern +language: ar +category: Structural +tag: +- Extensibility +- Instantiation +--- + +## الاسم / التصنيف + +نمط القالب المتكرر بغرابة + +## المعروف أيضًا باسم + +الحدود النوعية المتكررة، الجينيريك المتكرر + +## الهدف + +السماح للمكونات المشتقة بالوراثة من بعض الوظائف من مكون أساسي تكون متوافقة مع النوع المشتق. + +## الشرح + +مثال حقيقي + +> لتنظيم حدث للفنون القتالية المختلطة، من المهم التأكد من أن المباريات تتم بين رياضيين في نفس فئة الوزن. هذا يضمن تجنب المواجهات بين مقاتلين من أحجام مختلفة للغاية، مثل الوزن الثقيل ضد الوزن الخفيف. + +ببساطة + +> جعل بعض الطرق داخل نوع ما تقبل المعاملات الخاصة بأنواعه الفرعية. + +تقول ويكيبيديا + +> نمط القالب المتكرر بغرابة (CRTP) هو أسلوب برمجي، بدأ في C++، حيث تقوم فئة X بالاشتقاق من تطبيق قالب فئة باستخدام X نفسها كحجة للقالب. + +**مثال برمجي** + +لنحدد الواجهة العامة Fighter + + +```java +public interface Fighter { + + void fight(T t); + +} +``` + +تستخدم فئة `MMAFighter` لإنشاء مقاتلين يتميزون بفئة وزنهم. + + +```java +public class MmaFighter> implements Fighter { + + private final String name; + private final String surname; + private final String nickName; + private final String speciality; + + public MmaFighter(String name, String surname, String nickName, String speciality) { + this.name = name; + this.surname = surname; + this.nickName = nickName; + this.speciality = speciality; + } + + @Override + public void fight(T opponent) { + LOGGER.info("{} is going to fight against {}", this, opponent); + } + + @Override + public String toString() { + return name + " \"" + nickName + "\" " + surname; + } +} +``` + +فيما يلي بعض الأنواع الفرعية لـ `MMAFighter`: + + +```java +class MmaBantamweightFighter extends MmaFighter { + + public MmaBantamweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } + +} + +public class MmaHeavyweightFighter extends MmaFighter { + + public MmaHeavyweightFighter(String name, String surname, String nickName, String speciality) { + super(name, surname, nickName, speciality); + } + +} +``` + +يمكن للمقاتل أن يواجه خصمًا من نفس فئة الوزن، وإذا كان الخصم من فئة وزن مختلفة يحدث خطأ. + + +```java +MmaBantamweightFighter fighter1 = new MmaBantamweightFighter("Joe", "Johnson", "The Geek", "Muay Thai"); +MmaBantamweightFighter fighter2 = new MmaBantamweightFighter("Ed", "Edwards", "The Problem Solver", "Judo"); +fighter1.fight(fighter2); // This is fine + +MmaHeavyweightFighter fighter3 = new MmaHeavyweightFighter("Dave", "Davidson", "The Bug Smasher", "Kickboxing"); +MmaHeavyweightFighter fighter4 = new MmaHeavyweightFighter("Jack", "Jackson", "The Pragmatic", "Brazilian Jiu-Jitsu"); +fighter3.fight(fighter4); // This is fine too + +fighter1.fight(fighter3); // This will raise a compilation error +``` + +## Diagrama de clases + +![alt text](./etc/crtp.png "Diagrama de clases CRTP") + +## قابلية التطبيق + +استخدم نمط "القالب المتكرر بشكل غريب" عندما: + +* تواجه تعارضات في الأنواع عند ربط الأساليب في هيكل الكائنات +* ترغب في استخدام طريقة من الفئة معلمة يمكن أن تقبل الفئات الفرعية كوسائط، مما يسمح بتطبيقها على الكائنات التي ترث من هذه الفئة +* ترغب في أن تعمل بعض الأساليب فقط مع كائنات من نفس النوع، على سبيل المثال، لتحقيق المقارنة المتبادلة. + +## دروس تعليمية + +* [مدونة NuaH](https://nuah.livejournal.com/328187.html) +* إجابة من Yogesh Umesh Vaity على [ماذا يعني "الحدود التكرارية للنوع" في الأنواع العامة؟](https://stackoverflow.com/questions/7385949/what-does-recursive-type-bound-in-generics-mean) + +## الاستخدامات المعروفة + +* [java.lang.Enum](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Enum.html) + +## الاعتمادات + +* [كيف أفك تشفير "Enum>"؟](http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106) +* الفصل 5 Generics، العنصر 30 في [Effective Java](https://www.amazon.com/gp/product/0134685997/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0134685997&linkCode=as2&tag=javadesignpat-20&linkId=4e349f4b3ff8c50123f8147c828e53eb) diff --git a/localization/ar/crtp/etc/crtp.png b/localization/ar/crtp/etc/crtp.png new file mode 100644 index 000000000000..a348c8af6175 Binary files /dev/null and b/localization/ar/crtp/etc/crtp.png differ diff --git a/localization/ar/data-locality/README.md b/localization/ar/data-locality/README.md new file mode 100644 index 000000000000..9150d2b51bc6 --- /dev/null +++ b/localization/ar/data-locality/README.md @@ -0,0 +1,30 @@ +--- +title: Data Locality +shortTitle: Data Locality +category: Behavioral +language: ar +tag: + - Game programming + - Performance +--- + +## الهدف +يُسرع الوصول إلى الذاكرة من خلال تنظيم البيانات للاستفادة من ذاكرة التخزين المؤقت في وحدة المعالجة المركزية. + +تحتوي وحدات المعالجة المركزية الحديثة على ذاكرات تخزين مؤقت لتسريع الوصول إلى الذاكرة. يمكنها الوصول بسرعة أكبر إلى الذاكرة المجاورة لذاكرة تم الوصول إليها مؤخرًا. استفد من ذلك لتحسين الأداء عن طريق زيادة محلية البيانات، من خلال الحفاظ عليها في ذاكرة متجاورة بالترتيب الذي تعالجها فيه. + +## مخطط الفئات +![alt text](./etc/data-locality.urm.png "Data Locality pattern class diagram") + +## قابلية التطبيق + +* كما هو الحال مع معظم التحسينات، القاعدة الأولى لاستخدام نمط "محلية البيانات" هي عندما يكون لديك مشكلة في الأداء. +* مع هذا النمط بشكل خاص، ستحتاج أيضًا إلى التأكد من أن مشاكل الأداء ناتجة عن فقدان الذاكرة المؤقتة. + +## مثال من العالم الحقيقي + +* محرك الألعاب [Artemis](http://gamadu.com/artemis/) هو واحد من أولى وأكثر الأطر شهرة التي تستخدم معرفات بسيطة لكائنات اللعبة. + +## الاعتمادات + +* [أنماط برمجة الألعاب - أنماط التحسين: محلية البيانات](http://gameprogrammingpatterns.com/data-locality.html) diff --git a/localization/ar/data-locality/etc/data-locality.urm.png b/localization/ar/data-locality/etc/data-locality.urm.png new file mode 100644 index 000000000000..d19873739551 Binary files /dev/null and b/localization/ar/data-locality/etc/data-locality.urm.png differ diff --git a/localization/ar/decorator/README.md b/localization/ar/decorator/README.md new file mode 100644 index 000000000000..e36cc2695d79 --- /dev/null +++ b/localization/ar/decorator/README.md @@ -0,0 +1,164 @@ +--- +title: Decorator +shortTitle: Decorator +category: Structural +language: ar +tag: + - Gang of Four + - Extensibility +--- + +## المعروف أيضا باسم + +التغليف + +## الهدف + +إضافة مسؤوليات إضافية إلى كائن بطريقة ديناميكية. يوفر الزخرفة بديلاً مرنًا للوراثة لتوسيع الوظائف. + +## الشرح + +مثال من العالم الحقيقي + +> في التلال القريبة يعيش تيرول غاضب. عادة ما يكون يده عارية، ولكن في بعض الأحيان يحمل سلاحًا. لتسليح التيرول لا يتطلب الأمر إنشاء تيرول جديد بل تزيينه ديناميكيًا بسلاح مناسب. + +ببساطة + +> يتيح لك نمط الزخرفة تغيير سلوك كائن ديناميكيًا أثناء وقت التشغيل من خلال تغليفه في كائن من فئة الزخرفة. + +يقول Wikipedia + +> في البرمجة الكائنية التوجه، يعتبر نمط الزخرفة نمط تصميم يسمح بإضافة سلوك إلى كائن فردي، سواء بطريقة ثابتة أو ديناميكية، دون التأثير على سلوك الكائنات الأخرى من نفس الفئة. يُعتبر نمط الزخرفة مفيدًا للامتثال لمبدأ المسؤولية الفردية، حيث يسمح بتقسيم الوظائف بين الفئات ذات مجالات الاهتمام الفريدة، وكذلك لمبدأ الانفتاح-الإغلاق، من خلال السماح بتمديد وظائف الفئة دون تعديلها. + +**مثال برمجي** + +لنأخذ مثال التيرول. في البداية لدينا `SimpleTroll` الذي ينفذ الواجهة `Troll`: + + +```java +public interface Troll { + void atacar(); + int getPoderAtaque(); + void huirBatalla(); +} + +@Slf4j +public class SimpleTroll implements Troll { + + @Override + public void atacar() { + LOGGER.info("¡El troll intenta atraparte!"); + } + + @Override + public int getPoderAtaque() { + return 10; + } + + @Override + public void huirBatalla() { + LOGGER.info("¡El troll chilla de horror y huye!"); + } +} +``` + +بعد ذلك، نريد إضافة عصا للتيرول. يمكننا فعل ذلك بشكل ديناميكي باستخدام الزخرفة: + + +```java +@Slf4j +public class TrollConGarrote implements Troll { + + private final Troll decorado; + + public TrollConGarrote(Troll decorado) { + this.decorado = decorado; + } + + @Override + public void atacar() { + decorado.atacar(); + LOGGER.info("¡El troll te golpea con un garrote!"); + } + + @Override + public int getPoderAtaque() { + return decorado.getPoderAtaque() + 10; + } + + @Override + public void huirBatalla() { + decorado.huirBatalla(); + } +} +``` + +إليك التيرول في العمل: + + +```java +// التيرول البسيط +LOGGER.info("تيرول ذو مظهر بسيط يقترب."); +var troll = new SimpleTroll(); +troll.atacar(); +troll.huirBatalla(); +LOGGER.info("قوة التيرول البسيط: {}.\n", troll.getPoderAtaque()); + +// تغيير سلوك التيرول البسيط عن طريق إضافة ديكور +LOGGER.info("تيرول يحمل عصا ضخمة يفاجئك."); +var trollConGarrote = new TrollConGarrote(troll); +trollConGarrote.atacar(); +trollConGarrote.huirBatalla(); +LOGGER.info("قوة التيرول مع العصا: {}.\n", trollConGarrote.getPoderAtaque()); + +``` + +نتيجة البرنامج: + +```java +تيرول ذو مظهر بسيط يقترب. +!التيرول يحاول الإمساك بك! +!التيرول يصرخ من الرعب ويهرب! +قوة التيرول البسيط: 10. + +تيرول يحمل عصا ضخمة يفاجئك. +!التيرول يحاول الإمساك بك! +!التيرول يضربك بعصا ضخمة! +!التيرول يصرخ من الرعب ويهرب! +قوة التيرول مع العصا: 20. + +``` + +## مخطط الفئات + +![alt text](./etc/decorator.urm.png "مخطط فئات نمط الديكور") + +## القابلية للتطبيق + +يُستخدم نمط الديكور لـ: + +* إضافة مسؤوليات إلى كائنات فردية بشكل ديناميكي وشفاف، أي دون التأثير على الكائنات الأخرى. +* للمسؤوليات التي يمكن إزالتها. +* عندما يكون التوسيع بواسطة الفئات الفرعية غير عملي. في بعض الأحيان، قد يكون من الممكن إضافة عدد كبير من الامتدادات المستقلة التي قد تؤدي إلى انفجار في الفئات الفرعية لدعم كل مجموعة من التركيبات. أو قد تكون تعريفات الفئات مخفية أو غير متاحة للفئات الفرعية. + +## الدروس التعليمية + +* [دورة نمط الديكور](https://www.journaldev.com/1540/decorator-design-pattern-in-java-example) + +## الاستخدامات المعروفة + +* [java.io.InputStream](http://docs.oracle.com/javase/8/docs/api/java/io/InputStream.html)، [java.io.OutputStream](http://docs.oracle.com/javase/8/docs/api/java/io/OutputStream.html)، + [java.io.Reader](http://docs.oracle.com/javase/8/docs/api/java/io/Reader.html) + و [java.io.Writer](http://docs.oracle.com/javase/8/docs/api/java/io/Writer.html) +* [java.util.Collections#synchronizedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedCollection-java.util.Collection-) +* [java.util.Collections#unmodifiableXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#unmodifiableCollection-java.util.Collection-) +* [java.util.Collections#checkedXXX()](http://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#checkedCollection-java.util.Collection-java.lang.Class-) + +## الاعتمادات + +* [تصميم الأنماط: عناصر البرمجيات القابلة لإعادة الاستخدام](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [البرمجة الوظيفية في جافا: تسخير قوة تعبيرات لامبدا في جافا 8](https://www.amazon.com/gp/product/1937785467/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1937785467&linkCode=as2&tag=javadesignpat-20&linkId=7e4e2fb7a141631491534255252fd08b) +* [أنماط تصميم J2EE](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) +* [رأس الأنماط: دليل سهل الفهم](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) +* [إعادة الهيكلة إلى الأنماط](https://www.amazon.com/gp/product/0321213351/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321213351&linkCode=as2&tag=javadesignpat-20&linkId=2a76fcb387234bc71b1c61150b3cc3a7) +* [أنماط تصميم J2EE](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=f27d2644fbe5026ea448791a8ad09c94) diff --git a/localization/ar/decorator/etc/decorator.urm.png b/localization/ar/decorator/etc/decorator.urm.png new file mode 100644 index 000000000000..141c0563f0c6 Binary files /dev/null and b/localization/ar/decorator/etc/decorator.urm.png differ diff --git a/localization/ar/delegation/README.md b/localization/ar/delegation/README.md new file mode 100644 index 000000000000..1f0d84734dd1 --- /dev/null +++ b/localization/ar/delegation/README.md @@ -0,0 +1,119 @@ +--- +title: Delegation +shortTitle: Delegation +category: Structural +language: ar +tag: + - Decoupling +--- + +## يُعرف أيضًا بـ + +نمط الوكيل (Proxy Pattern) + +## الهدف + +هي تقنية يتم من خلالها أن يعبر كائن عن سلوك معين للخارج ولكنه في الواقع يفوض مسؤولية تنفيذ ذلك السلوك إلى كائن مرتبط. + +## الشرح + +مثال من الواقع + +> لنتخيل أن لدينا مغامرين يقاتلون ضد وحوش بأسلحة مختلفة حسب مهاراتهم وقدراتهم. يجب أن نكون قادرين على تجهيزهم بأسلحة مختلفة دون الحاجة لتعديل الشيفرة المصدرية لكل سلاح. يقوم نمط التفويض بهذا من خلال تفويض العمل بشكل ديناميكي إلى كائن معين يقوم بتنفيذ واجهة بها الطرق ذات الصلة. + +يقول ويكيبيديا + +> في البرمجة الشيئية، يشير التفويض إلى تقييم أحد الأعضاء (خاصية أو طريقة) لكائن (المستقبل) في سياق كائن آخر أصلي (المرسل). يمكن أن يتم التفويض بشكل صريح، عن طريق تمرير الكائن المرسل إلى الكائن المستقبل، وهو ما يمكن القيام به في أي لغة برمجة موجهة للكائنات؛ أو ضمنيًا، من خلال قواعد البحث عن الأعضاء في اللغة، وهو ما يتطلب دعم اللغة لهذه الوظيفة. + +**مثال برمجي** + +لدينا واجهة `Printer` وثلاثة تطبيقات هي `CanonPrinter`، `EpsonPrinter` و `HpPrinter`. + +```java +public interface Printer { + void print(final String message); +} + +@Slf4j +public class CanonPrinter implements Printer { + @Override + public void print(String message) { + LOGGER.info("Canon Printer : {}", message); + } +} + +@Slf4j +public class EpsonPrinter implements Printer { + @Override + public void print(String message) { + LOGGER.info("Epson Printer : {}", message); + } +} + +@Slf4j +public class HpPrinter implements Printer { + @Override + public void print(String message) { + LOGGER.info("HP Printer : {}", message); + } +} +``` + +El `PrinterController` puede ser utilizado como un `Printer` delegando cualquier trabajo manejado por este +a un objeto que la implemente. + +```java +public class PrinterController implements Printer { + + private final Printer printer; + + public PrinterController(Printer printer) { + this.printer = printer; + } + + @Override + public void print(String message) { + printer.print(message); + } +} +``` + +الآن في شفرة العميل، يمكن لوحدات تحكم الطابعة طباعة الرسائل بطرق مختلفة اعتمادًا على الكائن الذي يتم تفويض العمل إليه. + + +```java +private static final String MESSAGE_TO_PRINT = "hello world"; + +var hpPrinterController = new PrinterController(new HpPrinter()); +var canonPrinterController = new PrinterController(new CanonPrinter()); +var epsonPrinterController = new PrinterController(new EpsonPrinter()); + +hpPrinterController.print(MESSAGE_TO_PRINT); +canonPrinterController.print(MESSAGE_TO_PRINT); +epsonPrinterController.print(MESSAGE_TO_PRINT) +``` + +مخرجات البرنامج: + + +```java +HP Printer : hello world +Canon Printer : hello world +Epson Printer : hello world +``` + +## مخطط الفئات + +![alt text](./etc/delegation.png "Delegate") + +## القابلية للتطبيق + +استخدم نمط التفويض لتحقيق ما يلي: + +* تقليل ارتباط الأساليب بالفئة الخاصة بها +* مكونات تتصرف بشكل متطابق، مع مراعاة أن هذا الوضع قد يتغير في المستقبل. + +## الاعتمادات + +* [نمط التفويض: ويكيبيديا](https://en.wikipedia.org/wiki/Delegation_pattern) +* [نمط الوكيل: ويكيبيديا](https://en.wikipedia.org/wiki/Proxy_pattern) diff --git a/localization/ar/delegation/etc/delegation.png b/localization/ar/delegation/etc/delegation.png new file mode 100644 index 000000000000..375ef4d6b00f Binary files /dev/null and b/localization/ar/delegation/etc/delegation.png differ diff --git a/localization/ar/dependency-injection/README.md b/localization/ar/dependency-injection/README.md new file mode 100644 index 000000000000..9b45931c6f03 --- /dev/null +++ b/localization/ar/dependency-injection/README.md @@ -0,0 +1,101 @@ +--- +title: Dependency Injection +shortTitle: Dependency Injection +category: Creational +language: ar +tag: + - Decoupling +--- + +## الغرض + +حقن التبعيات هو نمط تصميم برمجي يتم فيه حقن واحدة أو أكثر من التبعيات (أو الخدمات) إلى كائن تابع (أو عميل) وتصبح جزءاً من حالة العميل. يفصل النمط بين إنشاء التبعيات للعميل وسلوكه الخاص، مما يسمح بتصاميم برامج منخفضة الارتباط وتلتزم بمبادئ عكس التحكم والمسؤولية الواحدة. + +## الشرح + +مثال من العالم الحقيقي + +> يحب الساحر العجوز ملء غليونه والتدخين من وقت لآخر. ومع ذلك، لا يريد أن يعتمد على علامة تجارية واحدة من التبغ، بل يحب أن يتمكن من الاستمتاع بها جميعاً بشكل قابل للتبادل. + +بكلمات بسيطة + +> حقن التبعيات يفصل إنشاء التبعيات للعميل عن سلوكه الخاص. + +تقول ويكيبيديا + +> في هندسة البرمجيات، حقن التبعيات هو تقنية يحصل فيها كائن على كائنات أخرى يعتمد عليها. تُسمى هذه الكائنات الأخرى بالتبعيات. + +**مثال برمجي** + +لنقم أولاً بتقديم واجهة التبغ `Tobacco` والعلامات التجارية المحددة. + + +```java +@Slf4j +public abstract class Tobacco { + + public void smoke(Wizard wizard) { + LOGGER.info("{} smoking {}", wizard.getClass().getSimpleName(), + this.getClass().getSimpleName()); + } +} + +public class SecondBreakfastTobacco extends Tobacco { +} + +public class RivendellTobacco extends Tobacco { +} + +public class OldTobyTobacco extends Tobacco { +} +``` + +التالي هو واجهة `Wizard` وتنفيذها. + + +```java +public interface Wizard { + + void smoke(); +} + +public class AdvancedWizard implements Wizard { + + private final Tobacco tobacco; + + public AdvancedWizard(Tobacco tobacco) { + this.tobacco = tobacco; + } + + @Override + public void smoke() { + tobacco.smoke(this); + } +} +``` + +وأخيراً يمكننا أن نثبت مدى سهولة إعطاء التبغ `Tobacco` لأي علامة تجارية قديمة للساحر. + + +```java + var advancedWizard = new AdvancedWizard(new SecondBreakfastTobacco()); + advancedWizard.smoke(); +``` + +## Class Diagram + +![alt text](./etc/dependency-injection.png "Dependency Injection") + +## Applicability + +Use the Dependency Injection pattern when: + +* You need to eliminate the knowledge of the concrete implementation of the object. +* To allow unit testing of classes in isolation using mock objects or stubs. + +## Credits + +* [Dependency Injection Principles, Practices, and Patterns](https://www.amazon.com/gp/product/161729473X/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=161729473X&linkId=57079257a5c7d33755493802f3b884bd) +* [Clean Code: A Handbook of Agile Software Craftsmanship](https://www.amazon.com/gp/product/0132350882/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0132350882&linkCode=as2&tag=javadesignpat-20&linkId=2c390d89cc9e61c01b9e7005c7842871) +* [Java 9 Dependency Injection: Write loosely coupled code with Spring 5 and Guice](https://www.amazon.com/gp/product/1788296257/ref=as_li_tl?ie=UTF8&tag=javadesignpat-20&camp=1789&creative=9325&linkCode=as2&creativeASIN=1788296257&linkId=4e9137a3bf722a8b5b156cce1eec0fc1) +* [Google Guice: Agile Lightweight Dependency Injection Framework](https://www.amazon.com/gp/product/1590599977/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=javadesignpat-20&creative=9325&linkCode=as2&creativeASIN=1590599977&linkId=3b10c90b7ba480a1b7777ff38000f956) diff --git a/localization/ar/dependency-injection/etc/dependency-injection.png b/localization/ar/dependency-injection/etc/dependency-injection.png new file mode 100644 index 000000000000..2a92c9eb228b Binary files /dev/null and b/localization/ar/dependency-injection/etc/dependency-injection.png differ diff --git a/localization/ar/dirty-flag/README.md b/localization/ar/dirty-flag/README.md new file mode 100644 index 000000000000..4c9b5742e942 --- /dev/null +++ b/localization/ar/dirty-flag/README.md @@ -0,0 +1,28 @@ +--- +title: Dirty Flag +shortTitle: Dirty Flag +category: Behavioral +language: ar +tag: + - Game programming + - Performance +--- + +## Also known as +* IsDirty pattern + +## Purpose +Avoid the costly re-acquisition of resources. Resources retain their identity, are stored in some fast-access storage, and are reused to avoid having to acquire them again. + +## Class Diagram +![alt text](./etc/dirty-flag.png "Dirty Flag") + +## Applicability +Use the Dirty Flag pattern when + +* The repetitive acquisition, initialization, and release of the same resource causes unnecessary performance overhead. + +## Credits + +* [Design Patterns: Dirty Flag](https://www.takeupcode.com/podcast/89-design-patterns-dirty-flag/) +* [J2EE Design Patterns](https://www.amazon.com/gp/product/0596004273/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596004273&linkCode=as2&tag=javadesignpat-20&linkId=48d37c67fb3d845b802fa9b619ad8f31) diff --git a/localization/ar/dirty-flag/etc/dirty-flag.png b/localization/ar/dirty-flag/etc/dirty-flag.png new file mode 100644 index 000000000000..98d4f679d17c Binary files /dev/null and b/localization/ar/dirty-flag/etc/dirty-flag.png differ diff --git a/localization/ar/double-buffer/README.md b/localization/ar/double-buffer/README.md new file mode 100644 index 000000000000..46b47a33ef48 --- /dev/null +++ b/localization/ar/double-buffer/README.md @@ -0,0 +1,245 @@ +--- +title: Double Buffer +shortTitle: Double Buffer +category: Behavioral +language: ar +tag: + - Performance + - Game programming +--- + +## الهدف + +الذاكرة المزدوجة هي مصطلح يُستخدم لوصف جهاز يحتوي على مخزنين. استخدام المخازن المتعددة يزيد من الأداء العام للجهاز ويساعد في تجنب الاختناقات. يُظهر هذا المثال استخدام الذاكرة المزدوجة في الرسومات. يُستخدم لعرض صورة أو إطار بينما يتم تخزين إطار آخر في المخزن ليتم عرضه لاحقًا. هذه الطريقة تجعل الرسوم المتحركة والألعاب تبدو أكثر واقعية مقارنة بتلك التي تُعرض باستخدام الذاكرة الفردية. + +## الشرح + +مثال من الحياة الواقعية +> مثال نموذجي، ويجب أن تتعامل معه جميع محركات الألعاب، هو التقديم. عندما يرسم اللعبة العالم الذي يراه المستخدمون، تقوم بذلك قطعة قطعة: الجبال البعيدة، التلال المتدحرجة، الأشجار، كل منها على حدة. إذا رأى المستخدم كيف يتم رسم العرض بشكل تدريجي، فسوف تنكسر الوهم لعالم متماسك. يجب تحديث المشهد بسلاسة وسرعة، مع عرض سلسلة من الإطارات المكتملة، يظهر كل منها فورًا. يحل التقديم المزدوج هذه المشكلة. + +ببساطة +> يضمن حالة يتم عرضها بشكل صحيح بينما يتم تعديل تلك الحالة تدريجيًا. يتم استخدامه كثيرًا في الرسومات الحاسوبية. + +ويكيبيديا تقول +> في علوم الكمبيوتر، التخزين في الذاكرة المتعددة هو استخدام أكثر من مخزن واحد لحمل كتلة من البيانات، بحيث يرى "القارئ" نسخة كاملة (على الرغم من أنها قديمة) من البيانات، بدلاً من نسخة محدثة جزئيًا من البيانات التي يقوم "الكاتب" بإنشائها. يُستخدم ذلك كثيرًا في الصور الحاسوبية. + +**مثال برمجي** + +واجهة `Buffer` التي تضمن الوظائف الأساسية للمخزن. + +```java +/** + * Buffer interface. + */ +public interface Buffer { + + /** + * Clear the pixel in (x, y). + * + * @param x X coordinate + * @param y Y coordinate + */ + void clear(int x, int y); + + /** + * Draw the pixel in (x, y). + * + * @param x X coordinate + * @param y Y coordinate + */ + void draw(int x, int y); + + /** + * Clear all the pixels. + */ + void clearAll(); + + /** + * Get all the pixels. + * + * @return pixel list + */ + Pixel[] getPixels(); + +} +``` + +إحدى تطبيقات واجهة `Buffer`. + + +```java +/** + * FrameBuffer implementation class. + */ +public class FrameBuffer implements Buffer { + + public static final int WIDTH = 10; + public static final int HEIGHT = 8; + + private final Pixel[] pixels = new Pixel[WIDTH * HEIGHT]; + + public FrameBuffer() { + clearAll(); + } + + @Override + public void clear(int x, int y) { + pixels[getIndex(x, y)] = Pixel.WHITE; + } + + @Override + public void draw(int x, int y) { + pixels[getIndex(x, y)] = Pixel.BLACK; + } + + @Override + public void clearAll() { + Arrays.fill(pixels, Pixel.WHITE); + } + + @Override + public Pixel[] getPixels() { + return pixels; + } + + private int getIndex(int x, int y) { + return x + WIDTH * y; + } +} +``` + +```java +/** + * Pixel enum. Each pixel can be white (not drawn) or black (drawn). + */ +public enum Pixel { + + WHITE, BLACK; +} +``` + +`Scene` representa la escena del juego en la que ya se ha renderizado el búfer actual. + +```java +/** + * Scene class. Render the output frame. + */ +@Slf4j +public class Scene { + + private final Buffer[] frameBuffers; + + private int current; + + private int next; + + /** + * Constructor of Scene. + */ + public Scene() { + frameBuffers = new FrameBuffer[2]; + frameBuffers[0] = new FrameBuffer(); + frameBuffers[1] = new FrameBuffer(); + current = 0; + next = 1; + } + + /** + * Draw the next frame. + * + * @param coordinateList list of pixels of which the color should be black + */ + public void draw(List> coordinateList) { + LOGGER.info("Start drawing next frame"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + frameBuffers[next].clearAll(); + coordinateList.forEach(coordinate -> { + var x = coordinate.getKey(); + var y = coordinate.getValue(); + frameBuffers[next].draw(x, y); + }); + LOGGER.info("Swap current and next buffer"); + swap(); + LOGGER.info("Finish swapping"); + LOGGER.info("Current buffer: " + current + " Next buffer: " + next); + } + + public Buffer getBuffer() { + LOGGER.info("Get current buffer: " + current); + return frameBuffers[current]; + } + + private void swap() { + current = current ^ next; + next = current ^ next; + current = current ^ next; + } + +} +``` + +```java +public static void main(String[] args) { + final var scene = new Scene(); + var drawPixels1 = List.of(new MutablePair<>(1, 1), new MutablePair<>(5, 6), new MutablePair<>(3, 2)); + scene.draw(drawPixels1); + var buffer1 = scene.getBuffer(); + printBlackPixelCoordinate(buffer1); + + var drawPixels2 = List.of(new MutablePair<>(3, 7), new MutablePair<>(6, 1)); + scene.draw(drawPixels2); + var buffer2 = scene.getBuffer(); + printBlackPixelCoordinate(buffer2); +} + +private static void printBlackPixelCoordinate(Buffer buffer) { + StringBuilder log = new StringBuilder("Black Pixels: "); + var pixels = buffer.getPixels(); + for (var i = 0; i < pixels.length; ++i) { + if (pixels[i] == Pixel.BLACK) { + var y = i / FrameBuffer.WIDTH; + var x = i % FrameBuffer.WIDTH; + log.append(" (").append(x).append(", ").append(y).append(")"); + } + } + LOGGER.info(log.toString()); +} +``` + +مخرجات وحدة التحكم + + +```text +[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1 +[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer +[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0 +[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 1 +[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (1, 1) (3, 2) (5, 6) +[main] INFO com.iluwatar.doublebuffer.Scene - Start drawing next frame +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 1 Next buffer: 0 +[main] INFO com.iluwatar.doublebuffer.Scene - Swap current and next buffer +[main] INFO com.iluwatar.doublebuffer.Scene - Finish swapping +[main] INFO com.iluwatar.doublebuffer.Scene - Current buffer: 0 Next buffer: 1 +[main] INFO com.iluwatar.doublebuffer.Scene - Get current buffer: 0 +[main] INFO com.iluwatar.doublebuffer.App - Black Pixels: (6, 1) (3, 7) +``` + +## مخطط الفئات + +![alt text](./etc/double-buffer.urm.png "مخطط فئة نمط المخزن المؤقت المزدوج") + +## القابلية للتطبيق + +هذا النمط هو أحد الأنماط التي ستعرف متى تحتاج إليها. إذا كان لديك نظام يفتقر إلى المخزن المؤقت المزدوج، فمن المحتمل أن يبدو بشكل غير صحيح مرئيًا (تمزق الصورة، إلخ) أو سيعمل بشكل غير صحيح. ولكن قول "ستعرف متى تحتاج إليه" لا يعطي الكثير من التوضيح. بشكل أكثر تحديدًا، هذا النمط مناسب عندما يكون كل هذا صحيحًا: + +- لدينا حالة يتم تعديلها بشكل تدريجي. +- يمكن الوصول إلى نفس الحالة في منتصف التعديل. +- نريد تجنب أن يرى الكود الذي يصل إلى الحالة العمل الجاري. +- نريد أن نتمكن من قراءة الحالة دون الحاجة إلى الانتظار أثناء الكتابة. + +## المصادر + +* [Game Programming Patterns - Double Buffer](http://gameprogrammingpatterns.com/double-buffer.html) + diff --git a/localization/ar/double-buffer/etc/double-buffer.urm.png b/localization/ar/double-buffer/etc/double-buffer.urm.png new file mode 100644 index 000000000000..072ec4dad8be Binary files /dev/null and b/localization/ar/double-buffer/etc/double-buffer.urm.png differ diff --git a/localization/ar/embedded-value/README.md b/localization/ar/embedded-value/README.md new file mode 100644 index 000000000000..c147fef4edab --- /dev/null +++ b/localization/ar/embedded-value/README.md @@ -0,0 +1,131 @@ +--- +title: Embedded Value +shortTitle: Embedded Value +category: Structural +language: ar +tag: + - Data Access + - Enterprise Application Pattern +--- + +## معروف أيضًا باسم + +التخصيص المدمج، المُركب + +## الهدف + +العديد من الكائنات الصغيرة تكون منطقية في نظام البرمجة الكائنية ولا تكون منطقية كجداول في قاعدة بيانات. القيمة المدمجة تخصيص قيم كائن إلى حقول سجل الكائن المالك. + +## الشرح + +مثال من الواقع + +> بعض الأمثلة تشمل الكائنات النقدية والفترات الزمنية. على الرغم من أن التفكير الافتراضي هو تخزين كائن كجدول، إلا أنه لا أحد في عقله السليم يرغب في جدول للقيم النقدية. +> مثال آخر هو الطلبات عبر الإنترنت التي تحتوي على عنوان الشحن مثل الشارع، المدينة، الدولة. نقوم بتخصيص هذه القيم من كائن عنوان الشحن إلى حقول سجل كائن الطلب. + +بكلمات بسيطة + +> يسمح نمط القيم المدمجة بتخصيص كائن لعدة حقول في جدول كائن آخر. + +**مثال برمجي** + +لنأخذ مثالًا من طلب عبر الإنترنت حيث لدينا تفاصيل العنصر المطلوب وعنوان الشحن. لدينا +عنوان الشحن مدمج في كائن الطلب. ولكن في قاعدة البيانات نقوم بتخصيص قيم عنوان الشحن في سجل الطلب بدلاً من إنشاء جدول منفصل لعنوان الشحن واستخدام مفتاح خارجي للإشارة إلى كائن الطلب. + +أولاً، لدينا كائنات `Order` و `ShippingAddress`. + + +```java +public class Order { + + private int id; + private String item; + private String orderedBy; + private ShippingAddress ShippingAddress; + + public Order(String item, String orderedBy, ShippingAddress ShippingAddress) { + this.item = item; + this.orderedBy = orderedBy; + this.ShippingAddress = ShippingAddress; + } +} +``` + +```java +public class ShippingAddress { + + private String city; + private String state; + private String pincode; + + public ShippingAddress(String city, String state, String pincode) { + this.city = city; + this.state = state; + this.pincode = pincode; + } +} +``` + +الآن، علينا إنشاء جدول واحد فقط للطلب مع الحقول الخاصة بسمات عنوان الشحن. + + +```Sql +CREATE TABLE Orders (Id INT AUTO_INCREMENT, item VARCHAR(50) NOT NULL, orderedBy VARCHAR(50) city VARCHAR(50), state VARCHAR(50), pincode CHAR(6) NOT NULL, PRIMARY KEY(Id)) +``` + +أثناء إجراء الاستفسارات والإدخالات في قاعدة البيانات، نقوم بتغليف وفك تغليف تفاصيل عناوين الشحن. + + +```java +final String INSERT_ORDER = "INSERT INTO Orders (item, orderedBy, city, state, pincode) VALUES (?, ?, ?, ?, ?)"; + +public boolean insertOrder(Order order) throws Exception { + var insertOrder = new PreparedStatement(INSERT_ORDER); + var address = order.getShippingAddress(); + conn.setAutoCommit(false); + insertIntoOrders.setString(1, order.getItem()); + insertIntoOrders.setString(2, order.getOrderedBy()); + insertIntoOrders.setString(3, address.getCity()); + insertIntoOrders.setString(4, address.getState()); + insertIntoOrders.setString(5, address.getPincode()); + + var affectedRows = insertIntoOrders.executeUpdate(); + if(affectedRows == 1){ + Logger.info("Inserted successfully"); + }else{ + Logger.info("Couldn't insert " + order); + } +} +``` + +## مخطط الفئات + +![alt text](./etc/embedded-value.urm.png "مخطط فئة القيمة المدمجة") + +## القابلية للتطبيق + +استخدم نمط القيمة المدمجة عندما: + +* تكون العديد من الكائنات الصغيرة ذات معنى في نظام OO ولكن ليس لها معنى كجداول في قاعدة بيانات. +* الحالات الأبسط للقيمة المدمجة هي الكائنات ذات القيم الواضحة والبسيطة مثل المال ونطاق التواريخ. +* إذا كنت تقوم بربط إلى مخطط موجود، يمكنك استخدام هذا النمط عندما تحتوي إحدى الجداول على بيانات ترغب في تقسيمها إلى أكثر من كائن في الذاكرة. قد يحدث هذا عندما ترغب في استخراج بعض السلوك في نموذج الكائنات. +* في معظم الحالات، ستستخدم القيمة المدمجة فقط في كائن مرجعي عندما تكون العلاقة بينهما ذات قيمة واحدة في كلا الطرفين (علاقة واحد إلى واحد). + +## الدروس التعليمية + +* [Dzone](https://dzone.com/articles/practical-php-patterns/practical-php-patterns-3) +* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html) +* [Five's Weblog](https://powerdream5.wordpress.com/2007/10/09/embedded-value/) + +## العواقب + +* الميزة الكبرى للقيمة المدمجة هي أنها تسمح بإجراء استفسارات SQL ضد قيم الكائن التابع. +* الكائن ذو القيمة المدمجة ليس له سلوك استمرارية. +* عند استخدام هذا، يجب أن تكون حذرًا من أن أي تغيير في التابع سيجعل المالك يعتبره "متسخًا" — وهو ما لا يمثل مشكلة مع الكائنات ذات القيمة التي يتم استبدالها في المالك. +* مشكلة أخرى هي التحميل والحفظ. إذا كنت تقوم بتحميل ذاكرة الكائن المدمج فقط عندما تقوم بتحميل المالك، فهذا حجة لحفظ كلاهما في نفس الجدول. +* قضية أخرى هي ما إذا كنت ترغب في الوصول إلى بيانات الكائنات المدمجة بشكل منفصل عبر SQL. قد يكون هذا مهمًا إذا كنت تقوم بإعداد تقارير عبر SQL وليس لديك قاعدة بيانات منفصلة للتقارير. + +## الائتمان + +* [Fowler, Martin - Patterns of enterprise application architecture-Addison-Wesley](https://www.amazon.com/Patterns-Enterprise-Application-Architecture-Martin/dp/0321127420) +* [Ram N Java](https://ramj2ee.blogspot.com/2013/08/embedded-value-design-pattern.html) diff --git a/localization/ar/embedded-value/etc/embedded-value.urm.png b/localization/ar/embedded-value/etc/embedded-value.urm.png new file mode 100644 index 000000000000..52d3ffb62352 Binary files /dev/null and b/localization/ar/embedded-value/etc/embedded-value.urm.png differ diff --git a/localization/ar/event-aggregator/README.md b/localization/ar/event-aggregator/README.md new file mode 100644 index 000000000000..03eae11fbe3c --- /dev/null +++ b/localization/ar/event-aggregator/README.md @@ -0,0 +1,167 @@ +--- +title: Event Aggregator +shortTitle: Event Aggregator +category: Structural +language: ar +tag: + - Reactive +--- + +## الاسم + +مجمع الأحداث + +## النية + +قد يصبح النظام الذي يحتوي على العديد من الكائنات معقدًا عندما يريد العميل الاشتراك في الأحداث. يجب على العميل +البحث والتسجيل في +كل كائن على حدة، وإذا كان كل كائن يحتوي على العديد من الأحداث فإن كل حدث يتطلب اشتراكًا +منفصلًا. يعمل مجمع الأحداث كمصدر واحد +للأحداث للعديد من الكائنات. يقوم بالتسجيل في جميع الأحداث للكائنات المتعددة مما يسمح للعملاء +بالتسجيل فقط مع المجمع. + +## الشرح + +مثال واقعي + +> يجلس الملك جوفري على عرش الحديد ويحكم الممالك السبع في وينترفل. يحصل على معظم معلوماته الهامة من يد الملك، +> وهو ثاني أقوى شخص في المملكة. يد الملك لديها العديد من المستشارين المقربين الذين يوفرون له معلومات هامة حول الأحداث التي تحدث في المملكة. + +بكلمات بسيطة + +> مجمع الأحداث هو وسيط للأحداث يقوم بجمع الأحداث من مصادر متعددة وتقديمها للمراقبين المسجلين. + +**مثال برمجي** + +في مثالنا البرمجي، نعرض تنفيذ نمط مجمع الأحداث. بعض الكائنات +هي مستمعون للأحداث، وبعضها الآخر هو مرسلو أحداث، والمجمع للأحداث يقوم بكلتا الوظيفتين. + + +```java +public interface EventObserver { + void onEvent(Event e); +} + +public abstract class EventEmitter { + + private final Map> observerLists; + + public EventEmitter() { + observerLists = new HashMap<>(); + } + + public final void registerObserver(EventObserver obs, Event e) { + ... + } + + protected void notifyObservers(Event e) { + ... + } +} +``` + +`KingJoffrey` يستمع إلى أحداث `KingsHand`. + + +```java +@Slf4j +public class KingJoffrey implements EventObserver { + @Override + public void onEvent(Event e) { + LOGGER.info("Received event from the King's Hand: {}", e.toString()); + } +} +``` + +`ReyMano` يستمع إلى الأحداث من مرؤوسيه `LordBaelish`، `LordVarys` و `Scout`. +ما يسمعه منهم، ينقله إلى "الملك جوفري". + + +```java +public class KingsHand extends EventEmitter implements EventObserver { + + public KingsHand() { + } + + public KingsHand(EventObserver obs, Event e) { + super(obs, e); + } + + @Override + public void onEvent(Event e) { + notifyObservers(e); + } +} +``` + +على سبيل المثال، `LordVarys` يجد خائنًا كل يوم أحد ويقوم بإبلاغ `KingsHand`. + + +```java +@Slf4j +public class LordVarys extends EventEmitter implements EventObserver { + @Override + public void timePasses(Weekday day) { + if (day == Weekday.SATURDAY) { + notifyObservers(Event.TRAITOR_DETECTED); + } + } +} +``` + +الجزء التالي يوضح كيفية بناء وربط الكائنات. + + +```java + var kingJoffrey = new KingJoffrey(); + + var kingsHand = new KingsHand(); + kingsHand.registerObserver(kingJoffrey, Event.TRAITOR_DETECTED); + kingsHand.registerObserver(kingJoffrey, Event.STARK_SIGHTED); + kingsHand.registerObserver(kingJoffrey, Event.WARSHIPS_APPROACHING); + kingsHand.registerObserver(kingJoffrey, Event.WHITE_WALKERS_SIGHTED); + + var varys = new LordVarys(); + varys.registerObserver(kingsHand, Event.TRAITOR_DETECTED); + varys.registerObserver(kingsHand, Event.WHITE_WALKERS_SIGHTED); + + var scout = new Scout(); + scout.registerObserver(kingsHand, Event.WARSHIPS_APPROACHING); + scout.registerObserver(varys, Event.WHITE_WALKERS_SIGHTED); + + var baelish = new LordBaelish(kingsHand, Event.STARK_SIGHTED); + + var emitters = List.of( + kingsHand, + baelish, + varys, + scout + ); + + Arrays.stream(Weekday.values()) + .>map(day -> emitter -> emitter.timePasses(day)) + .forEachOrdered(emitters::forEach); +``` + +الناتج في وحدة التحكم بعد تنفيذ المثال. + +``` +18:21:52.955 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Warships approaching +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: White walkers sighted +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Stark sighted +18:21:52.960 [main] INFO com.iluwatar.event.aggregator.KingJoffrey - Received event from the King's Hand: Traitor detected +``` + +## قابلية الاستخدام + +استخدم نمط "مجمّع الأحداث" عندما + +* يعد مجمّع الأحداث خيارًا جيدًا عندما يكون لديك العديد من الكائنات التي تعد مصادر محتملة للأحداث. بدلاً من جعل المراقب يتعامل مع التسجيل مع كل منها، يمكنك مركزية منطق التسجيل في مجمّع الأحداث. بالإضافة إلى تبسيط التسجيل، يبسط مجمّع الأحداث أيضًا مشكلات إدارة الذاكرة في استخدام المراقبين. + +## الأنماط المتعلقة + +* [Observer](https://java-design-patterns.com/patterns/observer/) + +## الاعتمادات + +* [مارتن فاولر - مجمّع الأحداث](http://martinfowler.com/eaaDev/EventAggregator.html) diff --git a/localization/ar/event-aggregator/etc/classes.png b/localization/ar/event-aggregator/etc/classes.png new file mode 100644 index 000000000000..295719ea3712 Binary files /dev/null and b/localization/ar/event-aggregator/etc/classes.png differ diff --git a/localization/ar/extension-objects/README.md b/localization/ar/extension-objects/README.md new file mode 100644 index 000000000000..0f96c8a0a516 --- /dev/null +++ b/localization/ar/extension-objects/README.md @@ -0,0 +1,149 @@ +--- +title: Extension objects +shortTitle: Extension objects +category: Behavioral +language: ar +tag: + - Extensibility +--- + +# نمط كائنات التوسيع (Extension Objects Pattern) + +## الهدف +التنبؤ بأنه يجب توسيع واجهة كائن ما في المستقبل. يتم تعريف الواجهات الإضافية من خلال كائنات التوسيع (Extension objects). + +## الشرح +مثال واقعي + +> افترض أنك تطور لعبة تعتمد على Java لعميل، وأثناء عملية التطوير، يُقترح عليك إضافة وظائف جديدة. يسمح لك نمط كائنات التوسيع بتكييف برنامجك مع التغييرات غير المتوقعة مع الحد الأدنى من إعادة الهيكلة، خاصة عند دمج وظائف إضافية في مشروعك. + +ببساطة + +> يتم استخدام نمط كائنات التوسيع لإضافة وظائف ديناميكيًا إلى الكائنات دون تعديل فئاتها الرئيسية. إنه نمط تصميم سلوكي يستخدم لإضافة وظائف جديدة إلى الفئات والكائنات الموجودة داخل البرنامج. يوفر هذا النمط للمبرمجين القدرة على تمديد/تعديل وظائف الفئات دون الحاجة إلى إعادة هيكلة الكود المصدر الحالي. + +تقول ويكيبيديا + +> في البرمجة الكائنية التوجه، يعتبر نمط كائنات التوسيع نمط تصميم يضاف إلى كائن بعد أن يتم تجميع الكائن الأصلي. الكائن المعدل غالبًا ما يكون فئة أو نموذجًا أو نوعًا. تعتبر أنماط كائنات التوسيع سمة من سمات بعض لغات البرمجة الكائنية التوجه. لا يوجد فرق نحوي بين استدعاء طريقة توسيع وطريقة معرفة في تعريف النوع. + +**مثال برمجي** + +الهدف من استخدام نمط كائنات التوسيع (Extension objects) هو تنفيذ ميزات/وظائف جديدة دون الحاجة إلى إعادة هيكلة كل فئة. +توضح الأمثلة التالية استخدام هذا النمط في فئة "العدو" التي تمتد من "الكيان" داخل لعبة: + +الفئة الرئيسية للتطبيق التي يتم تشغيل برنامجنا منها. + + +```java +public class App { + public static void main(String[] args) { + Entity enemy = new Enemy("Enemy"); + checkExtensionsForEntity(enemy); + } + + private static void checkExtensionsForEntity(Entity entity) { + Logger logger = Logger.getLogger(App.class.getName()); + String name = entity.getName(); + Function func = (e) -> () -> logger.info(name + " without " + e); + + String extension = "EnemyExtension"; + Optional.ofNullable(entity.getEntityExtension(extension)) + .map(e -> (EnemyExtension) e) + .ifPresentOrElse(EnemyExtension::extendedAction, func.apply(extension)); + } +} +``` +فئة العدو مع الإجراءات الأولية والتوسعات. + + +```java +class Enemy extends Entity { + public Enemy(String name) { + super(name); + } + + @Override + protected void performInitialAction() { + super.performInitialAction(); + System.out.println("Enemy wants to attack you."); + } + + @Override + public EntityExtension getEntityExtension(String extensionName) { + if (extensionName.equals("EnemyExtension")) { + return Optional.ofNullable(entityExtension).orElseGet(EnemyExtension::new); + } + return super.getEntityExtension(extensionName); + } +} +``` + +فئة EnemyExtension مع إعادة كتابة طريقة extendAction(). + + +```java +class EnemyExtension implements EntityExtension { + @Override + public void extendedAction() { + System.out.println("Enemy has advanced towards you!"); + } +} +``` + +فئة الكائن التي سيتم توسيعها بواسطة العدو. + + +```java +class Entity { + private String name; + protected EntityExtension entityExtension; + + public Entity(String name) { + this.name = name; + performInitialAction(); + } + + protected void performInitialAction() { + System.out.println(name + " performs the initial action."); + } + + public EntityExtension getEntityExtension(String extensionName) { + return null; + } + + public String getName() { + return name; + } +} +``` +واجهة `EntityExtension` التي ستستخدم `EnemyExtension`. + + +```java +interface EntityExtension { + void extendedAction(); +} +``` +إخراج البرنامج: + + +```markdown +Enemy performs the initial action. +Enemy wants to attack you. +Enemy has advanced towards you! +``` +في هذا المثال، يسمح نمط "كائنات التمديد" (Extension Objects) لكي يقوم الكائن العدو بتنفيذ إجراءات مبدئية فريدة وإجراءات متقدمة عند تطبيق التمديدات الخاصة. يوفر هذا النمط مرونة وقابلية تمديد لقاعدة الشيفرة مع تقليل الحاجة إلى إجراء تغييرات كبيرة في الشيفرة. + +## مخطط الفئات +![Extension_objects](./etc/extension_obj.png "Extension objects") + +## قابلية التطبيق +استخدم نمط "كائنات التمديد" (Extension objects) عندما: + +* تحتاج إلى دعم إضافة واجهات جديدة أو غير متوقعة إلى فئات موجودة ولا تريد التأثير على العملاء الذين لا يحتاجون إلى هذه الواجهة الجديدة. تتيح لك كائنات التمديد الاحتفاظ بالعمليات ذات الصلة معًا عن طريق تعريفها في فئة منفصلة. +* فئة تمثل تجريدًا رئيسيًا تؤدي وظائف مختلفة لعملاء مختلفين. يجب أن يكون عدد الوظائف التي يمكن أن تؤديها الفئة غير محدود. من الضروري الحفاظ على التجريد الرئيسي نفسه. على سبيل المثال، يبقى كائن العميل كائن عميل رغم أن الأنظمة الفرعية المختلفة قد تراها بطريقة مختلفة. +* يجب أن تكون الفئة قابلة للتمديد بسلوكيات جديدة دون الحاجة إلى تصنيفها منها. + +## أمثلة من العالم الحقيقي + +* [OpenDoc](https://en.wikipedia.org/wiki/OpenDoc) +* [ربط الكائنات وإدماجها](https://en.wikipedia.org/wiki/Object_Linking_and_Embedding) diff --git a/localization/ar/extension-objects/etc/extension_obj.png b/localization/ar/extension-objects/etc/extension_obj.png new file mode 100644 index 000000000000..a2b750e9dedd Binary files /dev/null and b/localization/ar/extension-objects/etc/extension_obj.png differ diff --git a/localization/ar/facade/README.md b/localization/ar/facade/README.md new file mode 100644 index 000000000000..99fa3bd04e9f --- /dev/null +++ b/localization/ar/facade/README.md @@ -0,0 +1,216 @@ +--- +title: Facade +shortTitle: Facade +category: Structural +language: ar +tag: + - Gang Of Four + - Decoupling +--- + +## الهدف + +توفير واجهة موحدة لمجموعة من واجهات النظام الفرعي. تقوم الواجهة البسيطة بتحديد واجهة تسهل استخدام النظام الفرعي. + +## الشرح + +مثال واقعي + +> كيف يعمل منجم الذهب؟ "حسنًا، ينزل العمال لاستخراج الذهب!" تقول. هذا ما تعتقده لأنك تستخدم واجهة بسيطة يوفرها منجم الذهب، ولكن من الداخل يجب أن يقوم بالكثير من الأشياء لتحقيق ذلك. هذه الواجهة البسيطة للنظام الفرعي المعقد هي الواجهة الأمامية. + +بإيجاز + +> يوفر نمط الواجهة الأمامية واجهة مبسطة لنظام فرعي معقد. + +تقول ويكيبيديا + +> الواجهة الأمامية هي كائن يوفر واجهة مبسطة لجسم من الكود الأكبر، مثل مكتبة الفصول. + +**مثال برمجي** + +لنأخذ مثال منجم الذهب. هنا لدينا تسلسل عمال الأقزام في المنجم. أولاً، لدينا فئة أساسية `DwarvenMineWorker`: + + +```java + +@Slf4j +public abstract class DwarvenMineWorker { + + public void goToSleep() { + LOGGER.info("{} goes to sleep.", name()); + } + + public void wakeUp() { + LOGGER.info("{} wakes up.", name()); + } + + public void goHome() { + LOGGER.info("{} goes home.", name()); + } + + public void goToMine() { + LOGGER.info("{} goes to the mine.", name()); + } + + private void action(Action action) { + switch (action) { + case GO_TO_SLEEP -> goToSleep(); + case WAKE_UP -> wakeUp(); + case GO_HOME -> goHome(); + case GO_TO_MINE -> goToMine(); + case WORK -> work(); + default -> LOGGER.info("Undefined action"); + } + } + + public void action(Action... actions) { + Arrays.stream(actions).forEach(this::action); + } + + public abstract void work(); + + public abstract String name(); + + enum Action { + GO_TO_SLEEP, WAKE_UP, GO_HOME, GO_TO_MINE, WORK + } +} +``` + +ثم لدينا الفئات المحددة للأقزام `DwarvenTunnelDigger`، `DwarvenGoldDigger` و `DwarvenCartOperator`: + + +```java +@Slf4j +public class DwarvenTunnelDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} creates another promising tunnel.", name()); + } + + @Override + public String name() { + return "Dwarven tunnel digger"; + } +} + +@Slf4j +public class DwarvenGoldDigger extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} digs for gold.", name()); + } + + @Override + public String name() { + return "Dwarf gold digger"; + } +} + +@Slf4j +public class DwarvenCartOperator extends DwarvenMineWorker { + + @Override + public void work() { + LOGGER.info("{} moves gold chunks out of the mine.", name()); + } + + @Override + public String name() { + return "Dwarf cart operator"; + } +} + +``` + +لإدارة جميع هؤلاء العمال في منجم الذهب، لدينا `FachadaDwarvenGoldmine`: + + +```java +public class DwarvenGoldmineFacade { + + private final List workers; + + public DwarvenGoldmineFacade() { + workers = List.of( + new DwarvenGoldDigger(), + new DwarvenCartOperator(), + new DwarvenTunnelDigger()); + } + + public void startNewDay() { + makeActions(workers, DwarvenMineWorker.Action.WAKE_UP, DwarvenMineWorker.Action.GO_TO_MINE); + } + + public void digOutGold() { + makeActions(workers, DwarvenMineWorker.Action.WORK); + } + + public void endDay() { + makeActions(workers, DwarvenMineWorker.Action.GO_HOME, DwarvenMineWorker.Action.GO_TO_SLEEP); + } + + private static void makeActions(Collection workers, + DwarvenMineWorker.Action... actions) { + workers.forEach(worker -> worker.action(actions)); + } +} +``` + +الآن سنستخدم الواجهة: + + +```java +var facade = new DwarvenGoldmineFacade(); +facade.startNewDay(); +facade.digOutGold(); +facade.endDay(); +``` + +إخراج البرنامج: + + +```java +// Dwarf gold digger wakes up. +// Dwarf gold digger goes to the mine. +// Dwarf cart operator wakes up. +// Dwarf cart operator goes to the mine. +// Dwarven tunnel digger wakes up. +// Dwarven tunnel digger goes to the mine. +// Dwarf gold digger digs for gold. +// Dwarf cart operator moves gold chunks out of the mine. +// Dwarven tunnel digger creates another promising tunnel. +// Dwarf gold digger goes home. +// Dwarf gold digger goes to sleep. +// Dwarf cart operator goes home. +// Dwarf cart operator goes to sleep. +// Dwarven tunnel digger goes home. +// Dwarven tunnel digger goes to sleep. +``` + +## مخطط الفئات + +![alt text](./etc/facade.urm.png "مخطط فئة نمط الواجهة") + +## قابلية التطبيق + +استخدم نمط الواجهة عندما: + +* ترغب في توفير واجهة بسيطة إلى نظام فرعي معقد. غالبًا ما تصبح الأنظمة الفرعية أكثر تعقيدًا مع تطورها. معظم الأنماط، عند تطبيقها، تؤدي إلى المزيد من الفئات الأصغر. هذا يجعل النظام الفرعي أكثر قابلية لإعادة الاستخدام وأسهل في التخصيص، ولكن أيضًا يصبح أكثر صعوبة في الاستخدام للعملاء الذين لا يحتاجون إلى تخصيصه. يمكن أن توفر الواجهة عرضًا بسيطًا افتراضيًا للنظام الفرعي الذي يكفي لمعظم العملاء. فقط العملاء الذين يحتاجون إلى مزيد من التخصيص سيضطرون للنظر إلى ما وراء الواجهة. +* هناك العديد من الاعتمادات بين العملاء وفئات تنفيذ التجريد. إدخال واجهة لفصل النظام الفرعي عن العملاء والأنظمة الفرعية الأخرى، مما يعزز الاستقلالية وقابلية النقل للنظام الفرعي. +* ترغب في تقسيم أنظمتك الفرعية. استخدم واجهة لتعريف نقطة دخول إلى كل مستوى من النظام الفرعي. إذا كانت الأنظمة الفرعية تعتمد على بعضها البعض، يمكنك تبسيط الاعتمادات بينها من خلال جعلها تتواصل مع بعضها البعض فقط من خلال واجهاتها. + +## الدروس التعليمية + +* [DigitalOcean](https://www.digitalocean.com/community/tutorials/facade-design-pattern-in-java) + +* [Refactoring Guru](https://refactoring.guru/design-patterns/facade) +* [GeekforGeeks](https://www.geeksforgeeks.org/facade-design-pattern-introduction/) +* [Tutorialspoint](https://www.tutorialspoint.com/design_pattern/facade_pattern.htm) + +## الاعتمادات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/gp/product/0201633612/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0201633612&linkCode=as2&tag=javadesignpat-20&linkId=675d49790ce11db99d90bde47f1aeb59) +* [Head First Design Patterns: A Brain-Friendly Guide](https://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0596007124&linkCode=as2&tag=javadesignpat-20&linkId=6b8b6eea86021af6c8e3cd3fc382cb5b) diff --git a/localization/ar/facade/etc/facade.urm.png b/localization/ar/facade/etc/facade.urm.png new file mode 100644 index 000000000000..8e3ec7aca45e Binary files /dev/null and b/localization/ar/facade/etc/facade.urm.png differ diff --git a/localization/ar/factory/README.md b/localization/ar/factory/README.md new file mode 100644 index 000000000000..5241065e3590 --- /dev/null +++ b/localization/ar/factory/README.md @@ -0,0 +1,140 @@ +--- +title: Factory +shortTitle: Factory +category: Creational +language: ar +tag: + - Gang of Four +--- + +## أيضًا معروف بـ + +* المصنع البسيط +* طريقة المصنع الثابتة + +## الهدف + +توفير طريقة ثابتة محاطة في فئة تسمى المصنع (Factory)، لإخفاء منطق التنفيذ وجعل شفرة العميل تركز على الاستخدام بدلاً من تهيئة الكائنات الجديدة. + +## الشرح + +مثال من الحياة الواقعية + +> تخيل كيميائيًا على وشك تصنيع العملات. يجب أن يكون الكيميائي قادرًا على إنشاء عملات ذهبية وكذلك عملات نحاسية، ويجب أن يكون من الممكن التبديل بينهما دون تعديل الكود المصدري الحالي. يجعل نمط المصنع هذا ممكنًا من خلال توفير طريقة بناء ثابتة يمكن استدعاؤها مع المعلمات ذات الصلة. + +تقول ويكيبيديا + +> المصنع (Factory) هو كائن لإنشاء كائنات أخرى: بشكل رسمي، المصنع هو دالة أو طريقة تُرجع كائنات من نموذج أو فئة متغيرة. + +**مثال برمجي** + +لدينا واجهة عملة `Coin` واثنان من تطبيقات العملة: عملة ذهبية `GoldCoin` وعملة نحاسية `CopperCoin`. + + +```java +public interface Coin { + String getDescription(); +} + +public class GoldCoin implements Coin { + + static final String DESCRIPTION = "This is a gold coin."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} + +public class CopperCoin implements Coin { + + static final String DESCRIPTION = "This is a copper coin."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} +``` + +تُمثل التعداد التالي أنواع العملات التي نقبلها (`GoldCoin` و `CopperCoin`). + + +```java +@RequiredArgsConstructor +@Getter +public enum CoinType { + + COPPER(CopperCoin::new), + GOLD(GoldCoin::new); + + private final Supplier constructor; +} +``` + +ثم لدينا الطريقة الثابتة للحصول على العملة `getCoin` لإنشاء كائنات العملة مغلفة داخل فئة المصنع `CoinFactory`. + + +```java +public class CoinFactory { + + public static Coin getCoin(CoinType type) { + return type.getConstructor().get(); + } +} +``` + +الآن في كود العميل يمكننا إنشاء أنواع مختلفة من العملات باستخدام فئة المصنع. + + +```java +LOGGER.info("The alchemist begins his work."); +var coin1 = CoinFactory.getCoin(CoinType.COPPER); +var coin2 = CoinFactory.getCoin(CoinType.GOLD); +LOGGER.info(coin1.getDescription()); +LOGGER.info(coin2.getDescription()); +``` + +### مخرجات البرنامج: + + +```java +The alchemist begins his work. +This is a copper coin. +This is a gold coin. +``` + +## المخطط البياني للفئات + +![alt text](./etc/factory.urm.png "Factory pattern diagrama de clases") + +## قابلية الاستخدام + +استخدم نمط المصنع (Factory) عندما يكون تركيزك على إنشاء الكائنات فقط دون القلق حول كيفية إنشائها وإدارتها. + +### المزايا + +* يسمح بجمع كل عمليات إنشاء الكائنات في مكان واحد وتجنب نشر الكلمة الأساسية 'new' عبر قاعدة الشيفرة. +* يُمكنك من كتابة شيفرة منخفضة الارتباط. من بين مزاياه الرئيسية: قابلية أفضل للاختبار، شيفرة سهلة الفهم، مكونات قابلة للاستبدال، قابلية التوسع، وميزات معزولة. + +### العيوب + +* قد يصبح الشيفرة أكثر تعقيدًا مما ينبغي. + +## أمثلة استخدام معروفة + +* [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +* [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +* [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +* [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +* [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) + (يعيد كائنات Singleton مختلفة استنادًا إلى بروتوكول معين) +* [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E)) +* [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) + وأيضًا طرق مشابهة أخرى. + +## الأنماط المرتبطة + +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/) +* [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/) +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/) diff --git a/localization/ar/factory/etc/factory.urm.png b/localization/ar/factory/etc/factory.urm.png new file mode 100644 index 000000000000..4b3420792e06 Binary files /dev/null and b/localization/ar/factory/etc/factory.urm.png differ diff --git a/localization/de/README.md b/localization/de/README.md index 0e33ffbfa712..b727e6ba47e1 100644 --- a/localization/de/README.md +++ b/localization/de/README.md @@ -1,69 +1,48 @@ - - # In Java implementierte Entwurfsmuster ![Java CI](https://github.com/iluwatar/java-design-patterns/workflows/Java%20CI/badge.svg) -[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) -[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) -[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) -[![Join the chat at https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Lizenz MIT](https://img.shields.io/badge/lizenz-MIT-blue.svg)](https://raw.githubusercontent.com/iluwatar/java-design-patterns/master/LICENSE.md) +[![Codezeilen](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=ncloc)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) +[![Abdeckung](https://sonarcloud.io/api/project_badges/measure?project=iluwatar_java-design-patterns&metric=coverage)](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns) +[![Chat beitreten unter https://gitter.im/iluwatar/java-design-patterns](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![All Contributors](https://img.shields.io/badge/all_contributors-208-orange.svg?style=flat-square)](#contributors-)
    -In einer anderen Sprache lesen : [**zh**](localization/zh/README.md), [**ko**](localization/ko/README.md), [**fr**](localization/fr/README.md), [**tr**](localization/tr/README.md), [**ar**](localization/ar/README.md), [**es**](localization/es/README.md), [**pt**](localization/pt/README.md), [**id**](localization/id/README.md), [**ru**](localization/ru/README.md), [**de**](localization/de/README.md), [**ja**](localization/ja/README.md) +In anderen Sprachen lesen: [**zh**](localization/zh/README.md), [**ko**](localization/ko/README.md), [**fr**](localization/fr/README.md), [**tr**](localization/tr/README.md), [**ar**](localization/ar/README.md), [**es**](localization/es/README.md), [**pt**](localization/pt/README.md), [**id**](localization/id/README.md), [**ru**](localization/ru/README.md), [**de**](localization/de/README.md), [**ja**](localization/ja/README.md)
    # Einführung -Entwurfschemas sind die besten formalisierten Praktiken, die ein Programmierer verwenden kann, -um allgemeine Probleme beim Entwurf einer Anwendung oder eines Systems zu lösen. +Entwurfsmuster sind bewährte Lösungen, die Entwickler nutzen können, um häufige Probleme beim Entwurf von Anwendungen oder Systemen zu lösen. -Entwurfschemas können den Entwicklungsprozess beschleunigen, indem sie getestete und bewährte -Entwicklungsparadigmen bereitstellen. +Sie helfen dabei, den Entwicklungsprozess zu beschleunigen, indem sie erprobte und zuverlässige Ansätze bereitstellen. -Die Wiederverwendung von Entwurfschemas hilft, subtile Fehler zu vermeiden, die größere -Probleme verursachen können, sowie die Lesbarkeit des Codes für Programmierer und Architekten zu verbessern, -welche mit den Prinzipien der Entwurfsmuster vertraut sind. +Die Wiederverwendung von Entwurfsmustern verhindert subtile Fehler, die zu größeren Problemen führen können, und verbessert die Lesbarkeit des Codes – besonders für Entwickler und Architekten, die mit diesen Mustern vertraut sind. # Erste Schritte -Auf dieser Seite werden Java Entwurfschemas vorgestellt. Die Lösungen wurden entwickelt von -erfahrenen Programmierern und Architekten aus der Open-Source-Gemeinschaft. Die -Schemas können anhand ihrer übergeordneten Beschreibungen oder anhand ihres -Quellcodes durchsucht werden. Die Quellcode-Beispiele sind gut kommentiert und können als -Programmiertutorials zur Implementierung eines bestimmten Schemas angesehen werden. Wir verwenden die am besten -bekannten und erprobtesten Open-Source Java-Technologien. +Diese Seite stellt Java-Entwurfsmuster vor. Die Lösungen wurden von erfahrenen Entwicklern und Architekten aus der Open-Source-Community erstellt. Die Muster können entweder durch ihre Beschreibungen oder durch den Quellcode erkundet werden. Die Codebeispiele sind gut kommentiert und eignen sich als Tutorials, um die Muster zu verstehen und umzusetzen. Wir verwenden dabei bekannte und bewährte Open-Source-Java-Technologien. -Bevor Sie in die Materie der Entwurfschemas eintauchen, sollten sie sich mit den verschiednen -[Software-Entwurfsprinzipien](https://java-design-patterns.com/principles/) auseinandersetzen. +Bevor Sie sich mit den Entwurfsmustern beschäftigen, sollten Sie sich mit den grundlegenden [Software-Entwurfsprinzipien](https://java-design-patterns.com/principles/) vertraut machen. -Alle Entwürfe sollten so einfach wie möglich gehalten werden. Sie sollten mit KISS, YAGNI, -und Do The Simplest Thing That Could Possibly Work prinzipen anfangen. Komplexe Entwurfschemas sollen nur eingesetzt werden, wenn diese für sinnvolle Erweiterungen benötigt werden. +Entwürfe sollten immer so einfach wie möglich gehalten werden. Beginnen Sie mit den Prinzipien KISS (Keep It Simple, Stupid), YAGNI (You Aren’t Gonna Need It) und "Do The Simplest Thing That Could Possibly Work". Komplexe Muster sollten nur dann verwendet werden, wenn sie wirklich notwendig sind. -Sobald sie mit diesen Konzepten vertraut sind, können sie beginnen, sich mit den [verfügbaren Entwurfschemas](https://java-design-patterns.com/patterns/) auseinanderzusetzen, durch einen -der folgenden Ansätze +Sobald Sie mit diesen Konzepten vertraut sind, können Sie sich die [verfügbaren Entwurfsmuster](https://java-design-patterns.com/patterns/) ansehen. Dafür gibt es verschiedene Ansätze: - - Nach einem bestimmten Schema anhand des Namens suchen. - Sie können keins finden? Bitte melden sie ein neues Schema [hier](https://github.com/iluwatar/java-design-patterns/issues). - - Verwendung von Tags wie `Performance`, `Gang of Four` oder `Data access`. - - Verwendung von Entwurfschema-Kategorien wie `Creational`, `Behavioral` und andere. +- Suchen Sie nach einem bestimmten Muster anhand des Namens. Fehlt ein Muster? Melden Sie es gerne [hier](https://github.com/iluwatar/java-design-patterns/issues). +- Nutzen Sie Tags wie `Performance`, `Gang of Four` oder `Data access`. +- Durchsuchen Sie die Muster nach Kategorien wie `Creational`, `Behavioral` und anderen. -Hoffentlich finden sie die auf dieser Website vorgestellten objektorientierten Lösungen -für ihre Architekturen nützlich und dass sie genauso viel Spaß beim Lernen haben, wie wir bei ihrer Entwicklung hatten. +Wir hoffen, dass Sie die hier vorgestellten Lösungen für Ihre Projekte nützlich finden und genauso viel Spaß beim Lernen haben, wie wir bei der Entwicklung hatten. -# Wie man bei diesem Projekt mitwirken kann +# Mitwirken -Wenn sie zu dem Projekt beitragen wollen, finden sie die entsprechenden Informationen in -unserem [Entwickler-Wiki](https://github.com/iluwatar/java-design-patterns/wiki).mWir helfen Ihnen -gerne und beantworten ihre Fragen im [Gitter chatroom](https://gitter.im/iluwatar/java-design-patterns). +Wenn Sie zum Projekt beitragen möchten, finden Sie alle notwendigen Informationen in unserem [Entwickler-Wiki](https://github.com/iluwatar/java-design-patterns/wiki). Bei Fragen helfen wir Ihnen gerne im [Gitter-Chatraum](https://gitter.im/iluwatar/java-design-patterns) weiter. # Lizenz -Dieses Projekt steht unter den Bedingungen der MIT-Lizenz. - +Dieses Projekt steht unter der MIT-Lizenz. \ No newline at end of file diff --git a/localization/de/abstract-document/README.md b/localization/de/abstract-document/README.md new file mode 100644 index 000000000000..13f8d26fbbbf --- /dev/null +++ b/localization/de/abstract-document/README.md @@ -0,0 +1,226 @@ +--- +title: "Abstract Document Pattern in Java: Vereinfachung der Datenverwaltung mit Flexibilität" +shortTitle: Abstract Document +description: "Erkunden Sie das Abstract Document Design Pattern in Java. Lernen Sie seine Absicht, Erklärung, Anwendbarkeit, Vorteile kennen und sehen Sie reale Beispiele zur Implementierung flexibler und dynamischer Datenstrukturen." +category: Strukturell +language: de +tag: + - Abstraktion + - Entkopplung + - Dynamische Typisierung + - Kapselung + - Erweiterbarkeit + - Polymorphismus +--- + +## Absicht des Abstract Document Design Patterns + +Das Abstract Document Design Pattern in Java ist ein wichtiges strukturelles Design Pattern, das eine konsistente Möglichkeit bietet, hierarchische und baumartige Datenstrukturen zu handhaben, indem es eine gemeinsame Schnittstelle für verschiedene Dokumenttypen definiert. Es trennt die Kernstruktur des Dokuments von spezifischen Datenformaten und ermöglicht dynamische Aktualisierungen und vereinfachte Wartung. + +## Detaillierte Erklärung des Abstract Document Patterns mit realen Beispielen + +Das Abstract Document Design Pattern in Java ermöglicht die dynamische Handhabung nicht-statischer Eigenschaften. Dieses Pattern verwendet das Konzept der Traits, um Typsicherheit zu gewährleisten und Eigenschaften verschiedener Klassen in eine Menge von Schnittstellen zu trennen. + +Reales Beispiel + +> Betrachten Sie ein Bibliothekssystem, das das Abstract Document Design Pattern in Java implementiert, wo Bücher verschiedene Formate und Attribute haben können: physische Bücher, eBooks und Hörbücher. Jedes Format hat einzigartige Eigenschaften, wie Seitenzahl für physische Bücher, Dateigröße für eBooks und Dauer für Hörbücher. Das Abstract Document Design Pattern ermöglicht es dem Bibliothekssystem, diese verschiedenen Formate flexibel zu verwalten. Durch die Verwendung dieses Patterns kann das System Eigenschaften dynamisch speichern und abrufen, ohne dass eine starre Struktur für jeden Buchtyp erforderlich ist, was es einfacher macht, neue Formate oder Attribute in der Zukunft hinzuzufügen, ohne dass wesentliche Änderungen am Codebase erforderlich sind. + +In einfachen Worten + +> Das Abstract Document Pattern ermöglicht das Anhängen von Eigenschaften an Objekte, ohne dass diese davon wissen. + +Wikipedia sagt + +> Ein objektorientiertes strukturelles Design Pattern zur Organisation von Objekten in schwach typisierten Schlüssel-Wert-Speichern und zur Bereitstellung der Daten über typisierte Ansichten. Der Zweck des Patterns besteht darin, einen hohen Grad an Flexibilität zwischen Komponenten in einer stark typisierten Sprache zu erreichen, in der neue Eigenschaften zur Objektstruktur dynamisch hinzugefügt werden können, ohne die Unterstützung der Typsicherheit zu verlieren. Das Pattern verwendet Traits, um verschiedene Eigenschaften einer Klasse in verschiedene Schnittstellen zu trennen. + +## Programmatisches Beispiel des Abstract Document Patterns in Java + +Betrachten Sie ein Auto, das aus mehreren Teilen besteht. Wir wissen jedoch nicht, ob das spezifische Auto wirklich alle Teile hat oder nur einige davon. Unsere Autos sind dynamisch und extrem flexibel. + +Lassen Sie uns zunächst die Basisklassen `Document` und `AbstractDocument` definieren. Sie sorgen im Wesentlichen dafür, dass das Objekt eine Eigenschaftsmap und eine beliebige Anzahl von Kindobjekten enthält. + +```java +public interface Document { + + Void put(String key, Object value); + + Object get(String key); + + Stream children(String key, Function, T> constructor); +} + +public abstract class AbstractDocument implements Document { + + private final Map properties; + + protected AbstractDocument(Map properties) { + Objects.requireNonNull(properties, "properties map is required"); + this.properties = properties; + } + + @Override + public Void put(String key, Object value) { + properties.put(key, value); + return null; + } + + @Override + public Object get(String key) { + return properties.get(key); + } + + @Override + public Stream children(String key, Function, T> constructor) { + return Stream.ofNullable(get(key)) + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(constructor); + } + + // Andere Eigenschaften und Methoden... +} +``` +Als nächstes definieren wir ein Enum Property und eine Menge von Schnittstellen für Typ, Preis, Modell und Teile. Dies ermöglicht es uns, eine statisch aussehende Schnittstelle für unsere Car-Klasse zu erstellen. + +```java +public enum Property { + + PARTS, TYPE, PRICE, MODEL +} + +public interface HasType extends Document { + + default Optional getType() { + return Optional.ofNullable((String) get(Property.TYPE.toString())); + } +} + +public interface HasPrice extends Document { + + default Optional getPrice() { + return Optional.ofNullable((Number) get(Property.PRICE.toString())); + } +} + +public interface HasModel extends Document { + + default Optional getModel() { + return Optional.ofNullable((String) get(Property.MODEL.toString())); + } +} + +public interface HasParts extends Document { + + default Stream getParts() { + return children(Property.PARTS.toString(), Part::new); + } +} +``` + +Jetzt sind wir bereit, das `Car` einzuführen. + +```java + public static void main(String[] args) { + LOGGER.info("Konstruktion von Teilen und Auto"); + + 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 carProperties = Map.of( + Property.MODEL.toString(), "300SL", + Property.PRICE.toString(), 10000L, + Property.PARTS.toString(), List.of(wheelProperties, doorProperties)); + + var car = new Car(carProperties); + + LOGGER.info("Hier ist unser Auto:"); + LOGGER.info("-> Modell: {}", car.getModel().orElseThrow()); + LOGGER.info("-> Preis: {}", car.getPrice().orElseThrow()); + LOGGER.info("-> Teile: "); + car.getParts().forEach(p -> LOGGER.info("\t{}/{}/{}", + p.getType().orElse(null), + p.getModel().orElse(null), + p.getPrice().orElse(null)) + ); +} +``` +Die Programmausgabe: + +``` +07:21:57.391 [main] INFO com.iluwatar.abstractdocument.App -- Konstruktion von Teilen und Auto +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- Hier ist unser Auto: +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- -> Modell: 300SL +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> Preis: 10000 +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> Teile: +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- Rad/15C/100 +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- Tür/Lambo/300 +``` +## Abstract Document Pattern Klassendiagramm + +![Abstract Document](./etc/abstract-document.png "Abstract Document Traits und Domain") + +## Wann sollte das Abstract Document Pattern in Java verwendet werden? + +Das Abstract Document Design Pattern ist besonders vorteilhaft in Szenarien, die eine Verwaltung unterschiedlicher Dokumenttypen in Java erfordern, die einige gemeinsame Attribute oder Verhaltensweisen teilen, aber auch einzigartige Attribute oder Verhaltensweisen haben, die spezifisch für ihren Typ sind. Hier sind einige Szenarien, in denen das Abstract Document Design Pattern anwendbar ist: + +* **Content-Management-Systeme (CMS)**: In einem CMS könnten verschiedene Arten von Inhalten wie Artikel, Bilder, Videos usw. vorkommen. Jede Inhaltsart könnte gemeinsame Attribute wie Erstellungsdatum, Autor und Tags haben, aber auch spezifische Attribute wie Bildabmessungen für Bilder oder Videodauer für Videos. + +* **Dateisysteme**: Wenn Sie ein Dateisystem entwerfen, in dem unterschiedliche Dateitypen verwaltet werden müssen, wie Dokumente, Bilder, Audiodateien und Verzeichnisse, kann das Abstract Document Pattern helfen, eine konsistente Möglichkeit zum Zugriff auf Attribute wie Dateigröße, Erstellungsdatum usw. zu bieten, während spezifische Attribute wie Bildauflösung oder Audiodauer berücksichtigt werden. + +* **E-Commerce-Systeme**: Eine E-Commerce-Plattform könnte verschiedene Produkttypen haben, wie physische Produkte, digitale Downloads und Abonnements. Jeder Typ könnte gemeinsame Attribute wie Name, Preis und Beschreibung haben, aber auch einzigartige Attribute wie Versandgewicht für physische Produkte oder Download-Link für digitale Produkte. + +* **Medizinische Aufzeichnungssysteme**: Im Gesundheitswesen könnten Patientenakten verschiedene Datentypen enthalten, wie demografische Daten, medizinische Vorgeschichte, Testergebnisse und Rezepte. Das Abstract Document Pattern kann helfen, gemeinsame Attribute wie Patienten-ID und Geburtsdatum zu verwalten, während spezialisierte Attribute wie Testergebnisse oder verschriebene Medikamente berücksichtigt werden. + +* **Konfigurationsmanagement**: Bei der Verwaltung von Konfigurationseinstellungen für Softwareanwendungen gibt es möglicherweise verschiedene Arten von Konfigurationselementen, jedes mit einer eigenen Reihe von Attributen. Das Abstract Document Pattern kann verwendet werden, um diese Konfigurationselemente zu verwalten, während eine konsistente Möglichkeit zum Zugriff auf und Bearbeiten der Attribute sichergestellt wird. + +* **Bildungsplattformen**: Bildungssysteme könnten verschiedene Arten von Lernmaterialien wie textbasierte Inhalte, Videos, Quizze und Aufgaben haben. Gemeinsame Attribute wie Titel, Autor und Veröffentlichungsdatum können geteilt werden, während spezifische Attribute wie Videodauer oder Aufgabenfälligkeit für jeden Typ einzigartig sind. + +* **Projektmanagement-Tools**: In Projektmanagement-Anwendungen könnten unterschiedliche Aufgabenarten wie To-Do-Items, Meilensteine und Probleme vorliegen. Das Abstract Document Pattern könnte verwendet werden, um allgemeine Attribute wie Aufgabenname und Zuweisung zu handhaben, während spezifische Attribute wie Meilensteindaten oder Problemprioritäten zugelassen werden. + +* **Dokumente haben vielfältige und sich entwickelnde Attributstrukturen.** + +* **Dynamisches Hinzufügen neuer Eigenschaften ist eine häufige Anforderung.** + +* **Entkopplung des Datenzugriffs von spezifischen Formaten ist entscheidend.** + +* **Wartbarkeit und Flexibilität sind entscheidend für die Codebasis.** + +Die Hauptidee hinter dem Abstract Document Design Pattern ist es, eine flexible und erweiterbare Möglichkeit zur Verwaltung unterschiedlicher Dokumenttypen oder Entitäten mit gemeinsamen und spezifischen Attributen bereitzustellen. Durch die Definition einer gemeinsamen Schnittstelle und deren Implementierung über verschiedene Dokumenttypen hinweg können Sie einen besser organisierten und konsistenteren Ansatz zur Handhabung komplexer Datenstrukturen erreichen. + +## Vorteile und Abwägungen des Abstract Document Patterns + +**Vorteile:** + +* **Flexibilität**: Ermöglicht die Handhabung unterschiedlicher Dokumentstrukturen und -eigenschaften. +* **Erweiterbarkeit**: Neue Attribute können dynamisch hinzugefügt werden, ohne bestehenden Code zu brechen. +* **Wartbarkeit**: Fördert sauberen und anpassungsfähigen Code durch Trennung der Verantwortlichkeiten. +* **Wiederverwendbarkeit**: Typspezifische Ansichten ermöglichen eine Wiederverwendung des Codes zum Zugriff auf bestimmte Attributtypen. + +**Abwägungen:** + +* **Komplexität**: Erfordert die Definition von Schnittstellen und Ansichten, was zu zusätzlichem Implementierungsaufwand führt. +* **Leistung**: Kann im Vergleich zum direkten Datenzugriff zu leichtem Leistungsaufwand führen. + +## Quellen und Danksagungen + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)](https://amzn.to/49zRP4R) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Abstract Document Pattern (Wikipedia)](https://en.wikipedia.org/wiki/Abstract_Document_Pattern) +* [Dealing with Properties (Martin Fowler)](http://martinfowler.com/apsupp/properties.pdf) + + + + + diff --git a/localization/de/abstract-document/etc/abstract-document.png b/localization/de/abstract-document/etc/abstract-document.png new file mode 100644 index 000000000000..6bc0b29a4e77 Binary files /dev/null and b/localization/de/abstract-document/etc/abstract-document.png differ diff --git a/localization/es/map-reduce/README.md b/localization/es/map-reduce/README.md new file mode 100644 index 000000000000..28a77f0062a1 --- /dev/null +++ b/localization/es/map-reduce/README.md @@ -0,0 +1,256 @@ +--- +title: "Patrón de Diseño MapReduce en Java" +shortTitle: MapReduce +description: "Aprende el patrón de diseño MapReduce en Java con ejemplos de la vida real, diagramas de clases y tutoriales. Entiende su intención, aplicación, beneficios y usos conocidos para mejorar tu conocimiento sobre patrones de diseño." +category: Optimización de Rendimiento +language: es +tag: + - Data processing + - Code simplification + - Delegation + - Performance + - Procesamiento de datos + - Simplificación de coódigo + - Delegación + - Rendimiento +--- + +## También conocido como: + +- Estrategia Separa-Aplica-Combina (Split-Apply-Combine Strategy) +- Patrón Dispersa-Recolecta (Scatter-Gather Pattern) + +## Intención del Patrón de Diseño Map Reduce + +MapReduce pretende procesar y generar grandes conjuntos de datos con un algoritmo en paralelo y distribuido en un grupo. Divide la carga de trabajo en dos fases principales: Mapear (Map) y Reducir (Reduce), permitiendo así una mayor eficiencia en procesamiento de datos en paralelo. + +## Explicación Detallada del Patrón de Diseño Map Reduce con Ejemplos del Mundo Real + +Ejemplo Práctico + +> Imagina una compañía de comercio en línea (e-commerce) que quiere analizar sus datos de ventas a lo largo de múltplies regiones. Tienen terabytes de datos de transacciones almacenados en cientos de servidores. Usando MapReduce, pueden procesar estos datos eficientemente para calcular las ventas totales por categoría de producto. La función Map procesaría registros de ventas individuales, regresando pares clave-valor de (categoría, cantidad de venta). La función Reduce entonces sumaría todas las cantidades de ventas por cada categoría, produciendo el resultado final. + +En palabras sencillas + +> MapReduce divide un gran problema en partes más pequeñas, las procesa en paralelo y después combina los resultados. + +En Wikipedia dice: + +> "MapReduce es un modelo de programación e implementación asociada para procesar y generar grandes conjuntos de información con un algoritmo en paralelo y distribuido en un grupo". +> MapReduce consiste de dos funciones principales: +> El nodo principal toma la entrada de información, la divide en problemas más pequeños (sub-problemas) y los distribuye a los nodos restantes (de trabajo). Un nodo de trabajo puede repetir este proceso llevando a una estructura de árbol multinivel. El nodo de trabajo procesa el sub-problema y le regresa la respuesta al nodo principal. +> El nodo principal recolecta todas las respuestas de todos los sub-problemas y las combina de cierta manera para regresar la salida de información - la respuesta del principal problema que estaba resolviendo. +> Este acercamiento permite un procesamiento eficiente de grandes cantidades de datos através de múltiples máquinas, convirtiéndola en una técnica fundamental en análisis de cantidades enormes de datos y computo distribuido. + +## Ejemplo Programático de Map Reduce en Java + +### 1. Fase de Map (Mapeador; División & Procesamiento de Datos) + +- El Mapper toma una entrada de texto (string), lo divide en palabras y cuenta las ocurrencias. +- Salida: Un mapa {palabra → conteo} por cada línea de entrada. + +#### `Mapper.java` + +```java +public class Mapper { + public static Map map(String input) { + Map wordCount = new HashMap<>(); + String[] words = input.split("\\s+"); + for (String word : words) { + word = word.toLowerCase().replaceAll("[^a-z]", ""); + if (!word.isEmpty()) { + wordCount.put(word, wordCount.getOrDefault(word, 0) + 1); + } + } + return wordCount; + } +} +``` + +Ejemplo de Entrada: `"Hello world hello"` +Salida: `{hello=2, world=1}` + +### 2. Fase de Shuffle (Combinación; Agrupar Datos por Clave) + +- La Combinación recolecta pares clave-valor de múltiples mapeadores (mappers) y valores de grupo por clave. + +#### `Shuffler.java` + +```java +public class Shuffler { + public static Map> shuffleAndSort(List> mapped) { + Map> grouped = new HashMap<>(); + for (Map map : mapped) { + for (Map.Entry entry : map.entrySet()) { + grouped.putIfAbsent(entry.getKey(), new ArrayList<>()); + grouped.get(entry.getKey()).add(entry.getValue()); + } + } + return grouped; + } +} +``` + +Ejemplo de Entrada: + +``` +[ + {"hello": 2, "world": 1}, + {"hello": 1, "java": 1} +] +``` + +Salida: + +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` + +### 3. Fase de Reduce (Reductor; Agregar Resultados) + +- El Reductor suma todas las ocurrencias de cada palabra. + +#### `Reducer.java` + +```java +public class Reducer { + public static List> reduce(Map> grouped) { + Map reduced = new HashMap<>(); + for (Map.Entry> entry : grouped.entrySet()) { + reduced.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum()); + } + + List> result = new ArrayList<>(reduced.entrySet()); + result.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + return result; + } +} +``` + +Ejemplo de Entrada: + +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` + +Salida: + +``` +[ + {"hello": 3}, + {"world": 1}, + {"java": 1} +] +``` + +### 4. Ejecutar el Proceso Completo de MapReduce + +- La clase MapReduce coordina los tres pasos. + +#### `MapReduce.java` + +```java +public class MapReduce { + public static List> mapReduce(List inputs) { + List> mapped = new ArrayList<>(); + for (String input : inputs) { + mapped.add(Mapper.map(input)); + } + + Map> grouped = Shuffler.shuffleAndSort(mapped); + + return Reducer.reduce(grouped); + } +} +``` + +### 5. Ejecución Principal (Llamar a MapReduce) + +- La clase Main ejecuta el "pipeline" de MapReduce y regresa el conteo final de palabras. + +#### `Main.java` + +```java + public static void main(String[] args) { + List inputs = Arrays.asList( + "Hello world hello", + "MapReduce is fun", + "Hello from the other side", + "Hello world" + ); + List> result = MapReduce.mapReduce(inputs); + for (Map.Entry entry : result) { + System.out.println(entry.getKey() + ": " + entry.getValue()); + } +} +``` + +Salida: + +``` +hello: 4 +world: 2 +the: 1 +other: 1 +side: 1 +mapreduce: 1 +is: 1 +from: 1 +fun: 1 +``` + +## Cuándo Usar el Patrón de Diseño MapReduce en Java + +Usa MapReduce cuando se están: + +- Procesando grandes conjuntos de información que no quepa en la memoria de una sola máquina. +- Realizando cálculos que se puedan desarrollar en paralelo. +- Trabajando en escenarios tolerante a fallos y computación distribuida. +- Analizando archivos de registro, datos de rastreo web o datos científicos. + +## Tutoriales del Patrón de Diseño MapReduce en Java + +- [Tutorial MapReduce (Apache Hadoop)](https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html) +- [Ejemplo MapReduce (Simplilearn)](https://www.youtube.com/watch?v=l2clwKnrtO8) + +## Ventajas y Desventajas del Patrón de Diseño MapReduce + +Ventajas: + +- Escalabilidad: Puede procesar grandes cantidades de datos através de múltiples máquinas +- Tolerancia a Fallos: Maneja los fallos elegantemente +- Simplicidad: Resume detalles complejos de distribución computacional + +Desventajas: + +- Costo-Beneficio: Ineficiente para conjuntos de datos chicos por la preparación y coordinación necesaria +- Flexibilidad Limitada: Inadecuado para todos los tipos de computación o algoritmos +- Latencia: En relación a lotes de información podría ser inadecaudo para necesidades de procesamiento en tiempo real + +## Aplicaciones Reales para el Patrón de Diseño MapReduce en Java + +- Implementación original de Google para indexar páginas web +- Hadoop MapReduce para procesamiento de información extensa +- Sistemas de análisis de registros a gran escala +- Secuencia genómica en análisis de bio-informática + +## Patrones de Diseño relacionados a Java + +- Patrón de Encadenamiento (Chaining Pattern) +- Patrón de Maestro-Trabajador (Master-Worker Pattern) +- Patrón de Tubería (Pipeline Pattern) + +## Referencias y Creditos + +- [¿Qué es MapReduce?](https://www.ibm.com/think/topics/mapreduce) +- [¿Por qué MapReduce no ha muerto?](https://www.codemotion.com/magazine/ai-ml/big-data/mapreduce-not-dead-heres-why-its-still-ruling-in-the-cloud/) +- [Soluciones Escalables de Procesamiento de Datos Distribuidos](https://tcpp.cs.gsu.edu/curriculum/?q=system%2Ffiles%2Fch07.pdf) +- [Patrones de Diseño en Java: Experiencia Práctica con Ejemplos del Mundo Real](https://amzn.to/3HWNf4U) diff --git a/localization/es/strategy/README.md b/localization/es/strategy/README.md index 5c04764f4c2d..d211914458e2 100644 --- a/localization/es/strategy/README.md +++ b/localization/es/strategy/README.md @@ -127,7 +127,7 @@ public class LambdaStrategy { public enum Strategy implements DragonSlayingStrategy { MeleeStrategy(() -> LOGGER.info( - "With your Excalibur you severe the dragon's head!")), + "With your Excalibur you sever the dragon's head!")), ProjectileStrategy(() -> LOGGER.info( "You shoot the dragon with the magical crossbow and it falls dead on the ground!")), SpellStrategy(() -> LOGGER.info( diff --git a/localization/fa/abstract-document/README.md b/localization/fa/abstract-document/README.md new file mode 100644 index 000000000000..7097ffc8b4ea --- /dev/null +++ b/localization/fa/abstract-document/README.md @@ -0,0 +1,243 @@ +--- +title: "الگوی Abstract Document در جاوا: ساده‌سازی مدیریت داده با انعطاف‌پذیری" +shortTitle: Abstract Document +description: "الگوی طراحی Abstract Document در جاوا را بررسی کنید. با هدف، توضیح، کاربرد، مزایا و نمونه‌های دنیای واقعی برای پیاده‌سازی ساختارهای داده‌ای پویا و انعطاف‌پذیر آشنا شوید." +category: Structural +language: fa +tag: + - Abstraction + - Decoupling + - Dynamic typing + - Encapsulation + - Extensibility + - Polymorphism +--- + +## هدف الگوی طراحی Abstract Document + +الگوی طراحی Abstract Document در جاوا یک الگوی طراحی ساختاری مهم است که راهی یکپارچه برای مدیریت ساختارهای داده‌ای سلسله‌مراتبی و درخت‌ی فراهم می‌کند، با تعریف یک واسط مشترک برای انواع مختلف اسناد. این الگو ساختار اصلی سند را از فرمت‌های خاص داده جدا می‌کند، که باعث به‌روزرسانی پویا و نگهداری ساده‌تر می‌شود. + +## توضیح دقیق الگوی Abstract Document با نمونه‌های دنیای واقعی + +الگوی طراحی Abstract Document در جاوا امکان مدیریت پویا ویژگی‌های پویا(غیر استاتیک) را فراهم می‌کند. این الگو از مفهوم traits استفاده می‌کند تا ایمنی نوع‌داده (type safety) را فراهم کرده و ویژگی‌های کلاس‌های مختلف را به مجموعه‌ای از واسط‌ها تفکیک کند. + +مثال دنیای واقعی + +> فرض کنید یک سیستم کتابخانه از الگوی Abstract Document در جاوا استفاده می‌کند، جایی که کتاب‌ها می‌توانند فرمت‌ها و ویژگی‌های متنوعی داشته باشند: کتاب‌های فیزیکی، کتاب‌های الکترونیکی، و کتاب‌های صوتی. هر فرمت ویژگی‌های خاص خود را دارد، مانند تعداد صفحات برای کتاب‌های فیزیکی، حجم فایل برای کتاب‌های الکترونیکی، و مدت‌زمان برای کتاب‌های صوتی. الگوی Abstract Document به سیستم کتابخانه اجازه می‌دهد تا این فرمت‌های متنوع را به‌صورت انعطاف‌پذیر مدیریت کند. با استفاده از این الگو، سیستم می‌تواند ویژگی‌ها را به‌صورت پویا ذخیره و بازیابی کند، بدون نیاز به ساختار سفت و سخت برای هر نوع کتاب، و این کار افزودن فرمت‌ها یا ویژگی‌های جدید را در آینده بدون تغییرات عمده در کد آسان می‌سازد. + +به زبان ساده + +> الگوی Abstract Document اجازه می‌دهد ویژگی‌هایی به اشیاء متصل شوند بدون اینکه خود آن اشیاء از آن اطلاع داشته باشند. + +ویکی‌پدیا می‌گوید + +> یک الگوی طراحی ساختاری شی‌ء‌گرا برای سازماندهی اشیاء در کلید-مقدارهایی با تایپ آزاد و ارائه داده‌ها از طریق نمای تایپ است. هدف این الگو دستیابی به انعطاف‌پذیری بالا بین اجزا در یک زبان strongly typed است که در آن بتوان ویژگی‌های جدیدی را به‌صورت پویا به ساختار درختی اشیاء اضافه کرد، بدون از دست دادن پشتیبانی از type safety. این الگو از traits برای جداسازی ویژگی‌های مختلف یک کلاس در اینترفیس‌های متفاوت استفاده می‌کند. + +نمودار کلاس + +![Abstract Document class diagram](./etc/abstract-document.png "Abstract Document class diagram") + +## مثال برنامه‌نویسی از الگوی Abstract Document در جاوا + +فرض کنید یک خودرو داریم که از قطعات مختلفی تشکیل شده است. اما نمی‌دانیم آیا این خودرو خاص واقعاً همه قطعات را دارد یا فقط برخی از آن‌ها. خودروهای ما پویا و بسیار انعطاف‌پذیر هستند. + +بیایید ابتدا کلاس‌های پایه `Document` و `AbstractDocument` را تعریف کنیم. این کلاس‌ها اساساً یک شیء را قادر می‌سازند تا یک نقشه از ویژگی‌ها و هر تعداد شیء فرزند را نگه دارد. + +```java +public interface Document { + + Void put(String key, Object value); + + Object get(String key); + + Stream children(String key, Function, T> constructor); +} + +public abstract class AbstractDocument implements Document { + + private final Map properties; + + protected AbstractDocument(Map properties) { + Objects.requireNonNull(properties, "properties map is required"); + this.properties = properties; + } + + @Override + public Void put(String key, Object value) { + properties.put(key, value); + return null; + } + + @Override + public Object get(String key) { + return properties.get(key); + } + + @Override + public Stream children(String key, Function, T> constructor) { + return Stream.ofNullable(get(key)) + .filter(Objects::nonNull) + .map(el -> (List>) el) + .findAny() + .stream() + .flatMap(Collection::stream) + .map(constructor); + } + + // Other properties and methods... +} +``` +در ادامه، یک enum به نام Property و مجموعه‌ای از واسط‌ها برای type، price، model و parts تعریف می‌کنیم. این کار به ما اجازه می‌دهد یک واسط با ظاهر استاتیک برای کلاس Car ایجاد کنیم. +```java +public enum Property { + + PARTS, TYPE, PRICE, MODEL +} + +public interface HasType extends Document { + + default Optional getType() { + return Optional.ofNullable((String) get(Property.TYPE.toString())); + } +} + +public interface HasPrice extends Document { + + default Optional getPrice() { + return Optional.ofNullable((Number) get(Property.PRICE.toString())); + } +} + +public interface HasModel extends Document { + + default Optional getModel() { + return Optional.ofNullable((String) get(Property.MODEL.toString())); + } +} + +public interface HasParts extends Document { + + default Stream getParts() { + return children(Property.PARTS.toString(), Part::new); + } +} + +public class Part extends AbstractDocument implements HasType, HasModel, HasPrice { + + public Part(Map properties) { + super(properties); + } +} +``` +اکنون آماده‌ایم تا کلاس Car را معرفی کنیم. +```java +public class Car extends AbstractDocument implements HasModel, HasPrice, HasParts { + + public Car(Map properties) { + super(properties); + } +} +``` +و در نهایت، نحوه ساخت و استفاده از Car را در یک مثال کامل می‌بینید. +```java + 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 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 car = new Car(carProperties); + + LOGGER.info("Here is our car:"); + 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)) + ); +} +``` +خروجی برنامه: +``` +07:21:57.391 [main] INFO com.iluwatar.abstractdocument.App -- Constructing parts and car +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- Here is our car: +07:21:57.393 [main] INFO com.iluwatar.abstractdocument.App -- -> model: 300SL +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> price: 10000 +07:21:57.394 [main] INFO com.iluwatar.abstractdocument.App -- -> parts: +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- wheel/15C/100 +07:21:57.395 [main] INFO com.iluwatar.abstractdocument.App -- door/Lambo/300 +``` + + ### چه زمانی از الگوی Abstract Document در جاوا استفاده کنیم؟ + +الگوی طراحی Abstract Document به‌ویژه در سناریوهایی مفید است که نیاز به مدیریت انواع مختلفی از اسناد در جاوا وجود دارد که برخی ویژگی‌ها یا رفتارهای مشترک دارند، ولی ویژگی‌ها یا رفتارهای خاص خود را نیز دارند. در ادامه چند سناریوی مناسب برای این الگو آورده شده است: + +* سیستم‌های مدیریت محتوا (CMS): ممکن است انواع مختلفی از محتوا مانند مقاله، تصویر، ویدئو و... وجود داشته باشد. هر نوع محتوا ویژگی‌های مشترکی مثل تاریخ ایجاد، نویسنده و تگ‌ها دارد، ولی همچنین ویژگی‌های خاصی مثل ابعاد تصویر یا مدت‌زمان ویدئو. + +* سیستم‌های فایل: اگر یک سیستم فایل طراحی می‌کنید که باید انواع مختلف فایل مانند اسناد، تصاویر، فایل‌های صوتی و دایرکتوری‌ها را مدیریت کند، این الگو می‌تواند راهی یکپارچه برای دسترسی به ویژگی‌هایی مانند اندازه فایل یا تاریخ ایجاد، فراهم کند و در عین حال ویژگی‌های خاص هر نوع فایل را هم مدیریت کند. + +* سیستم‌های تجارت الکترونیک: یک پلتفرم فروش آنلاین ممکن است محصولات مختلفی داشته باشد مثل محصولات فیزیکی، فایل‌های دیجیتال، و اشتراک‌ها. این محصولات ویژگی‌هایی مثل نام، قیمت و توضیح را به اشتراک می‌گذارند، ولی ویژگی‌های خاصی هم دارند مانند وزن حمل برای محصولات فیزیکی یا لینک دانلود برای دیجیتال‌ها. + +* سیستم‌های سوابق پزشکی: در مراقبت سلامت، پرونده بیماران ممکن است داده‌های مختلفی مثل مشخصات فردی، سوابق پزشکی، نتایج آزمایش‌ها و نسخه‌ها را شامل شود. این الگو می‌تواند ویژگی‌های مشترک مثل شماره بیمار یا تاریخ تولد را مدیریت کند و هم‌زمان ویژگی‌های خاصی مثل نتایج آزمایش یا داروهای تجویزی را هم پوشش دهد. + +* مدیریت پیکربندی: هنگام کار با تنظیمات پیکربندی نرم‌افزار، ممکن است انواع مختلفی از عناصر پیکربندی وجود داشته باشد، هر یک با ویژگی‌های خاص خود. این الگو می‌تواند برای مدیریت این عناصر مفید باشد. + +* پلتفرم‌های آموزشی: سیستم‌های آموزشی ممکن است انواع مختلفی از منابع یادگیری داشته باشند مثل محتوای متنی، ویدیوها، آزمون‌ها و تمرین‌ها. ویژگی‌های مشترکی مثل عنوان، نویسنده و تاریخ انتشار وجود دارد، ولی ویژگی‌های خاصی مانند مدت ویدیو یا مهلت تحویل تمرین نیز ممکن است وجود داشته باشد. + +* ابزارهای مدیریت پروژه: در برنامه‌های مدیریت پروژه، ممکن است انواع مختلفی از وظایف مانند آیتم‌های to-do، milestoneها و issueها داشته باشید. این الگو می‌تواند برای مدیریت ویژگی‌های عمومی مانند نام وظیفه و مسئول آن استفاده شود و در عین حال ویژگی‌های خاص مانند تاریخ milestone یا اولویت issue را نیز پوشش دهد. + +* اسناد ساختار ویژگی‌های متنوع و در حال تحول دارند. + +* افزودن ویژگی‌های جدید به‌صورت پویا یک نیاز رایج است. + +* جداسازی دسترسی به داده از فرمت‌های خاص حیاتی است. + +* نگهداری‌پذیری و انعطاف‌پذیری برای کد اهمیت دارد. + +ایده اصلی پشت الگوی Abstract Document فراهم کردن روشی انعطاف‌پذیر و قابل توسعه برای مدیریت انواع مختلف اسناد یا موجودیت‌ها با ویژگی‌های مشترک و خاص است. با تعریف یک واسط مشترک و پیاده‌سازی آن در انواع مختلف اسناد، می‌توان به شیوه‌ای منظم و یکپارچه برای مدیریت ساختارهای پیچیده داده دست یافت. +### مزایا و معایب الگوی Abstract Document +
    +مزایا: + +* انعطاف‌پذیری: پشتیبانی از ساختارهای متنوع اسناد و ویژگی‌ها. + +* قابلیت توسعه: افزودن ویژگی‌های جدید بدون شکستن کد موجود. + +* نگهداری‌پذیری: ارتقاء کد تمیز و قابل تطبیق به‌واسطه جداسازی وظایف. + +* قابلیت استفاده مجدد: نمای دید تایپ‌شده باعث استفاده مجدد از کد برای دسترسی به نوع خاصی از ویژگی می‌شود. + +معایب: + +* پیچیدگی: نیاز به تعریف واسط‌ها و نماها، که باعث اضافه شدن سربار پیاده‌سازی می‌شود. + +* کارایی: ممکن است سربار کمی نسبت به دسترسی مستقیم به داده داشته باشد. +
    + +منابع و اعتبارها + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) + +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) + +* [Pattern-Oriented Software Architecture Volume 4: A Pattern Language for Distributed Computing (v. 4)] (https://amzn.to/49zRP4R) + +* [Patterns of Enterprise Application Architecture] (https://amzn.to/3WfKBPR) + +* [Abstract Document Pattern (Wikipedia)] (https://en.wikipedia.org/wiki/Abstract_Document_Pattern) + +* [Dealing with Properties (Martin Fowler)] (http://martinfowler.com/apsupp/properties.pdf) diff --git a/localization/fa/abstract-document/etc/abstract-document.png b/localization/fa/abstract-document/etc/abstract-document.png new file mode 100644 index 000000000000..6bc0b29a4e77 Binary files /dev/null and b/localization/fa/abstract-document/etc/abstract-document.png differ diff --git a/localization/fa/active-object/README.md b/localization/fa/active-object/README.md new file mode 100644 index 000000000000..7c105b072aaa --- /dev/null +++ b/localization/fa/active-object/README.md @@ -0,0 +1,220 @@ +--- +title: "الگوی Active Object در جاوا: دستیابی به پردازش ناهمگام کارآمد" +shortTitle: Active Object +description: "با الگوی طراحی Active Object در جاوا آشنا شوید. این راهنما رفتار ناهمگام، هم‌زمانی (concurrency) و مثال‌های کاربردی برای بهبود عملکرد برنامه‌های جاوای شما را پوشش می‌دهد." +category: Concurrency +language: fa +tag: + - Asynchronous + - Decoupling + - Messaging + - Synchronization + - Thread management +--- + +## هدف الگوی طراحی Active Object + +الگوی Active Object روشی مطمئن برای پردازش ناهمگام در جاوا فراهم می‌کند که به پاسخ‌گو بودن برنامه‌ها و مدیریت مؤثر threadها کمک می‌کند. این الگو با محصور کردن وظایف در شیءهایی که هر کدام thread و صف پیام مخصوص خود را دارند، به این هدف می‌رسد. این جداسازی باعث می‌شود thread اصلی پاسخ‌گو باقی بماند و مشکلاتی مانند دست‌کاری مستقیم threadها یا دسترسی به وضعیت مشترک (shared state) به وجود نیاید. + +## توضیح کامل الگوی Active Object با مثال‌های دنیای واقعی + +مثال دنیای واقعی + +> تصور کنید در یک رستوران شلوغ، مشتریان سفارش خود را به گارسون‌ها می‌سپارند. به‌جای آنکه گارسون‌ها خودشان به آشپزخانه بروند و غذا را آماده کنند، سفارش‌ها را روی کاغذهایی می‌نویسند و به یک هماهنگ‌کننده می‌دهند. این هماهنگ‌کننده گروهی از سرآشپزها را مدیریت می‌کند که غذاها را به صورت ناهمگام آماده می‌کنند. هرگاه آشپزی آزاد شود، سفارش بعدی را از صف برمی‌دارد، غذا را آماده می‌کند و پس از آن گارسون را برای سرو غذا مطلع می‌سازد. +> +> در این قیاس، گارسون‌ها نماینده threadهای کلاینت هستند، هماهنگ‌کننده نقش زمان‌بند (scheduler) را ایفا می‌کند، و آشپزها نمایان‌گر اجرای متدها در threadهای جداگانه هستند. این ساختار باعث می‌شود گارسون‌ها بتوانند بدون مسدود شدن توسط فرایند آماده‌سازی غذا، سفارش‌های بیشتری دریافت کنند—درست مانند اینکه الگوی Active Object، فراخوانی متد را از اجرای آن جدا می‌کند تا هم‌زمانی (concurrency) را افزایش دهد. + +به زبان ساده + +> الگوی Active Object، اجرای متد را از فراخوانی آن جدا می‌کند تا در برنامه‌های چندریسمانی (multithreaded)، هم‌زمانی و پاسخ‌گویی بهتری فراهم شود. + +طبق تعریف ویکی‌پدیا + +> الگوی طراحی Active Object اجرای متد را از فراخوانی آن جدا می‌کند، برای شیءهایی که هرکدام thread کنترل مخصوص به خود را دارند. هدف، معرفی هم‌زمانی با استفاده از فراخوانی متد به‌صورت ناهمگام و یک زمان‌بند برای مدیریت درخواست‌ها است. +> +> این الگو شامل شش جزء کلیدی است: +> +> * یک proxy، که رابطی برای کلاینت‌ها با متدهای عمومی فراهم می‌کند. +> * یک interface که درخواست متد برای شیء فعال (active object) را تعریف می‌کند. +> * فهرستی از درخواست‌های معلق از سوی کلاینت‌ها. +> * یک زمان‌بند (scheduler) که تصمیم می‌گیرد کدام درخواست بعدی اجرا شود. +> * پیاده‌سازی متد شیء فعال. +> * یک callback یا متغیر برای اینکه کلاینت نتیجه را دریافت کند. + +نمودار توالی + +![Active Object sequence diagram](./etc/active-object-sequence-diagram.png) + +## مثال برنامه‌نویسی از Active Object در جاوا + +این بخش نحوه عملکرد الگوی Active Object در جاوا را توضیح می‌دهد و کاربرد آن در مدیریت وظایف ناهمگام و کنترل هم‌زمانی را نشان می‌دهد. + +اورک‌ها به دلیل ذات وحشی و غیرقابل مهارشان شناخته می‌شوند. به‌نظر می‌رسد هرکدام thread کنترل مخصوص خود را دارند. برای پیاده‌سازی یک موجود که دارای سازوکار thread مستقل خود باشد و فقط API را در اختیار قرار دهد نه اجرای داخلی را، می‌توان از الگوی Active Object استفاده کرد. + +```java +public abstract class ActiveCreature { + private final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName()); + + private BlockingQueue requests; + + private String name; + + private Thread thread; + + public ActiveCreature(String name) { + this.name = name; + this.requests = new LinkedBlockingQueue(); + thread = new Thread(new Runnable() { + @Override + public void run() { + while (true) { + try { + requests.take().run(); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + } + } + } + } + ); + thread.start(); + } + + public void eat() throws InterruptedException { + requests.put(new Runnable() { + @Override + public void run() { + logger.info("{} is eating!", name()); + logger.info("{} has finished eating!", name()); + } + } + ); + } + + public void roam() throws InterruptedException { + requests.put(new Runnable() { + @Override + public void run() { + logger.info("{} has started to roam the wastelands.", name()); + } + } + ); + } + + public String name() { + return this.name; + } +} +``` + +می‌توان دید هر کلاسی که از ActiveCreature ارث‌بری کند، دارای thread کنترل مختص به خود برای فراخوانی و اجرای متدها خواهد بود. + +برای مثال، کلاس Orc: + +```java +public class Orc extends ActiveCreature { + + public Orc(String name) { + super(name); + } +} +``` +اکنون می‌توان چند موجود مانند orc ایجاد کرد، به آن‌ها دستور داد که بخورند و پرسه بزنند، و آن‌ها این دستورات را در thread مختص به خود اجرا می‌کنند: + +```java +public class App implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(App.class.getName()); + + private static final int NUM_CREATURES = 3; + + public static void main(String[] args) { + var app = new App(); + app.run(); + } + + @Override + public void run() { + List creatures = new ArrayList<>(); + try { + for (int i = 0; i < NUM_CREATURES; i++) { + creatures.add(new Orc(Orc.class.getSimpleName() + i)); + creatures.get(i).eat(); + creatures.get(i).roam(); + } + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage()); + Thread.currentThread().interrupt(); + } finally { + for (int i = 0; i < NUM_CREATURES; i++) { + creatures.get(i).kill(0); + } + } + } +} +``` + +خروجی برنامه: + +``` +09:00:02.501 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 is eating! +09:00:02.501 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 is eating! +09:00:02.501 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 is eating! +09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has finished eating! +09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has finished eating! +09:00:02.504 [Thread-0] INFO com.iluwatar.activeobject.ActiveCreature -- Orc0 has started to roam in the wastelands. +09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has finished eating! +09:00:02.504 [Thread-1] INFO com.iluwatar.activeobject.ActiveCreature -- Orc1 has started to roam in the wastelands. +09:00:02.504 [Thread-2] INFO com.iluwatar.activeobject.ActiveCreature -- Orc2 has started to roam in the wastelands. +``` + +چه زمانی از الگوی Active Object در جاوا استفاده کنیم؟ + +از الگوی Active Object در جاوا استفاده کنید زمانی که: +> * نیاز دارید وظایف ناهمگام را بدون مسدود کردن thread اصلی مدیریت کنید تا عملکرد و پاسخ‌گویی بهتری داشته باشید. +> * نیاز به تعامل ناهمگام با منابع خارجی دارید. +> * می‌خواهید پاسخ‌گویی برنامه را افزایش دهید. +> * نیاز به مدیریت وظایف هم‌زمان به‌صورت ماژولار و قابل نگهداری دارید. + +آموزش‌های Java برای الگوی Active Object +> [Android and Java Concurrency: The Active Object Pattern (Douglas Schmidt)]((https://www.youtube.com/watch?v=Cd8t2u5Qmvc)) + +کاربردهای دنیای واقعی الگوی Active Object در جاوا + +> سیستم‌های معاملات بلادرنگ که درخواست‌ها به‌صورت ناهمگام پردازش می‌شوند. +> که در آن وظایف طولانی در پس‌زمینه اجرا می‌شوند بدون آنکه رابط کاربری را متوقف کنند. +> رابط‌های کاربری گرافیکی (GUI) +> برنامه‌نویسی بازی‌ها برای مدیریت به‌روزرسانی‌های هم‌زمان وضعیت بازی یا محاسبات هوش مصنوعی. + +مزایا و ملاحظات الگوی Active Object + +با مزایا و معایب استفاده از الگوی Active Object در جاوا آشنا شوید؛ از جمله بهبود ایمنی threadها و ملاحظات سربار احتمالی (overhead). + +> مزایا: +> +> * پاسخ‌گویی بهتر thread اصلی. +> * محصورسازی مسائل مربوط به هم‌زمانی درون شیءها. +> * بهبود سازمان‌دهی کد و قابلیت نگهداری. +> * فراهم‌سازی ایمنی در برابر شرایط بحرانی (thread safety) و جلوگیری از مشکلات وضعیت مشترک. + +> معایب: +> +> * سربار اضافی به دلیل ارسال پیام و مدیریت threadها. +> * برای تمام سناریوهای هم‌زمانی مناسب نیست. + +الگوهای طراحی مرتبط در جاوا + +> * [Command](https://java-design-patterns.com/patterns/command/): درخواست را به‌عنوان یک شیء کپسوله می‌کند، مشابه روشی که Active Object فراخوانی متد را کپسوله می‌کند. +> * [Promise](https://java-design-patterns.com/patterns/promise/): راهی برای دریافت نتیجه یک فراخوانی متد ناهمگام فراهم می‌کند؛ اغلب همراه با Active Object استفاده می‌شود. +> * [Proxy](https://java-design-patterns.com/patterns/proxy/): الگوی Active Object می‌تواند از proxy برای مدیریت فراخوانی‌های متد به‌صورت ناهمگام استفاده کند. + +منابع و مراجع + +> * [Design Patterns: Elements of Reusable Object Software](https://amzn.to/3HYqrBE) +> * [Concurrent Programming in Java: Design Principles and Patterns](https://amzn.to/498SRVq) +> * [Java Concurrency in Practice](https://amzn.to/4aRMruW) +> * [Learning Concurrent Programming in Scala](https://amzn.to/3UE07nV) +> * [Pattern Languages of Program Design 3](https://amzn.to/3OI1j61) +> * [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) + diff --git a/localization/fa/active-object/etc/active-object-sequence-diagram.png b/localization/fa/active-object/etc/active-object-sequence-diagram.png new file mode 100644 index 000000000000..b725d9b07b6d Binary files /dev/null and b/localization/fa/active-object/etc/active-object-sequence-diagram.png differ diff --git a/localization/fa/active-object/etc/active-object.urm.png b/localization/fa/active-object/etc/active-object.urm.png new file mode 100644 index 000000000000..c14f66144ee2 Binary files /dev/null and b/localization/fa/active-object/etc/active-object.urm.png differ diff --git a/localization/fa/active-object/etc/active-object.urm.puml b/localization/fa/active-object/etc/active-object.urm.puml new file mode 100644 index 000000000000..3fc3c8e1e921 --- /dev/null +++ b/localization/fa/active-object/etc/active-object.urm.puml @@ -0,0 +1,25 @@ +@startuml +package com.iluwatar.activeobject { + abstract class ActiveCreature { + - logger : Logger + - name : String + - requests : BlockingQueue + - thread : Thread + + ActiveCreature(name : String) + + eat() + + name() : String + + roam() + } + class App { + - creatures : Integer + - logger : Logger + + App() + + main(args : String[]) {static} + + run() + } + class Orc { + + Orc(name : String) + } +} +Orc --|> ActiveCreature +@enduml \ No newline at end of file diff --git a/localization/fa/factory/README.md b/localization/fa/factory/README.md new file mode 100644 index 000000000000..db41813464e3 --- /dev/null +++ b/localization/fa/factory/README.md @@ -0,0 +1,155 @@ +--- +title: "الگوی factory در جاوا: ساده‌سازی ایجاد اشیاء" +shortTitle: factory +description: "الگوی طراحی factory در جاوا را با مثال‌ها و توضیحات دقیق بیاموزید. یاد بگیرید چگونه با استفاده از الگوی factory کدی انعطاف‌پذیر و مقیاس‌پذیر ایجاد کنید. مناسب برای توسعه‌دهندگانی که به دنبال بهبود مهارت‌های طراحی شیءگرا هستند." +category: structural +language: fa +tag: + - Abstraction + - Encapsulation + - Gang of Four + - Instantiation + - Polymorphism +--- + +## هدف از الگوی طراحی factory + +الگوی طراحی factory در جاوا یک الگوی ساختاری است که یک رابط برای ایجاد یک شیء تعریف می‌کند اما به زیرکلاس‌ها اجازه می‌دهد نوع اشیائی را که ایجاد خواهند شد تغییر دهند. این الگو انعطاف‌پذیری و مقیاس‌پذیری را در کد شما ترویج می‌دهد. + +## توضیح دقیق الگوی factory با مثال‌های دنیای واقعی + +### مثال دنیای واقعی + +> تصور کنید در یک نانوایی انواع مختلف کیک‌ها با استفاده از الگوی طراحی factory ساخته می‌شوند. `CakeFactory` فرآیند ایجاد را مدیریت می‌کند و امکان افزودن آسان انواع جدید کیک‌ها را بدون تغییر در فرآیند اصلی فراهم می‌کند. `CakeFactory` می‌تواند انواع مختلفی از کیک‌ها مانند کیک شکلاتی، کیک وانیلی و کیک توت‌فرنگی تولید کند. به جای اینکه کارکنان نانوایی به صورت دستی مواد اولیه را انتخاب کنند و دستورالعمل‌های خاصی را برای هر نوع کیک دنبال کنند، از `CakeFactory` برای مدیریت فرآیند استفاده می‌کنند. مشتری فقط نوع کیک را درخواست می‌کند و `CakeFactory` مواد اولیه و دستورالعمل مناسب را تعیین کرده و نوع خاصی از کیک را ایجاد می‌کند. این تنظیم به نانوایی اجازه می‌دهد تا انواع جدید کیک‌ها را به راحتی اضافه کند بدون اینکه فرآیند اصلی تغییر کند، که این امر انعطاف‌پذیری و مقیاس‌پذیری را ترویج می‌دهد. + +### تعریف ویکی‌پدیا + +> الگوی factory یک شیء برای ایجاد اشیاء دیگر است – به طور رسمی، factory یک تابع یا متدی است که اشیاء با نمونه‌ها یا کلاس‌های مختلف را بازمی‌گرداند. + +### نمودار توالی + +![نمودار توالی factory](./etc/factory-sequence-diagram.png) + +## مثال برنامه‌نویسی از الگوی factory در جاوا + +تصور کنید یک کیمیاگر قصد دارد سکه‌هایی تولید کند. کیمیاگر باید بتواند هم سکه‌های طلا و هم سکه‌های مسی ایجاد کند و تغییر بین آن‌ها باید بدون تغییر در کد موجود امکان‌پذیر باشد. الگوی factory این امکان را فراهم می‌کند با ارائه یک متد ایجاد استاتیک که می‌توان آن را با پارامترهای مرتبط فراخوانی کرد. + +در جاوا، می‌توانید الگوی factory را با تعریف یک رابط `Coin` و پیاده‌سازی‌های آن `GoldCoin` و `CopperCoin` پیاده‌سازی کنید. کلاس `CoinFactory` یک متد استاتیک `getCoin` ارائه می‌دهد تا اشیاء سکه را بر اساس نوع ایجاد کند. + +```java +public interface Coin { + String getDescription(); +} +``` + +```java +public class GoldCoin implements Coin { + + static final String DESCRIPTION = "This is a gold coin."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} +``` + +```java +public class CopperCoin implements Coin { + + static final String DESCRIPTION = "This is a copper coin."; + + @Override + public String getDescription() { + return DESCRIPTION; + } +} +``` + +کد زیر انواع سکه‌هایی که پشتیبانی می‌شوند (`GoldCoin` و `CopperCoin`) را نشان می‌دهد. + +```java +@RequiredArgsConstructor +@Getter +public enum CoinType { + + COPPER(CopperCoin::new), + GOLD(GoldCoin::new); + + private final Supplier constructor; +} +``` + +سپس متد استاتیک `getCoin` برای ایجاد اشیاء سکه در کلاس factory `CoinFactory` کپسوله شده است. + +```java +public class CoinFactory { + + public static Coin getCoin(CoinType type) { + return type.getConstructor().get(); + } +} +``` + +اکنون، در کد کلاینت، می‌توانیم انواع مختلفی از سکه‌ها را با استفاده از کلاس factory تولید کنیم. + +```java +public static void main(String[] args) { + LOGGER.info("The alchemist begins his work."); + var coin1 = CoinFactory.getCoin(CoinType.COPPER); + var coin2 = CoinFactory.getCoin(CoinType.GOLD); + LOGGER.info(coin1.getDescription()); + LOGGER.info(coin2.getDescription()); +} +``` + +خروجی برنامه: + +``` +06:19:53.530 [main] INFO com.iluwatar.factory.App -- The alchemist begins his work. +06:19:53.533 [main] INFO com.iluwatar.factory.App -- This is a copper coin. +06:19:53.533 [main] INFO com.iluwatar.factory.App -- This is a gold coin. +``` + +## زمان استفاده از الگوی factory در جاوا + +* از الگوی طراحی factory در جاوا زمانی استفاده کنید که کلاس از قبل نوع دقیق و وابستگی‌های اشیائی که نیاز به ایجاد آن دارد را نمی‌داند. +* زمانی که یک متد یکی از چندین کلاس ممکن که یک کلاس والد مشترک دارند را بازمی‌گرداند و می‌خواهد منطق انتخاب شیء را کپسوله کند. +* این الگو معمولاً هنگام طراحی فریم‌ورک‌ها یا کتابخانه‌ها برای ارائه بهترین انعطاف‌پذیری و جداسازی از انواع کلاس‌های خاص استفاده می‌شود. + +## کاربردهای دنیای واقعی الگوی factory در جاوا + +> * [java.util.Calendar#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +> * [java.util.ResourceBundle#getBundle()](https://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +> * [java.text.NumberFormat#getInstance()](https://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +> * [java.nio.charset.Charset#forName()](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +> * این مورد [java.net.URLStreamHandlerFactory#createURLStreamHandler(String)](https://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html) اشیاء singleton مختلف را بر اساس یک پروتکل بازمی‌گرداند +> * [java.util.EnumSet#of()](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of(E)) +> * [javax.xml.bind.JAXBContext#createMarshaller()](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) و متدهای مشابه دیگر. +> +> * کتابخانه‌ی JavaFX از الگوهای factory برای ایجاد کنترل‌های مختلف رابط کاربری متناسب با نیازهای محیط کاربر استفاده می‌کند. + +## مزایا و معایب الگوی factory + +### مزایا: + +> * پیاده‌سازی الگوی factory در برنامه جاوای شما، وابستگی بین پیاده‌سازی و کلاس‌هایی که استفاده می‌کند را کاهش می‌دهد. +> * از [اصل Open/Closed](https://java-design-patterns.com/principles/#open-closed-principle) پشتیبانی می‌کند، زیرا سیستم می‌تواند انواع جدیدی را بدون تغییر کد موجود معرفی کند. + +### معایب: + +> * کد می‌تواند به دلیل معرفی چندین کلاس اضافی پیچیده‌تر شود. +> * استفاده بیش از حد می‌تواند کد را کمتر خوانا کند اگر پیچیدگی ایجاد اشیاء کم یا غیرضروری باشد. + +## الگوهای طراحی مرتبط با جاوا + +> * الگوی [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): می‌توان آن را نوعی factory در نظر گرفت که با گروهی از محصولات کار می‌کند. +> * الگوی [Singleton](https://java-design-patterns.com/patterns/singleton/): اغلب همراه با factory استفاده می‌شود تا اطمینان حاصل شود که یک کلاس تنها یک نمونه دارد. +> * الگوی [Builder](https://java-design-patterns.com/patterns/builder/): ساخت یک شیء پیچیده را از نمایش آن جدا می‌کند، مشابه نحوه‌ای که factoryها مدیریت نمونه‌سازی را انجام می‌دهند. +> * الگوی [Factory Kit](https://java-design-patterns.com/patterns/factory-kit/): یک factory از محتوای غیرقابل تغییر با رابط‌های builder و factory جداگانه است. + +## منابع و اعتبارات + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0Rk5y) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3UpTLrG) diff --git a/localization/fa/factory/etc/factory-sequence-diagram.png b/localization/fa/factory/etc/factory-sequence-diagram.png new file mode 100644 index 000000000000..260bea92f247 Binary files /dev/null and b/localization/fa/factory/etc/factory-sequence-diagram.png differ diff --git a/localization/ko/factory-method/README.md b/localization/ko/factory-method/README.md new file mode 100644 index 000000000000..9a1cf3e709d5 --- /dev/null +++ b/localization/ko/factory-method/README.md @@ -0,0 +1,131 @@ +--- +title: "Factory Method" +shortTitle: Factory Method +category: Creational +language: ko +tag: + - Gang of Four +--- + +## 또한 ~으로 알려진 + +* Virtual Constructor + +## 의도 + +팩토리 메소드 패턴을 사용하여 객체를 생성하기 위한 인터페이스를 정의하고, 어떤 클래스를 인스턴스화할지는 하위 클래스가 결정하도록 합니다. 이 생성 설계 패턴은 클래스가 인스턴스 생성을 하위 클래스로 위임하여 코드 유연성과 유지보수성을 향상시킬 수 있습니다. + +## 설명 + +예시 + +> 한 물류 회사가 다양한 유형의 패키지를 배송해야 한다고 가정해 봅시다. 일반, 급송, 그리고 대형 배송 패키지가 있습니다. 이 회사는 중앙 시스템을 통해 배송 요청을 처리하지만, 각 패키지 유형이 어떻게 처리되는지에 대한 구체적인 사항은 알지 못합니다. 이를 관리하기 위해 이 회사는 팩토리 메소드 패턴을 사용합니다. +> +> 이 구조에서 중앙 클래스인 `DeliveryRequest`에는 `createPackage()`라는 메서드가 있습니다. 이 메서드는 `StandardDelivery`, `ExpressDelivery`, `OversizedDelivery`와 같은 하위 클래스에서 오버라이딩되며, 각 하위 클래스는 해당 패키지 유형을 생성하고 관리하는 방법을 알고 있습니다. 이렇게 함으로써 중앙 시스템은 각 패키지 유형이 어떻게 생성되고 처리되는지에 대한 세부 사항을 알 필요 없이 배송 요청을 처리할 수 있어, 유연하고 쉽게 유지보수가 가능합니다. + +평범하게 말하자면, + +> 이 패턴은 인스턴스 생성 로직을 자식 클래스에 위임하는 방법을 제공합니다. + +Wikipedia 말에 의하면 + +> 클래스 기반 프로그래밍에서, 팩토리 메소드 패턴은 생성 패턴 중 하나로, 생성할 객체의 구체적인 클래스를 명시하지 않고 객체를 생성하는 문제를 해결합니다. 이를 위해 생성자는 직접 호출하지 않고, 팩토리 메소드를 호출하여 객체를 생성하는 방식을 사용합니다. 팩토리 메소드는 인터페이스에 명시되어 자식 클래스가 구현하거나, 기본 클래스에 구현되어 파생 클래스에서 필요에 따라 오버라이딩할 수 있습니다. + +## 프로그램 코드 예제 + +팩토리 메소드 접근 방식은 다음 예제에서 볼 수 있듯이 유연하고 유지 관리 가능한 코드를 달성하기 위한 자바 디자인 패턴에서 중추적인 역할을 합니다. + +대장장이가 무기를 제작합니다. 엘프는 엘프족 무기를, 오크는 오크족 무기를 필요로 합니다. 고객에 따라 적합한 유형의 대장장이가 소환됩니다. + +우선, `Blacksmith` 인터페이스와 이를 구현한 몇 가지 클래스들이 있습니다. + +```java +public interface Blacksmith { + Weapon manufactureWeapon(WeaponType weaponType); +} + +public class ElfBlacksmith implements Blacksmith { + public Weapon manufactureWeapon(WeaponType weaponType) { + return ELFARSENAL.get(weaponType); + } +} + +public class OrcBlacksmith implements Blacksmith { + public Weapon manufactureWeapon(WeaponType weaponType) { + return ORCARSENAL.get(weaponType); + } +} +``` + +고객이 오면, 올바른 유형의 대장장이가 소환되어 요청된 무기를 제작합니다. + +```java +public static void main(String[] args) { + + Blacksmith blacksmith = new OrcBlacksmith(); + Weapon weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); + LOGGER.info(MANUFACTURED, blacksmith, weapon); + weapon = blacksmith.manufactureWeapon(WeaponType.AXE); + LOGGER.info(MANUFACTURED, blacksmith, weapon); + + blacksmith = new ElfBlacksmith(); + weapon = blacksmith.manufactureWeapon(WeaponType.SPEAR); + LOGGER.info(MANUFACTURED, blacksmith, weapon); + weapon = blacksmith.manufactureWeapon(WeaponType.AXE); + LOGGER.info(MANUFACTURED, blacksmith, weapon); +} +``` + +프로그램 실행 결과 +``` +06:40:07.269 [main] INFO com.iluwatar.factory.method.App -- The orc blacksmith manufactured an orcish spear +06:40:07.271 [main] INFO com.iluwatar.factory.method.App -- The orc blacksmith manufactured an orcish axe +06:40:07.272 [main] INFO com.iluwatar.factory.method.App -- The elf blacksmith manufactured an elven spear +06:40:07.272 [main] INFO com.iluwatar.factory.method.App -- The elf blacksmith manufactured an elven axe +``` + +## 클래스 다이어그램 + +![alt text](./etc/factory-method.urm.png "Factory Method Class Diagram") + +## 적용 가능성 + +자바에서 팩토리 메소드 패턴을 사용하는 경우: + +* 클래스가 생성해야 할 객체의 클래스를 예측할 수 없을 때 +* 클래스가 생성할 객체를 자식 클래스가 지정하도록 하고 싶을 때 +* 여러 보조 자식 클래스 중 하나에 책임을 위임하고, 어느 자식 클래스가 해당 작업을 담당하는지에 대한 정보를 국지화하고 싶을 때 + +## 실제 사례 + +* [java.util.Calendar](http://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#getInstance--) +* [java.util.ResourceBundle](http://docs.oracle.com/javase/8/docs/api/java/util/ResourceBundle.html#getBundle-java.lang.String-) +* [java.text.NumberFormat](http://docs.oracle.com/javase/8/docs/api/java/text/NumberFormat.html#getInstance--) +* [java.nio.charset.Charset](http://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html#forName-java.lang.String-) +* [java.net.URLStreamHandlerFactory](http://docs.oracle.com/javase/8/docs/api/java/net/URLStreamHandlerFactory.html#createURLStreamHandler-java.lang.String-) +* [java.util.EnumSet](https://docs.oracle.com/javase/8/docs/api/java/util/EnumSet.html#of-E-) +* [javax.xml.bind.JAXBContext](https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/JAXBContext.html#createMarshaller--) +* Frameworks that run application components, configured dynamically at runtime. + +## 장단점 + +장점: + +* 팩토리 메소드 패턴은 하위 클래스에 대한 후크(hooks)를 제공하여 코드의 유연성과 유지보수성을 높입니다. +* 병렬적인 클래스 계층 구조를 연결합니다. +* 애플리케이션 특화 클래스를 코드에 직접 포함할 필요가 없어집니다. 코드가 제품 인터페이스와만 상호작용하므로, 사용자 정의 구체 클래스와 쉽게 연동할 수 있습니다. + +단점: + +* 확장된 팩토리 메소드를 구현하기 위해 새로운 하위 클래스를 추가해야 하므로 코드가 복잡해질 수 있습니다. + +## 관련 패턴 + +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): 팩토리 메소드는 종종 추상 팩토리 패턴 내에서 호출됩니다. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): 프로토타입 클래스를 복제한 새 인스턴스를 반환하는 팩토리 메소드. + +## 크레딧 + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0Rk5y) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/3UpTLrG) +* [Patterns of Enterprise Application Architecture](https://amzn.to/4b2ZxoM) diff --git a/localization/ko/factory-method/etc/factory-method.urm.png b/localization/ko/factory-method/etc/factory-method.urm.png new file mode 100644 index 000000000000..7c97aff91ee6 Binary files /dev/null and b/localization/ko/factory-method/etc/factory-method.urm.png differ diff --git a/localization/ko/strategy/README.md b/localization/ko/strategy/README.md index e445fa91d3d2..f604962d1f6b 100644 --- a/localization/ko/strategy/README.md +++ b/localization/ko/strategy/README.md @@ -123,7 +123,7 @@ public class LambdaStrategy { public enum Strategy implements DragonSlayingStrategy { MeleeStrategy(() -> LOGGER.info( - "With your Excalibur you severe the dragon's head!")), + "With your Excalibur you sever the dragon's head!")), ProjectileStrategy(() -> LOGGER.info( "You shoot the dragon with the magical crossbow and it falls dead on the ground!")), SpellStrategy(() -> LOGGER.info( diff --git a/localization/pt/singleton/README.md b/localization/pt/singleton/README.md new file mode 100644 index 000000000000..83604d959368 --- /dev/null +++ b/localization/pt/singleton/README.md @@ -0,0 +1,109 @@ +--- +title: "Singleton Pattern in Java: Implementing Global Access Points in Java Applications" +shortTitle: Singleton +description: "Explore the Singleton Pattern in Java with our comprehensive guide. Learn how to implement efficient object management for your Java applications, ensuring optimal use of resources and easy access with examples and detailed explanations." +category: Creational +language: pt +tag: + - Gang of Four + - Instantiation + - Lazy initialization + - Resource management +--- + +## Também conhecido como + +* Single Instance + +## Propósito + +Assegurar que a classe Java possua somente uma instância e forneça um ponto global de acesso a essa instância Singleton. + +## Explicação + +Exemplo do Mundo Real + +> Uma analogia do mundo real para o padrão Singleton é um governo emitindo um passaporte. Em um país, cada cidadão pode receber apenas um passaporte válido por vez. O escritório de passaportes garante que nenhum passaporte duplicado seja emitido para a mesma pessoa. Sempre que um cidadão precisa viajar, ele deve usar este passaporte único, que serve como o identificador único e globalmente reconhecido para suas credenciais de viagem. Este acesso controlado e gerenciamento de instância exclusivo espelham como o padrão Singleton garante o gerenciamento eficiente de objetos em aplicativos Java. + +Em outras palavras + +> Assegura que somente um objeto de classe em particular seja criado. + +De acordo com a Wikipédia + +> Na engenharia de software, o padrão Singleton é um padrão de design de software que restringe a instanciação de uma classe a um objeto. Isso é útil quando exatamente um objeto é necessário para coordenar ações no sistema. + +## Exemplo Programático + +Joshua Bloch, Effective Java 2nd Edition pg.18 + +> Um tipo Enum de elemento único é a melhor maneira de implementar um Singleton + +```java +public enum EnumIvoryTower { + INSTANCE +} +``` + +Então para usar: + +```java + var enumIvoryTower1 = EnumIvoryTower.INSTANCE; + var enumIvoryTower2 = EnumIvoryTower.INSTANCE; + LOGGER.info("enumIvoryTower1={}", enumIvoryTower1); + LOGGER.info("enumIvoryTower2={}", enumIvoryTower2); +``` + +A saída do console + +``` +enumIvoryTower1=com.iluwatar.singleton.EnumIvoryTower@1221555852 +enumIvoryTower2=com.iluwatar.singleton.EnumIvoryTower@1221555852 +``` + +## Quando usar + +Use o padrão Singleton quando + +* Deve haver exatamente uma instância de uma classe, e ela deve ser acessível aos clientes a partir de um ponto de acesso bem conhecido +* Quando a única instância deve ser extensível por herança, e os clientes devem ser capazes de usar uma instância estendida sem modificar seu código + +## Aplicações do mundo real + +* A classe de Log +* Classes de configuração em muitos aplicativos +* Pools de conexão +* Gerenciador de arquivos +* [java.lang.Runtime#getRuntime()](http://docs.oracle.com/javase/8/docs/api/java/lang/Runtime.html#getRuntime%28%29) +* [java.awt.Desktop#getDesktop()](http://docs.oracle.com/javase/8/docs/api/java/awt/Desktop.html#getDesktop--) +* [java.lang.System#getSecurityManager()](http://docs.oracle.com/javase/8/docs/api/java/lang/System.html#getSecurityManager--) + +## Benefícios e desafios do padrão Singleton + +Benefícios: + +* Acesso controlado à instância única. +* Poluição reduzida do namespace. +* Permite refinamento de operações e representação. +* Permite um número variável de instâncias (se desejado mais de uma). +* Mais flexível do que operações de classe. + +Desafios: + +* Difícil de testar devido ao estado global. +* Gerenciamento de ciclo de vida potencialmente mais complexo. +* Pode introduzir gargalos se usado em um contexto concorrente sem sincronização cuidadosa. + +## Outros Padrōes de Projeto Relacionados + +* [Abstract Factory](https://java-design-patterns.com/patterns/abstract-factory/): Geralmente usado para garantir que uma classe tenha apenas uma instância. +* [Factory Method](https://java-design-patterns.com/patterns/factory-method/): O padrão Singleton pode ser implementado usando o padrāo Factory para encapsular a lógica de criação. +* [Prototype](https://java-design-patterns.com/patterns/prototype/): Evita a necessidade de criar instâncias, pode ser usado em conjunto com o Singleton para gerenciar instâncias únicas. + +## Referências e Créditos + +* [Design Patterns: Elements of Reusable Object-Oriented Software](https://amzn.to/3w0pvKI) +* [Effective Java](https://amzn.to/4cGk2Jz) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3yhh525) +* [Refactoring to Patterns](https://amzn.to/3VOO4F5) diff --git a/lockable-object/README.md b/lockable-object/README.md index 9ae3caf52834..108d27c5e337 100644 --- a/lockable-object/README.md +++ b/lockable-object/README.md @@ -35,6 +35,10 @@ Wikipedia says > In computer science, a lock or mutex (from mutual exclusion) is a synchronization primitive that prevents state from being modified or accessed by multiple threads of execution at once. Locks enforce mutual exclusion concurrency control policies, and with a variety of possible methods there exist multiple unique implementations for different applications. +Sequence diagram + +![Lockable Object Sequence Diagram](./etc/lockable-object-sequence-diagram.png) + ## Programmatic Example of Lockable Object Pattern in Java The Lockable Object pattern is a concurrency control design pattern in Java that allows only one thread to access a shared resource at a time, ensuring mutual exclusion and preventing data corruption. Instead of using the `synchronized` keyword on the methods to be synchronized, the object which implements the Lockable interface handles the request. @@ -127,10 +131,6 @@ public class App implements Runnable { This example demonstrates the Lockable Object pattern by showing how multiple threads can attempt to acquire a lock on a shared resource, with only one thread being able to acquire the lock at a time. -## Detailed Explanation of Lockable Object Pattern with Real-World Examples - -![Lockable Object](./etc/lockable-object.urm.png "Lockable Object class diagram") - ## When to Use the Lockable Object Pattern in Java * Use the Lockable Object pattern in Java when you need to prevent data corruption by multiple threads accessing a shared resource concurrently, ensuring thread safety and robust shared resource management. diff --git a/lockable-object/etc/lockable-object-sequence-diagram.png b/lockable-object/etc/lockable-object-sequence-diagram.png new file mode 100644 index 000000000000..10b08a5f15f1 Binary files /dev/null and b/lockable-object/etc/lockable-object-sequence-diagram.png differ diff --git a/lockable-object/pom.xml b/lockable-object/pom.xml index 432cd5b1b245..a96ec972e726 100644 --- a/lockable-object/pom.xml +++ b/lockable-object/pom.xml @@ -34,6 +34,14 @@ lockable-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/App.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/App.java index 2199e2d8d0e6..1c05f7d33428 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/App.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/App.java @@ -43,11 +43,10 @@ * *

    In this example, we create a new Lockable object with the SwordOfAragorn implementation of it. * Afterward we create 6 Creatures with the Elf, Orc and Human implementations and assign them each - * to a Fiend object and the Sword is the target object. Because there is only one Sword, and it uses - * the Lockable Object pattern, only one creature can hold the sword at a given time. When the sword - * is locked, any other alive Fiends will try to lock, which will result in a race to lock the + * to a Fiend object and the Sword is the target object. Because there is only one Sword, and it + * uses the Lockable Object pattern, only one creature can hold the sword at a given time. When the + * sword is locked, any other alive Fiends will try to lock, which will result in a race to lock the * sword. - * */ @Slf4j public class App implements Runnable { diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/LockingException.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/LockingException.java index 8d9038f5348d..dd1d2b8f7d9b 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/LockingException.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/LockingException.java @@ -26,16 +26,12 @@ import java.io.Serial; -/** - * An exception regarding the locking process of a Lockable object. - */ +/** An exception regarding the locking process of a Lockable object. */ public class LockingException extends RuntimeException { - @Serial - private static final long serialVersionUID = 8556381044865867037L; + @Serial private static final long serialVersionUID = 8556381044865867037L; public LockingException(String message) { super(message); } - } diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/SwordOfAragorn.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/SwordOfAragorn.java index 85a5d7df5f30..8e05ea6e684b 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/SwordOfAragorn.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/SwordOfAragorn.java @@ -29,8 +29,8 @@ import lombok.extern.slf4j.Slf4j; /** - * An implementation of a Lockable object. This is the Sword of Aragorn and every creature wants - * to possess it! + * An implementation of a Lockable object. This is the Sword of Aragorn and every creature wants to + * possess it! */ @Slf4j public class SwordOfAragorn implements Lockable { diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Creature.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Creature.java index 4380e77555f8..05b6db00129b 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Creature.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/Creature.java @@ -109,5 +109,4 @@ public synchronized void hit(int damage) { public synchronized boolean isAlive() { return getHealth() > 0; } - } diff --git a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/CreatureStats.java b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/CreatureStats.java index 1d2a4040f8b0..7996a0269f9a 100644 --- a/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/CreatureStats.java +++ b/lockable-object/src/main/java/com/iluwatar/lockableobject/domain/CreatureStats.java @@ -35,8 +35,7 @@ public enum CreatureStats { HUMAN_HEALTH(60), HUMAN_DAMAGE(60); - @Getter - final int value; + @Getter final int value; CreatureStats(int value) { this.value = value; diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/CreatureTest.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/CreatureTest.java index f2fec0531dd4..de7fc3678089 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/CreatureTest.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/CreatureTest.java @@ -97,7 +97,7 @@ void killCreature(Creature source, Creature target) { } @Test - void invalidDamageTest(){ + void invalidDamageTest() { Assertions.assertThrows(IllegalArgumentException.class, () -> elf.hit(-50)); } } diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/ExceptionsTest.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/ExceptionsTest.java index 614ed9655b08..26a97853e78a 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/ExceptionsTest.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/ExceptionsTest.java @@ -32,12 +32,11 @@ class ExceptionsTest { private static final String MSG = "test"; @Test - void testException(){ + void testException() { Exception e; - try{ + try { throw new LockingException(MSG); - } - catch(LockingException ex){ + } catch (LockingException ex) { e = ex; } Assertions.assertEquals(MSG, e.getMessage()); diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/FeindTest.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/FeindTest.java index 70b864a1c479..cedaf65b0d59 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/FeindTest.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/FeindTest.java @@ -39,14 +39,14 @@ class FeindTest { private Lockable sword; @BeforeEach - void init(){ + void init() { elf = new Elf("Nagdil"); orc = new Orc("Ghandar"); sword = new SwordOfAragorn(); } @Test - void nullTests(){ + void nullTests() { Assertions.assertThrows(NullPointerException.class, () -> new Feind(null, null)); Assertions.assertThrows(NullPointerException.class, () -> new Feind(elf, null)); Assertions.assertThrows(NullPointerException.class, () -> new Feind(null, sword)); @@ -67,5 +67,4 @@ void testBaseCase() throws InterruptedException { sword.unlock(elf.isAlive() ? elf : orc); Assertions.assertNull(sword.getLocker()); } - } diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/SubCreaturesTests.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/SubCreaturesTests.java index 766069b89165..b6db856853c4 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/SubCreaturesTests.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/SubCreaturesTests.java @@ -34,7 +34,7 @@ class SubCreaturesTests { @Test - void statsTest(){ + void statsTest() { var elf = new Elf("Limbar"); var orc = new Orc("Dargal"); var human = new Human("Jerry"); diff --git a/lockable-object/src/test/java/com/iluwatar/lockableobject/TheSwordOfAragornTest.java b/lockable-object/src/test/java/com/iluwatar/lockableobject/TheSwordOfAragornTest.java index e4b604e7052d..4c40f806b3cb 100644 --- a/lockable-object/src/test/java/com/iluwatar/lockableobject/TheSwordOfAragornTest.java +++ b/lockable-object/src/test/java/com/iluwatar/lockableobject/TheSwordOfAragornTest.java @@ -43,7 +43,7 @@ void basicSwordTest() { } @Test - void invalidLockerTest(){ + void invalidLockerTest() { var sword = new SwordOfAragorn(); Assertions.assertThrows(NullPointerException.class, () -> sword.lock(null)); Assertions.assertThrows(NullPointerException.class, () -> sword.unlock(null)); diff --git a/map-reduce/README.md b/map-reduce/README.md new file mode 100644 index 000000000000..d832616ba5a2 --- /dev/null +++ b/map-reduce/README.md @@ -0,0 +1,265 @@ +--- +title: "MapReduce Pattern in Java" +shortTitle: MapReduce +description: "Learn the MapReduce pattern in Java with real-world examples, class diagrams, and tutorials. Understand its intent, applicability, benefits, and known uses to enhance your design pattern knowledge." +category: Functional +language: en +tag: + - Concurrency + - Data processing + - Data transformation + - Functional decomposition + - Immutable + - Multithreading + - Scalability +--- + +## Also known as + +* Map-Reduce +* Divide and Conquer for Data Processing + +## Intent of Map Reduce Design Pattern + +To efficiently process large-scale datasets by dividing computation into two phases: map and reduce, which can be executed in parallel and distributed across multiple nodes. + +## Detailed Explanation of Map Reduce Pattern with Real-World Examples + +Real-world example + +> Imagine a large e-commerce company that wants to analyze its sales data across multiple regions. They have terabytes of transaction data stored across hundreds of servers. Using MapReduce, they can efficiently process this data to calculate total sales by product category. The Map function would process individual sales records, emitting key-value pairs of (category, sale amount). The Reduce function would then sum up all sale amounts for each category, producing the final result. + +In plain words + +> MapReduce splits a large problem into smaller parts, processes them in parallel, and then combines the results. + +Wikipedia says + +> MapReduce is a programming model and associated implementation for processing and generating big data sets with a parallel, distributed algorithm on a cluster. MapReduce consists of two main steps: +The Map step: The master node takes the input, divides it into smaller sub-problems, and distributes them to worker nodes. A worker node may do this again in turn, leading to a multi-level tree structure. The worker node processes the smaller problem, and passes the answer back to its master node. The Reduce step: The master node then collects the answers to all the sub-problems and combines them in some way to form the output – the answer to the problem it was originally trying to solve. This approach allows for efficient processing of vast amounts of data across multiple machines, making it a fundamental technique in big data analytics and distributed computing. + +Flowchart + +![MapReduce flowchart](./etc/mapreduce-flowchart.png) + +## Programmatic Example of Map Reduce in Java + +### 1. Map Phase (Splitting & Processing Data) + +* Each input string is split into words, normalized, and counted. +* Output: A map `{word → count}` for each input string. + +#### `Mapper.java` + +```java +public class Mapper { + public static Map map(String input) { + Map wordCount = new HashMap<>(); + String[] words = input.split("\\s+"); + for (String word : words) { + word = word.toLowerCase().replaceAll("[^a-z]", ""); + if (!word.isEmpty()) { + wordCount.put(word, wordCount.getOrDefault(word, 0) + 1); + } + } + return wordCount; + } +} +``` + +Example Input: ```"Hello world hello"``` +Output: ```{hello=2, world=1}``` + +### 2. Shuffle Phase – Grouping Words Across Inputs + +* Takes results from all mappers and groups values by word. +* Output: A map `{word → list of counts}`. + +#### `Shuffler.java` + +```java +public class Shuffler { + public static Map> shuffleAndSort(List> mapped) { + Map> grouped = new HashMap<>(); + for (Map map : mapped) { + for (Map.Entry entry : map.entrySet()) { + grouped.putIfAbsent(entry.getKey(), new ArrayList<>()); + grouped.get(entry.getKey()).add(entry.getValue()); + } + } + return grouped; + } +} +``` + +Example Input: + +``` +[ + {"hello": 2, "world": 1}, + {"hello": 1, "java": 1} +] +``` + +Output: + +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` + +### 3. Reduce Phase – Aggregating Counts + +* Sums the list of counts for each word. +* Output: A sorted list of word counts in descending order. + +#### `Reducer.java` + +```java +public class Reducer { + public static List> reduce(Map> grouped) { + Map reduced = new HashMap<>(); + for (Map.Entry> entry : grouped.entrySet()) { + reduced.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum()); + } + + List> result = new ArrayList<>(reduced.entrySet()); + result.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + return result; + } +} +``` + +Example Input: + +``` +{ + "hello": [2, 1], + "world": [1], + "java": [1] +} +``` + +Output: + +``` +[ + {"hello": 3}, + {"world": 1}, + {"java": 1} +] +``` + +### 4. MapReduce Coordinator – Running the Whole Pipeline + +* Coordinates map, shuffle, and reduce phases. + +#### `MapReduce.java` + +```java +public class MapReduce { + public static List> mapReduce(List inputs) { + List> mapped = new ArrayList<>(); + for (String input : inputs) { + mapped.add(Mapper.map(input)); + } + + Map> grouped = Shuffler.shuffleAndSort(mapped); + + return Reducer.reduce(grouped); + } +} +``` + +### 5. Main Execution – Example Usage + +* Runs the MapReduce process and prints results. + +#### `Main.java` + +```java + public static void main(String[] args) { + List inputs = Arrays.asList( + "Hello world hello", + "MapReduce is fun", + "Hello from the other side", + "Hello world" + ); + List> result = MapReduce.mapReduce(inputs); + for (Map.Entry entry : result) { + System.out.println(entry.getKey() + ": " + entry.getValue()); + } +} +``` + +Output: + +``` +hello: 4 +world: 2 +the: 1 +other: 1 +side: 1 +mapreduce: 1 +is: 1 +from: 1 +fun: 1 +``` + +## When to Use the Map Reduce Pattern in Java + +Use MapReduce when: + +* When processing large datasets that can be broken into independent chunks. +* When data operations can be naturally divided into map (transformation) and reduce (aggregation) phases. +* When horizontal scalability and parallelization are essential, especially in distributed or big data environments. +* When leveraging Java-based distributed computing platforms like Hadoop or Spark. + +## Map Reduce Pattern Java Tutorials + +* [MapReduce Tutorial(Apache Hadoop)](https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html) +* [MapReduce Example(Simplilearn)](https://www.youtube.com/watch?v=l2clwKnrtO8) + +## Benefits and Trade-offs of Map Reduce Pattern + +Benefits: + +* Enables massive scalability by distributing processing across nodes. +* Encourages a functional style, promoting immutability and stateless operations. +* Simplifies complex data workflows by separating transformation (map) from aggregation (reduce). +* Fault-tolerant due to isolated, recoverable processing tasks. + +Trade-offs: + +* Requires a suitable problem structure — not all tasks fit the map/reduce paradigm. +* Data shuffling between map and reduce phases can be performance-intensive. +* Higher complexity in debugging and optimizing distributed jobs. +* Intermediate I/O can become a bottleneck in large-scale operations. + +## Real-World Applications of Map Reduce Pattern in Java + +* Hadoop MapReduce: Java-based framework for distributed data processing using MapReduce. +* Apache Spark: Utilizes similar map and reduce transformations in its RDD and Dataset APIs. +* Elasticsearch: Uses MapReduce-style aggregation pipelines for querying distributed data. +* Google Bigtable: Underlying storage engine influenced by MapReduce principles. +* MongoDB Aggregation Framework: Conceptually applies MapReduce in its data pipelines. + +## Related Java Design Patterns + +* [Master-Worker](https://java-design-patterns.com/patterns/master-worker/): Similar distribution of tasks among workers, with a master coordinating job execution. +* [Pipeline](https://java-design-patterns.com/patterns/pipeline/): Can be used to chain multiple MapReduce operations into staged transformations. +* [Iterator](https://java-design-patterns.com/patterns/iterator/): Often used under the hood to process input streams lazily in map and reduce steps. + +## References and Credits + +* [Big Data: Principles and Paradigms](https://amzn.to/3RJIGPZ) +* [Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems](https://amzn.to/3E6VhtD) +* [Hadoop: The Definitive Guide: Storage and Analysis at Internet Scale](https://amzn.to/4ij2y7F) +* [Java 8 in Action: Lambdas, Streams, and functional-style programming](https://amzn.to/3QCmGXs) +* [Java Design Patterns: A Hands-On Experience with Real-World Examples](https://amzn.to/3HWNf4U) +* [Programming Pig: Dataflow Scripting with Hadoop](https://amzn.to/4cAU36K) +* [What is MapReduce (IBM)](https://www.ibm.com/think/topics/mapreduce) +* [Why MapReduce is not dead (Codemotion)](https://www.codemotion.com/magazine/ai-ml/big-data/mapreduce-not-dead-heres-why-its-still-ruling-in-the-cloud/) diff --git a/map-reduce/etc/map-reduce.png b/map-reduce/etc/map-reduce.png new file mode 100644 index 000000000000..caf764553291 Binary files /dev/null and b/map-reduce/etc/map-reduce.png differ diff --git a/map-reduce/etc/map-reduce.urm.puml b/map-reduce/etc/map-reduce.urm.puml new file mode 100644 index 000000000000..508f1fd3363d --- /dev/null +++ b/map-reduce/etc/map-reduce.urm.puml @@ -0,0 +1,24 @@ +@startuml +package com.iluwatar { + class Main { + + Main() + + main(args : String[]) {static} + } + class MapReduce { + + MapReduce() + + mapReduce(inputs : List) : List> {static} + } + class Mapper { + + Mapper() + + map(input : String) : Map {static} + } + class Reducer { + + Reducer() + + reduce(grouped : Map>) : List> {static} + } + class Shuffler { + + Shuffler() + + shuffleAndSort(mapped : List>) : Map> {static} + } +} +@enduml \ No newline at end of file diff --git a/map-reduce/etc/mapreduce-flowchart.png b/map-reduce/etc/mapreduce-flowchart.png new file mode 100644 index 000000000000..dade8f7ccdf2 Binary files /dev/null and b/map-reduce/etc/mapreduce-flowchart.png differ diff --git a/map-reduce/pom.xml b/map-reduce/pom.xml new file mode 100644 index 000000000000..4778f1bdb7ca --- /dev/null +++ b/map-reduce/pom.xml @@ -0,0 +1,62 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + map-reduce + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.mapreduce.Main + + + + + + + + + diff --git a/map-reduce/src/main/java/com/iluwatar/Main.java b/map-reduce/src/main/java/com/iluwatar/Main.java new file mode 100644 index 000000000000..98e6381235ae --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Main.java @@ -0,0 +1,53 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +/** + * The Main class serves as the entry point for executing the MapReduce program. It processes a list + * of text inputs, applies the MapReduce pattern, and prints the results. + */ +public class Main { + private static final Logger logger = Logger.getLogger(Main.class.getName()); + + /** + * The main method initiates the MapReduce process and displays the word count results. + * + * @param args Command-line arguments (not used). + */ + public static void main(String[] args) { + List inputs = + Arrays.asList( + "Hello world hello", "MapReduce is fun", "Hello from the other side", "Hello world"); + List> result = MapReduce.mapReduce(inputs); + for (Map.Entry entry : result) { + logger.info(entry.getKey() + ": " + entry.getValue()); + } + } +} diff --git a/map-reduce/src/main/java/com/iluwatar/MapReduce.java b/map-reduce/src/main/java/com/iluwatar/MapReduce.java new file mode 100644 index 000000000000..49b6f0a1d3d6 --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/MapReduce.java @@ -0,0 +1,57 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * The MapReduce class orchestrates the MapReduce process, calling the Mapper, Shuffler, and Reducer + * components. + */ +public class MapReduce { + private MapReduce() { + throw new UnsupportedOperationException( + "MapReduce is a utility class and cannot be instantiated."); + } + + /** + * Executes the MapReduce process on the given list of input strings. + * + * @param inputs List of input strings to be processed. + * @return A list of word counts sorted in descending order. + */ + public static List> mapReduce(List inputs) { + List> mapped = new ArrayList<>(); + for (String input : inputs) { + mapped.add(Mapper.map(input)); + } + + Map> grouped = Shuffler.shuffleAndSort(mapped); + + return Reducer.reduce(grouped); + } +} diff --git a/map-reduce/src/main/java/com/iluwatar/Mapper.java b/map-reduce/src/main/java/com/iluwatar/Mapper.java new file mode 100644 index 000000000000..c048c5e4a6d1 --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Mapper.java @@ -0,0 +1,57 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.HashMap; +import java.util.Map; + +/** + * The Mapper class is responsible for processing an input string and generating a map of word + * occurrences. + */ +public class Mapper { + private Mapper() { + throw new UnsupportedOperationException( + "Mapper is a utility class and cannot be instantiated."); + } + + /** + * Splits a given input string into words and counts their occurrences. + * + * @param input The input string to be mapped. + * @return A map where keys are words and values are their respective counts. + */ + public static Map map(String input) { + Map wordCount = new HashMap<>(); + String[] words = input.split("\\s+"); + for (String word : words) { + word = word.toLowerCase().replaceAll("[^a-z]", ""); + if (!word.isEmpty()) { + wordCount.put(word, wordCount.getOrDefault(word, 0) + 1); + } + } + return wordCount; + } +} diff --git a/map-reduce/src/main/java/com/iluwatar/Reducer.java b/map-reduce/src/main/java/com/iluwatar/Reducer.java new file mode 100644 index 000000000000..fbfc8d09d61a --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Reducer.java @@ -0,0 +1,56 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** The Reducer class is responsible for aggregating word counts from the shuffled data. */ +public class Reducer { + private Reducer() { + throw new UnsupportedOperationException( + "Reducer is a utility class and cannot be instantiated."); + } + + /** + * Sums the occurrences of each word and sorts the results in descending order. + * + * @param grouped A map where keys are words and values are lists of their occurrences. + * @return A sorted list of word counts in descending order. + */ + public static List> reduce(Map> grouped) { + Map reduced = new HashMap<>(); + for (Map.Entry> entry : grouped.entrySet()) { + reduced.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum()); + } + + List> result = new ArrayList<>(reduced.entrySet()); + result.sort(Map.Entry.comparingByValue(Comparator.reverseOrder())); + return result; + } +} diff --git a/map-reduce/src/main/java/com/iluwatar/Shuffler.java b/map-reduce/src/main/java/com/iluwatar/Shuffler.java new file mode 100644 index 000000000000..cf58bc9019e3 --- /dev/null +++ b/map-reduce/src/main/java/com/iluwatar/Shuffler.java @@ -0,0 +1,56 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** The Shuffler class is responsible for grouping word occurrences from multiple mappers. */ +public class Shuffler { + + private Shuffler() { + throw new UnsupportedOperationException( + "Shuffler is a utility class and cannot be instantiated."); + } + + /** + * Merges multiple word count maps into a single grouped map. + * + * @param mapped List of maps containing word counts from the mapping phase. + * @return A map where keys are words and values are lists of their occurrences across inputs. + */ + public static Map> shuffleAndSort(List> mapped) { + Map> grouped = new HashMap<>(); + for (Map map : mapped) { + for (Map.Entry entry : map.entrySet()) { + grouped.putIfAbsent(entry.getKey(), new ArrayList<>()); + grouped.get(entry.getKey()).add(entry.getValue()); + } + } + return grouped; + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/MapReduceTest.java b/map-reduce/src/test/java/com/iluwatar/MapReduceTest.java new file mode 100644 index 000000000000..6abaa2142c7d --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/MapReduceTest.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import org.junit.jupiter.api.Test; + +class MapReduceTest { + + @Test + void testMapReduce() { + List inputs = + Arrays.asList("Hello world hello", "MapReduce is fun", "Hello from the other side"); + + List> result = MapReduce.mapReduce(inputs); + + assertEquals("hello", result.get(0).getKey()); // hello = 3 + assertEquals(3, result.get(0).getValue()); + assertEquals(1, result.get(1).getValue()); + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/MapperTest.java b/map-reduce/src/test/java/com/iluwatar/MapperTest.java new file mode 100644 index 000000000000..7ebd48407042 --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/MapperTest.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; +import org.junit.jupiter.api.Test; + +class MapperTest { + + @Test + void testMapSingleSentence() { + String input = "Hello world hello"; + Map result = Mapper.map(input); + + assertEquals(2, result.get("hello")); + assertEquals(1, result.get("world")); + } + + @Test + void testMapCaseInsensitivity() { + String input = "HeLLo WoRLd hello WORLD"; + Map result = Mapper.map(input); + + assertEquals(2, result.get("hello")); + assertEquals(2, result.get("world")); + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/ReducerTest.java b/map-reduce/src/test/java/com/iluwatar/ReducerTest.java new file mode 100644 index 000000000000..903b3828c8d7 --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/ReducerTest.java @@ -0,0 +1,74 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import org.junit.jupiter.api.Test; + +class ReducerTest { + + @Test + void testReduceWithMultipleWords() { + Map> input = new HashMap<>(); + input.put("apple", Arrays.asList(2, 3, 1)); + input.put("banana", Arrays.asList(1, 1)); + input.put("cherry", List.of(4)); + + List> result = Reducer.reduce(input); + + assertEquals(3, result.size()); + assertEquals("apple", result.get(0).getKey()); + assertEquals(6, result.get(0).getValue()); + assertEquals("cherry", result.get(1).getKey()); + assertEquals(4, result.get(1).getValue()); + assertEquals("banana", result.get(2).getKey()); + assertEquals(2, result.get(2).getValue()); + } + + @Test + void testReduceWithEmptyInput() { + Map> input = new HashMap<>(); + + List> result = Reducer.reduce(input); + + assertTrue(result.isEmpty()); + } + + @Test + void testReduceWithTiedCounts() { + Map> input = new HashMap<>(); + input.put("tie1", Arrays.asList(2, 2)); + input.put("tie2", Arrays.asList(1, 3)); + + List> result = Reducer.reduce(input); + + assertEquals(2, result.size()); + assertEquals(4, result.get(0).getValue()); + assertEquals(4, result.get(1).getValue()); + // Note: The order of tie1 and tie2 is not guaranteed in case of a tie + } +} diff --git a/map-reduce/src/test/java/com/iluwatar/ShufflerTest.java b/map-reduce/src/test/java/com/iluwatar/ShufflerTest.java new file mode 100644 index 000000000000..3362f7ea20a5 --- /dev/null +++ b/map-reduce/src/test/java/com/iluwatar/ShufflerTest.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import org.junit.jupiter.api.Test; + +class ShufflerTest { + + @Test + void testShuffleAndSort() { + List> mappedData = + Arrays.asList(Map.of("hello", 1, "world", 2), Map.of("hello", 2, "java", 1)); + + Map> grouped = Shuffler.shuffleAndSort(mappedData); + + assertEquals(Arrays.asList(1, 2), grouped.get("hello")); + assertEquals(List.of(2), grouped.get("world")); + assertEquals(List.of(1), grouped.get("java")); + } +} diff --git a/marker-interface/README.md b/marker-interface/README.md index 1d242f5bc190..c272093e0d9a 100644 --- a/marker-interface/README.md +++ b/marker-interface/README.md @@ -37,6 +37,10 @@ Wikipedia says > > To use this pattern, a class implements a marker interface (also called tagging interface) which is an empty interface, and methods that interact with instances of that class test for the existence of the interface. Whereas a typical interface specifies functionality (in the form of method declarations) that an implementing class must support, a marker interface need not do so. The mere presence of such an interface indicates specific behavior on the part of the implementing class. Hybrid interfaces, which both act as markers and specify required methods, are possible but may prove confusing if improperly used. +Flowchart + +![Marker Interface flowchart](./etc/marker-interface-flowchart.png) + ## Programmatic Example of Marker Interface Pattern in Java The Marker Interface design pattern is a design pattern in computer science that is used with languages that provide run-time type information about objects. It provides a means to associate metadata with a class where the language does not have explicit support for such metadata. diff --git a/marker-interface/etc/marker-interface-flowchart.png b/marker-interface/etc/marker-interface-flowchart.png new file mode 100644 index 000000000000..289a2cdb6233 Binary files /dev/null and b/marker-interface/etc/marker-interface-flowchart.png differ diff --git a/marker-interface/etc/marker-interface.urm.puml b/marker-interface/etc/marker-interface.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/marker-interface/etc/marker-interface.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/marker-interface/pom.xml b/marker-interface/pom.xml index 43ee4bf78fd7..f564d6756133 100644 --- a/marker-interface/pom.xml +++ b/marker-interface/pom.xml @@ -34,6 +34,14 @@ 4.0.0 marker-interface + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -42,6 +50,7 @@ org.hamcrest hamcrest-core + 3.0 test diff --git a/marker-interface/src/main/java/App.java b/marker-interface/src/main/java/App.java index 1d4a0dc97e3c..c1f36664b495 100644 --- a/marker-interface/src/main/java/App.java +++ b/marker-interface/src/main/java/App.java @@ -66,4 +66,3 @@ public static void main(String[] args) { } } } - diff --git a/marker-interface/src/main/java/Guard.java b/marker-interface/src/main/java/Guard.java index 8852e830293f..469ee2e7734f 100644 --- a/marker-interface/src/main/java/Guard.java +++ b/marker-interface/src/main/java/Guard.java @@ -24,9 +24,7 @@ */ import lombok.extern.slf4j.Slf4j; -/** - * Class defining Guard. - */ +/** Class defining Guard. */ @Slf4j public class Guard implements Permission { diff --git a/marker-interface/src/main/java/Permission.java b/marker-interface/src/main/java/Permission.java index 21f751f72814..bdaf18375e91 100644 --- a/marker-interface/src/main/java/Permission.java +++ b/marker-interface/src/main/java/Permission.java @@ -22,8 +22,5 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -/** - * Interface without any methods Marker interface is based on that assumption. - */ -public interface Permission { -} +/** Interface without any methods Marker interface is based on that assumption. */ +public interface Permission {} diff --git a/marker-interface/src/main/java/Thief.java b/marker-interface/src/main/java/Thief.java index d412d3fa0c4a..ae6bec874671 100644 --- a/marker-interface/src/main/java/Thief.java +++ b/marker-interface/src/main/java/Thief.java @@ -24,9 +24,7 @@ */ import lombok.extern.slf4j.Slf4j; -/** - * Class defining Thief. - */ +/** Class defining Thief. */ @Slf4j public class Thief { diff --git a/marker-interface/src/test/java/AppTest.java b/marker-interface/src/test/java/AppTest.java index 1d70483204b9..4600eed4a063 100644 --- a/marker-interface/src/test/java/AppTest.java +++ b/marker-interface/src/test/java/AppTest.java @@ -22,17 +22,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/marker-interface/src/test/java/GuardTest.java b/marker-interface/src/test/java/GuardTest.java index 9470689bc1ee..7ae34f63d026 100644 --- a/marker-interface/src/test/java/GuardTest.java +++ b/marker-interface/src/test/java/GuardTest.java @@ -27,9 +27,7 @@ import org.junit.jupiter.api.Test; -/** - * Guard test - */ +/** Guard test */ class GuardTest { @Test @@ -37,4 +35,4 @@ void testGuard() { var guard = new Guard(); assertThat(guard, instanceOf(Permission.class)); } -} \ No newline at end of file +} diff --git a/marker-interface/src/test/java/ThiefTest.java b/marker-interface/src/test/java/ThiefTest.java index 91f31293007f..130dd12fcb25 100644 --- a/marker-interface/src/test/java/ThiefTest.java +++ b/marker-interface/src/test/java/ThiefTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Thief test - */ +/** Thief test */ class ThiefTest { @Test void testThief() { var thief = new Thief(); assertThat(thief, not(instanceOf(Permission.class))); } -} \ No newline at end of file +} diff --git a/master-worker/README.md b/master-worker/README.md index 1b8f7cfef3b3..70885952240d 100644 --- a/master-worker/README.md +++ b/master-worker/README.md @@ -33,6 +33,10 @@ Wikipedia says > Master–slave is a model of asymmetric communication or control where one device or process (the master) controls one or more other devices or processes (the slaves) and serves as their communication hub. In some systems, a master is selected from a group of eligible devices, with the other devices acting in the role of slaves. +Sequence diagram + +![Master-Worker sequence diagram](./etc/master-worker-sequence-diagram.png) + ## Programmatic Example of Master-Worker Pattern in Java In the provided code, the `MasterWorker` class initiates the concurrent computation process. The `Master` class divides the work among `Worker` objects, each performing its task in parallel, thus optimizing task processing and enhancing system efficiency. diff --git a/master-worker/etc/master-worker-sequence-diagram.png b/master-worker/etc/master-worker-sequence-diagram.png new file mode 100644 index 000000000000..e2e28a262646 Binary files /dev/null and b/master-worker/etc/master-worker-sequence-diagram.png differ diff --git a/master-worker/pom.xml b/master-worker/pom.xml index 2661fea405ce..630fbb6f5b84 100644 --- a/master-worker/pom.xml +++ b/master-worker/pom.xml @@ -34,6 +34,14 @@ master-worker + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/App.java b/master-worker/src/main/java/com/iluwatar/masterworker/App.java index 483729f725c7..fbe31eca8b76 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/App.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/App.java @@ -33,13 +33,14 @@ import lombok.extern.slf4j.Slf4j; /** - *

    The Master-Worker pattern is used when the problem at hand can be solved by + * The Master-Worker pattern is used when the problem at hand can be solved by * dividing into multiple parts which need to go through the same computation and may need to be * aggregated to get final result. Parallel processing is performed using a system consisting of a * master and some number of workers, where a master divides the work among the workers, gets the * result back from them and assimilates all the results to give final result. The only * communication is between the master and the worker - none of the workers communicate among one - * another and the user only communicates with the master to get required job done.

    + * another and the user only communicates with the master to get required job done. + * *

    In our example, we have generic abstract classes {@link MasterWorker}, {@link Master} and * {@link Worker} which have to be extended by the classes which will perform the specific job at * hand (in this case finding transpose of matrix, done by {@link ArrayTransposeMasterWorker}, @@ -52,9 +53,8 @@ * result. We also have 2 abstract classes {@link Input} and {@link Result}, which contain the input * data and result data respectively. The Input class also has an abstract method divideData which * defines how the data is to be divided into segments. These classes are extended by {@link - * ArrayInput} and {@link ArrayResult}.

    + * ArrayInput} and {@link ArrayResult}. */ - @Slf4j public class App { @@ -63,7 +63,6 @@ public class App { * * @param args command line args */ - public static void main(String[] args) { var mw = new ArrayTransposeMasterWorker(); var rows = 10; @@ -78,5 +77,4 @@ public static void main(String[] args) { LOGGER.info("Please enter non-zero input"); } } - } diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/ArrayInput.java b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayInput.java index 84e7aef20fc3..3d492aa08089 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/ArrayInput.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayInput.java @@ -28,10 +28,7 @@ import java.util.Arrays; import java.util.List; -/** - * Class ArrayInput extends abstract class {@link Input} and contains data of type int[][]. - */ - +/** Class ArrayInput extends abstract class {@link Input} and contains data of type int[][]. */ public class ArrayInput extends Input { public ArrayInput(int[][] data) { @@ -39,13 +36,13 @@ public ArrayInput(int[][] data) { } static int[] makeDivisions(int[][] data, int num) { - var initialDivision = data.length / num; //equally dividing + var initialDivision = data.length / num; // equally dividing var divisions = new int[num]; Arrays.fill(divisions, initialDivision); if (initialDivision * num != data.length) { var extra = data.length - initialDivision * num; var l = 0; - //equally dividing extra among all parts + // equally dividing extra among all parts while (extra > 0) { divisions[l] = divisions[l] + 1; extra--; @@ -66,7 +63,7 @@ public List> divideData(int num) { } else { var divisions = makeDivisions(this.data, num); var result = new ArrayList>(num); - var rowsDone = 0; //number of rows divided so far + var rowsDone = 0; // number of rows divided so far for (var i = 0; i < num; i++) { var rows = divisions[i]; if (rows != 0) { @@ -76,7 +73,7 @@ public List> divideData(int num) { var dividedInput = new ArrayInput(divided); result.add(dividedInput); } else { - break; //rest of divisions will also be 0 + break; // rest of divisions will also be 0 } } return result; diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/ArrayResult.java b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayResult.java index a81fd6c3d234..fa99b5ce6c1f 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/ArrayResult.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayResult.java @@ -24,10 +24,7 @@ */ package com.iluwatar.masterworker; -/** - * Class ArrayResult extends abstract class {@link Result} and contains data of type int[][]. - */ - +/** Class ArrayResult extends abstract class {@link Result} and contains data of type int[][]. */ public class ArrayResult extends Result { public ArrayResult(int[][] data) { diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java index 865c6f229010..8d3e9e7904ad 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/ArrayUtilityMethods.java @@ -27,10 +27,7 @@ import java.security.SecureRandom; import lombok.extern.slf4j.Slf4j; -/** - * Class ArrayUtilityMethods has some utility methods for matrices and arrays. - */ - +/** Class ArrayUtilityMethods has some utility methods for matrices and arrays. */ @Slf4j public class ArrayUtilityMethods { @@ -40,9 +37,8 @@ public class ArrayUtilityMethods { * Method arraysSame compares 2 arrays @param a1 and @param a2 and @return whether their values * are equal (boolean). */ - public static boolean arraysSame(int[] a1, int[] a2) { - //compares if 2 arrays have the same value + // compares if 2 arrays have the same value if (a1.length != a2.length) { return false; } else { @@ -63,7 +59,6 @@ public static boolean arraysSame(int[] a1, int[] a2) { * Method matricesSame compares 2 matrices @param m1 and @param m2 and @return whether their * values are equal (boolean). */ - public static boolean matricesSame(int[][] m1, int[][] m2) { if (m1.length != m2.length) { return false; @@ -90,19 +85,16 @@ public static int[][] createRandomIntMatrix(int rows, int columns) { var matrix = new int[rows][columns]; for (var i = 0; i < rows; i++) { for (var j = 0; j < columns; j++) { - //filling cells in matrix + // filling cells in matrix matrix[i][j] = RANDOM.nextInt(10); } } return matrix; } - /** - * Method printMatrix prints input matrix @param matrix. - */ - + /** Method printMatrix prints input matrix @param matrix. */ public static void printMatrix(int[][] matrix) { - //prints out int[][] + // prints out int[][] for (var ints : matrix) { for (var j = 0; j < matrix[0].length; j++) { LOGGER.info(ints[j] + " "); @@ -110,5 +102,4 @@ public static void printMatrix(int[][] matrix) { LOGGER.info(""); } } - } diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/Input.java b/master-worker/src/main/java/com/iluwatar/masterworker/Input.java index 6dd46865b1b0..4e7c73d8633e 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/Input.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/Input.java @@ -32,7 +32,6 @@ * * @param T will be type of data. */ - public abstract class Input { public final T data; diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/Result.java b/master-worker/src/main/java/com/iluwatar/masterworker/Result.java index 61450d65694d..cc0bd4682101 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/Result.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/Result.java @@ -29,7 +29,6 @@ * * @param T will be type of data. */ - public abstract class Result { public final T data; diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java index dcbd47a3f842..0ebef7dd82ff 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorker.java @@ -31,7 +31,6 @@ * Class ArrayTransposeMasterWorker extends abstract class {@link MasterWorker} and specifically * solves the problem of finding transpose of input array. */ - public class ArrayTransposeMasterWorker extends MasterWorker { public ArrayTransposeMasterWorker() { diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java index 8027161b813c..5f785ebff144 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/MasterWorker.java @@ -28,10 +28,7 @@ import com.iluwatar.masterworker.Result; import com.iluwatar.masterworker.system.systemmaster.Master; -/** - * The abstract MasterWorker class which contains reference to master. - */ - +/** The abstract MasterWorker class which contains reference to master. */ public abstract class MasterWorker { private final Master master; @@ -46,4 +43,3 @@ public Result getResult(Input input) { return this.master.getFinalResult(); } } - diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java index da3e6c5d2e34..50ee1c2b16fe 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/ArrayTransposeMaster.java @@ -35,7 +35,6 @@ * Class ArrayTransposeMaster extends abstract class {@link Master} and contains definition of * aggregateData, which will obtain final result from all data obtained and for setWorkers. */ - public class ArrayTransposeMaster extends Master { public ArrayTransposeMaster(int numOfWorkers) { super(numOfWorkers); @@ -43,7 +42,7 @@ public ArrayTransposeMaster(int numOfWorkers) { @Override ArrayList setWorkers(int num) { - //i+1 will be id + // i+1 will be id return IntStream.range(0, num) .mapToObj(i -> new ArrayTransposeWorker(this, i + 1)) .collect(Collectors.toCollection(() -> new ArrayList<>(num))); @@ -60,20 +59,19 @@ ArrayResult aggregateData() { columns += ((ArrayResult) elements.nextElement()).data[0].length; } var resultData = new int[rows][columns]; - var columnsDone = 0; //columns aggregated so far + var columnsDone = 0; // columns aggregated so far var workers = this.getWorkers(); for (var i = 0; i < this.getExpectedNumResults(); i++) { - //result obtained from ith worker + // result obtained from ith worker var worker = workers.get(i); var workerId = worker.getWorkerId(); var work = ((ArrayResult) allResultData.get(workerId)).data; for (var m = 0; m < work.length; m++) { - //m = row number, n = columns number + // m = row number, n = columns number System.arraycopy(work[m], 0, resultData[m], columnsDone, work[0].length); } columnsDone += work[0].length; } return new ArrayResult(resultData); } - } diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java index 800b63f71630..791de82c9266 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemmaster/Master.java @@ -37,14 +37,12 @@ * number of results), allResultData (hashtable of results obtained from workers, mapped by their * ids) and finalResult (aggregated from allResultData). */ - public abstract class Master { private final int numOfWorkers; private final List workers; private final Hashtable> allResultData; private int expectedNumResults; - @Getter - private Result finalResult; + @Getter private Result finalResult; Master(int numOfWorkers) { this.numOfWorkers = numOfWorkers; @@ -77,7 +75,7 @@ private void divideWork(Input input) { if (dividedInput != null) { this.expectedNumResults = dividedInput.size(); for (var i = 0; i < this.expectedNumResults; i++) { - //ith division given to ith worker in this.workers + // ith division given to ith worker in this.workers this.workers.get(i).setReceivedData(this, dividedInput.get(i)); this.workers.get(i).start(); } @@ -99,7 +97,7 @@ public void receiveData(Result data, Worker w) { private void collectResult(Result data, int workerId) { this.allResultData.put(workerId, data); if (this.allResultData.size() == this.expectedNumResults) { - //all data received + // all data received this.finalResult = aggregateData(); } } diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java index 110a15a1ae99..0d83fd3eb1e3 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorker.java @@ -32,7 +32,6 @@ * Class ArrayTransposeWorker extends abstract class {@link Worker} and defines method * executeOperation(), to be performed on data received from master. */ - public class ArrayTransposeWorker extends Worker { public ArrayTransposeWorker(Master master, int id) { @@ -41,14 +40,14 @@ public ArrayTransposeWorker(Master master, int id) { @Override ArrayResult executeOperation() { - //number of rows in result matrix is equal to number of columns in input matrix and vice versa + // number of rows in result matrix is equal to number of columns in input matrix and vice versa var arrayInput = (ArrayInput) this.getReceivedData(); final var rows = arrayInput.data[0].length; final var cols = arrayInput.data.length; var resultData = new int[rows][cols]; for (var i = 0; i < cols; i++) { for (var j = 0; j < rows; j++) { - //flipping element positions along diagonal + // flipping element positions along diagonal resultData[j][i] = arrayInput.data[i][j]; } } diff --git a/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java index a276fc540578..0f154c1953d8 100644 --- a/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java +++ b/master-worker/src/main/java/com/iluwatar/masterworker/system/systemworkers/Worker.java @@ -33,11 +33,9 @@ * The abstract Worker class which extends Thread class to enable parallel processing. Contains * fields master(holding reference to master), workerId (unique id) and receivedData(from master). */ - public abstract class Worker extends Thread { private final Master master; - @Getter - private final int workerId; + @Getter private final int workerId; private Input receivedData; Worker(Master master, int id) { @@ -61,7 +59,7 @@ private void sendToMaster(Result data) { this.master.receiveData(data, this); } - public void run() { //from Thread class + public void run() { // from Thread class var work = executeOperation(); sendToMaster(work); } diff --git a/master-worker/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java b/master-worker/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java index 2f259573e2be..30cee36ce34e 100644 --- a/master-worker/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java +++ b/master-worker/src/test/java/com/iluwatar/masterworker/ArrayInputTest.java @@ -30,10 +30,7 @@ import java.util.Random; import org.junit.jupiter.api.Test; -/** - * Testing divideData method in {@link ArrayInput} class. - */ - +/** Testing divideData method in {@link ArrayInput} class. */ class ArrayInputTest { @Test @@ -49,14 +46,14 @@ void divideDataTest() { } var i = new ArrayInput(inputMatrix); var table = i.divideData(4); - var division1 = new int[][]{inputMatrix[0], inputMatrix[1], inputMatrix[2]}; - var division2 = new int[][]{inputMatrix[3], inputMatrix[4], inputMatrix[5]}; - var division3 = new int[][]{inputMatrix[6], inputMatrix[7]}; - var division4 = new int[][]{inputMatrix[8], inputMatrix[9]}; - assertTrue(matricesSame(table.get(0).data, division1) - && matricesSame(table.get(1).data, division2) - && matricesSame(table.get(2).data, division3) - && matricesSame(table.get(3).data, division4)); + var division1 = new int[][] {inputMatrix[0], inputMatrix[1], inputMatrix[2]}; + var division2 = new int[][] {inputMatrix[3], inputMatrix[4], inputMatrix[5]}; + var division3 = new int[][] {inputMatrix[6], inputMatrix[7]}; + var division4 = new int[][] {inputMatrix[8], inputMatrix[9]}; + assertTrue( + matricesSame(table.get(0).data, division1) + && matricesSame(table.get(1).data, division2) + && matricesSame(table.get(2).data, division3) + && matricesSame(table.get(3).data, division4)); } - } diff --git a/master-worker/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java b/master-worker/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java index 2e2a1bc54e05..2c3cbaa7595b 100644 --- a/master-worker/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java +++ b/master-worker/src/test/java/com/iluwatar/masterworker/ArrayUtilityMethodsTest.java @@ -28,24 +28,20 @@ import org.junit.jupiter.api.Test; -/** - * Testing utility methods in {@link ArrayUtilityMethods} class. - */ - +/** Testing utility methods in {@link ArrayUtilityMethods} class. */ class ArrayUtilityMethodsTest { @Test void arraysSameTest() { - var arr1 = new int[]{1, 4, 2, 6}; - var arr2 = new int[]{1, 4, 2, 6}; + var arr1 = new int[] {1, 4, 2, 6}; + var arr2 = new int[] {1, 4, 2, 6}; assertTrue(ArrayUtilityMethods.arraysSame(arr1, arr2)); } @Test void matricesSameTest() { - var matrix1 = new int[][]{{1, 4, 2, 6}, {5, 8, 6, 7}}; - var matrix2 = new int[][]{{1, 4, 2, 6}, {5, 8, 6, 7}}; + var matrix1 = new int[][] {{1, 4, 2, 6}, {5, 8, 6, 7}}; + var matrix2 = new int[][] {{1, 4, 2, 6}, {5, 8, 6, 7}}; assertTrue(ArrayUtilityMethods.matricesSame(matrix1, matrix2)); } - } diff --git a/master-worker/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java b/master-worker/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java index d8a014b61f51..9d037ada05f3 100644 --- a/master-worker/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java +++ b/master-worker/src/test/java/com/iluwatar/masterworker/system/ArrayTransposeMasterWorkerTest.java @@ -31,29 +31,28 @@ import com.iluwatar.masterworker.ArrayUtilityMethods; import org.junit.jupiter.api.Test; -/** - * Testing getResult method in {@link ArrayTransposeMasterWorker} class. - */ - +/** Testing getResult method in {@link ArrayTransposeMasterWorker} class. */ class ArrayTransposeMasterWorkerTest { @Test void getResultTest() { var atmw = new ArrayTransposeMasterWorker(); - var matrix = new int[][]{ - {1, 2, 3, 4, 5}, - {1, 2, 3, 4, 5}, - {1, 2, 3, 4, 5}, - {1, 2, 3, 4, 5}, - {1, 2, 3, 4, 5} - }; - var matrixTranspose = new int[][]{ - {1, 1, 1, 1, 1}, - {2, 2, 2, 2, 2}, - {3, 3, 3, 3, 3}, - {4, 4, 4, 4, 4}, - {5, 5, 5, 5, 5} - }; + var matrix = + new int[][] { + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5}, + {1, 2, 3, 4, 5} + }; + var matrixTranspose = + new int[][] { + {1, 1, 1, 1, 1}, + {2, 2, 2, 2, 2}, + {3, 3, 3, 3, 3}, + {4, 4, 4, 4, 4}, + {5, 5, 5, 5, 5} + }; var i = new ArrayInput(matrix); var r = (ArrayResult) atmw.getResult(i); assertTrue(ArrayUtilityMethods.matricesSame(r.data, matrixTranspose)); diff --git a/master-worker/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java b/master-worker/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java index a6deb3a83eab..b1fedbd97faf 100644 --- a/master-worker/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java +++ b/master-worker/src/test/java/com/iluwatar/masterworker/system/systemworkers/ArrayTransposeWorkerTest.java @@ -31,22 +31,18 @@ import com.iluwatar.masterworker.system.systemmaster.ArrayTransposeMaster; import org.junit.jupiter.api.Test; -/** - * Testing executeOperation method in {@link ArrayTransposeWorker} class. - */ - +/** Testing executeOperation method in {@link ArrayTransposeWorker} class. */ class ArrayTransposeWorkerTest { @Test void executeOperationTest() { var atm = new ArrayTransposeMaster(1); var atw = new ArrayTransposeWorker(atm, 1); - var matrix = new int[][]{{2, 4}, {3, 5}}; - var matrixTranspose = new int[][]{{2, 3}, {4, 5}}; + var matrix = new int[][] {{2, 4}, {3, 5}}; + var matrixTranspose = new int[][] {{2, 3}, {4, 5}}; var i = new ArrayInput(matrix); atw.setReceivedData(atm, i); var r = atw.executeOperation(); assertTrue(ArrayUtilityMethods.matricesSame(r.data, matrixTranspose)); } - } diff --git a/mediator/README.md b/mediator/README.md index 628d976e072e..bcf64f022b87 100644 --- a/mediator/README.md +++ b/mediator/README.md @@ -6,7 +6,7 @@ category: Behavioral language: en tag: - Decoupling - - Gang Of Four + - Gang of Four - Messaging - Object composition --- @@ -33,6 +33,10 @@ Wikipedia says > In software engineering, the mediator pattern defines an object that encapsulates how a set of objects interact. This pattern is considered to be a behavioral pattern due to the way it can alter the program's running behavior. In object-oriented programming, programs often consist of many classes. Business logic and computation are distributed among these classes. However, as more classes are added to a program, especially during maintenance and/or refactoring, the problem of communication between these classes may become more complex. This makes the program harder to read and maintain. Furthermore, it can become difficult to change the program, since any change may affect code in several other classes. With the mediator pattern, communication between objects is encapsulated within a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby reducing coupling. +Sequence diagram + +![Mediator sequence diagram](./etc/mediator-sequence-diagram.png) + ## Programmatic Example of Mediator Pattern in Java In this example, the mediator encapsulates how a set of objects interact. Instead of referring to each other directly, they use the mediator interface. @@ -175,10 +179,6 @@ Here's the console output from running the example. 14:05:15.083 [main] INFO com.iluwatar.mediator.PartyMemberBase -- Rogue arrives for dinner ``` -## Detailed Explanation of Mediator Pattern with Real-World Examples - -![Mediator](./etc/mediator_1.png "Mediator") - ## When to Use the Mediator Pattern in Java Use the Mediator pattern when diff --git a/mediator/etc/mediator-sequence-diagram.png b/mediator/etc/mediator-sequence-diagram.png new file mode 100644 index 000000000000..e79a4aaec9ef Binary files /dev/null and b/mediator/etc/mediator-sequence-diagram.png differ diff --git a/mediator/pom.xml b/mediator/pom.xml index 6fa9bbaf405b..737f818838b2 100644 --- a/mediator/pom.xml +++ b/mediator/pom.xml @@ -34,6 +34,14 @@ mediator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/mediator/src/main/java/com/iluwatar/mediator/Action.java b/mediator/src/main/java/com/iluwatar/mediator/Action.java index 61f70f0e66ed..d61873e69d70 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Action.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Action.java @@ -26,9 +26,7 @@ import lombok.Getter; -/** - * Action enumeration. - */ +/** Action enumeration. */ public enum Action { HUNT("hunted a rabbit", "arrives for dinner"), TALE("tells a tale", "comes to listen"), @@ -37,8 +35,7 @@ public enum Action { NONE("", ""); private final String title; - @Getter - private final String description; + @Getter private final String description; Action(String title, String description) { this.title = title; diff --git a/mediator/src/main/java/com/iluwatar/mediator/App.java b/mediator/src/main/java/com/iluwatar/mediator/App.java index c5e4c64cdd57..aaab69f4d23b 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/App.java +++ b/mediator/src/main/java/com/iluwatar/mediator/App.java @@ -41,9 +41,8 @@ * the mediator. This reduces the dependencies between communicating objects, thereby lowering the * coupling. * - *

    In this example the mediator encapsulates how a set of objects ({@link PartyMember}) - * interact. Instead of referring to each other directly they use the mediator ({@link Party}) - * interface. + *

    In this example the mediator encapsulates how a set of objects ({@link PartyMember}) interact. + * Instead of referring to each other directly they use the mediator ({@link Party}) interface. */ public class App { diff --git a/mediator/src/main/java/com/iluwatar/mediator/Hobbit.java b/mediator/src/main/java/com/iluwatar/mediator/Hobbit.java index effe0dd15a3e..c0b5fc967bcc 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Hobbit.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Hobbit.java @@ -1,37 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Hobbit party member. - */ -public class Hobbit extends PartyMemberBase { - - @Override - public String toString() { - return "Hobbit"; - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Hobbit party member. */ +public class Hobbit extends PartyMemberBase { + + @Override + public String toString() { + return "Hobbit"; + } +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/Hunter.java b/mediator/src/main/java/com/iluwatar/mediator/Hunter.java index 783cd1aa9435..ddf0c2810440 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Hunter.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Hunter.java @@ -1,36 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Hunter party member. - */ -public class Hunter extends PartyMemberBase { - - @Override - public String toString() { - return "Hunter"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Hunter party member. */ +public class Hunter extends PartyMemberBase { + + @Override + public String toString() { + return "Hunter"; + } +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/Party.java b/mediator/src/main/java/com/iluwatar/mediator/Party.java index 387f4809f2cf..b7c87f162a68 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Party.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Party.java @@ -1,36 +1,33 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Party interface. - */ -public interface Party { - - void addMember(PartyMember member); - - void act(PartyMember actor, Action action); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Party interface. */ +public interface Party { + + void addMember(PartyMember member); + + void act(PartyMember actor, Action action); +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java index c61e39faf123..92e37506f9d9 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java +++ b/mediator/src/main/java/com/iluwatar/mediator/PartyImpl.java @@ -1,55 +1,53 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -import java.util.ArrayList; -import java.util.List; - -/** - * Party implementation. - */ -public class PartyImpl implements Party { - - private final List members; - - public PartyImpl() { - members = new ArrayList<>(); - } - - @Override - public void act(PartyMember actor, Action action) { - for (var member : members) { - if (!member.equals(actor)) { - member.partyAction(action); - } - } - } - - @Override - public void addMember(PartyMember member) { - members.add(member); - member.joinedParty(this); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +import java.util.ArrayList; +import java.util.List; + +/** Party implementation. */ +public class PartyImpl implements Party { + + private final List members; + + public PartyImpl() { + members = new ArrayList<>(); + } + + @Override + public void act(PartyMember actor, Action action) { + for (var member : members) { + if (!member.equals(actor)) { + member.partyAction(action); + } + } + } + + @Override + public void addMember(PartyMember member) { + members.add(member); + member.joinedParty(this); + } +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/PartyMember.java b/mediator/src/main/java/com/iluwatar/mediator/PartyMember.java index dc1aec4e4b62..4fad4e85b646 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/PartyMember.java +++ b/mediator/src/main/java/com/iluwatar/mediator/PartyMember.java @@ -24,9 +24,7 @@ */ package com.iluwatar.mediator; -/** - * Interface for party members interacting with {@link Party}. - */ +/** Interface for party members interacting with {@link Party}. */ public interface PartyMember { void joinedParty(Party party); diff --git a/mediator/src/main/java/com/iluwatar/mediator/PartyMemberBase.java b/mediator/src/main/java/com/iluwatar/mediator/PartyMemberBase.java index 21909cd3c8d1..8a67b8f59f7c 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/PartyMemberBase.java +++ b/mediator/src/main/java/com/iluwatar/mediator/PartyMemberBase.java @@ -1,59 +1,56 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -import lombok.extern.slf4j.Slf4j; - -/** - * Abstract base class for party members. - */ -@Slf4j -public abstract class PartyMemberBase implements PartyMember { - - protected Party party; - - @Override - public void joinedParty(Party party) { - LOGGER.info("{} joins the party", this); - this.party = party; - } - - @Override - public void partyAction(Action action) { - LOGGER.info("{} {}", this, action.getDescription()); - } - - @Override - public void act(Action action) { - if (party != null) { - LOGGER.info("{} {}", this, action); - party.act(this, action); - } - } - - @Override - public abstract String toString(); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +import lombok.extern.slf4j.Slf4j; + +/** Abstract base class for party members. */ +@Slf4j +public abstract class PartyMemberBase implements PartyMember { + + protected Party party; + + @Override + public void joinedParty(Party party) { + LOGGER.info("{} joins the party", this); + this.party = party; + } + + @Override + public void partyAction(Action action) { + LOGGER.info("{} {}", this, action.getDescription()); + } + + @Override + public void act(Action action) { + if (party != null) { + LOGGER.info("{} {}", this, action); + party.act(this, action); + } + } + + @Override + public abstract String toString(); +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/Rogue.java b/mediator/src/main/java/com/iluwatar/mediator/Rogue.java index 2724b46e8425..5edeff28fbf8 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Rogue.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Rogue.java @@ -1,37 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Rogue party member. - */ -public class Rogue extends PartyMemberBase { - - @Override - public String toString() { - return "Rogue"; - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Rogue party member. */ +public class Rogue extends PartyMemberBase { + + @Override + public String toString() { + return "Rogue"; + } +} diff --git a/mediator/src/main/java/com/iluwatar/mediator/Wizard.java b/mediator/src/main/java/com/iluwatar/mediator/Wizard.java index d43b95e390e1..126766ea9a95 100644 --- a/mediator/src/main/java/com/iluwatar/mediator/Wizard.java +++ b/mediator/src/main/java/com/iluwatar/mediator/Wizard.java @@ -1,37 +1,34 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.mediator; - -/** - * Wizard party member. - */ -public class Wizard extends PartyMemberBase { - - @Override - public String toString() { - return "Wizard"; - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.mediator; + +/** Wizard party member. */ +public class Wizard extends PartyMemberBase { + + @Override + public String toString() { + return "Wizard"; + } +} diff --git a/mediator/src/test/java/com/iluwatar/mediator/AppTest.java b/mediator/src/test/java/com/iluwatar/mediator/AppTest.java index 47d2727cc6e0..85be64afe7ac 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/AppTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.mediator; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java index ce892303a462..7353bc83b354 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyImplTest.java @@ -30,10 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * PartyImplTest - * - */ +/** PartyImplTest */ class PartyImplTest { /** @@ -58,5 +55,4 @@ void testPartyAction() { verifyNoMoreInteractions(partyMember1, partyMember2); } - } diff --git a/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java b/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java index 144175ca0b5e..46be9593ae79 100644 --- a/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java +++ b/mediator/src/test/java/com/iluwatar/mediator/PartyMemberTest.java @@ -42,10 +42,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.LoggerFactory; -/** - * PartyMemberTest - * - */ +/** PartyMemberTest */ class PartyMemberTest { static Stream dataProvider() { @@ -53,8 +50,7 @@ static Stream dataProvider() { Arguments.of((Supplier) Hobbit::new), Arguments.of((Supplier) Hunter::new), Arguments.of((Supplier) Rogue::new), - Arguments.of((Supplier) Wizard::new) - ); + Arguments.of((Supplier) Wizard::new)); } private InMemoryAppender appender; @@ -69,9 +65,7 @@ void tearDown() { appender.stop(); } - /** - * Verify if a party action triggers the correct output to the std-Out - */ + /** Verify if a party action triggers the correct output to the std-Out */ @ParameterizedTest @MethodSource("dataProvider") void testPartyAction(Supplier memberSupplier) { @@ -85,9 +79,7 @@ void testPartyAction(Supplier memberSupplier) { assertEquals(Action.values().length, appender.getLogSize()); } - /** - * Verify if a member action triggers the expected interactions with the party class - */ + /** Verify if a member action triggers the expected interactions with the party class */ @ParameterizedTest @MethodSource("dataProvider") void testAct(Supplier memberSupplier) { @@ -109,9 +101,7 @@ void testAct(Supplier memberSupplier) { assertEquals(Action.values().length + 1, appender.getLogSize()); } - /** - * Verify if {@link PartyMemberBase#toString()} generate the expected output - */ + /** Verify if {@link PartyMemberBase#toString()} generate the expected output */ @ParameterizedTest @MethodSource("dataProvider") void testToString(Supplier memberSupplier) { @@ -141,6 +131,4 @@ public String getLastMessage() { return log.get(log.size() - 1).getFormattedMessage(); } } - - } diff --git a/memento/README.md b/memento/README.md index c1c5372b295d..520c4b4ecc53 100644 --- a/memento/README.md +++ b/memento/README.md @@ -36,6 +36,10 @@ Wikipedia says > The memento pattern is a software design pattern that provides the ability to restore an object to its previous state (undo via rollback). +Sequence diagram + +![Memento sequence diagram](./etc/memento-sequence-diagram.png) + ## Programmatic Example of Memento Pattern in Java In our astrology application, we use the Memento pattern to capture and restore the state of star objects. Each state is saved as a memento, allowing us to revert to previous states as needed. diff --git a/memento/etc/memento-sequence-diagram.png b/memento/etc/memento-sequence-diagram.png new file mode 100644 index 000000000000..8d70255a129b Binary files /dev/null and b/memento/etc/memento-sequence-diagram.png differ diff --git a/memento/pom.xml b/memento/pom.xml index 6f4314d54adf..6ec5ca5d010c 100644 --- a/memento/pom.xml +++ b/memento/pom.xml @@ -34,6 +34,14 @@ memento + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/memento/src/main/java/com/iluwatar/memento/App.java b/memento/src/main/java/com/iluwatar/memento/App.java index effc83d3fb81..d47bc20db84a 100644 --- a/memento/src/main/java/com/iluwatar/memento/App.java +++ b/memento/src/main/java/com/iluwatar/memento/App.java @@ -47,9 +47,7 @@ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var states = new Stack(); diff --git a/memento/src/main/java/com/iluwatar/memento/Star.java b/memento/src/main/java/com/iluwatar/memento/Star.java index deccb85957e6..8f806b1e1fe1 100644 --- a/memento/src/main/java/com/iluwatar/memento/Star.java +++ b/memento/src/main/java/com/iluwatar/memento/Star.java @@ -27,27 +27,21 @@ import lombok.Getter; import lombok.Setter; -/** - * Star uses "mementos" to store and restore state. - */ +/** Star uses "mementos" to store and restore state. */ public class Star { private StarType type; private int ageYears; private int massTons; - /** - * Constructor. - */ + /** Constructor. */ public Star(StarType startType, int startAge, int startMass) { this.type = startType; this.ageYears = startAge; this.massTons = startMass; } - /** - * Makes time pass for the star. - */ + /** Makes time pass for the star. */ public void timePasses() { ageYears *= 2; massTons *= 8; @@ -60,8 +54,7 @@ public void timePasses() { ageYears *= 2; massTons = 0; } - default -> { - } + default -> {} } } @@ -85,9 +78,7 @@ public String toString() { return String.format("%s age: %d years mass: %d tons", type.toString(), ageYears, massTons); } - /** - * StarMemento implementation. - */ + /** StarMemento implementation. */ @Getter @Setter private static class StarMementoInternal implements StarMemento { diff --git a/memento/src/main/java/com/iluwatar/memento/StarMemento.java b/memento/src/main/java/com/iluwatar/memento/StarMemento.java index 3a355a1ea2c6..b0214ad1acc2 100644 --- a/memento/src/main/java/com/iluwatar/memento/StarMemento.java +++ b/memento/src/main/java/com/iluwatar/memento/StarMemento.java @@ -1,32 +1,28 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.memento; - -/** - * External interface to memento. - */ -public interface StarMemento { - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.memento; + +/** External interface to memento. */ +public interface StarMemento {} diff --git a/memento/src/main/java/com/iluwatar/memento/StarType.java b/memento/src/main/java/com/iluwatar/memento/StarType.java index 69bc9b9becaf..41e1659ef0e5 100644 --- a/memento/src/main/java/com/iluwatar/memento/StarType.java +++ b/memento/src/main/java/com/iluwatar/memento/StarType.java @@ -24,9 +24,7 @@ */ package com.iluwatar.memento; -/** - * StarType enumeration. - */ +/** StarType enumeration. */ public enum StarType { SUN("sun"), RED_GIANT("red giant"), diff --git a/memento/src/test/java/com/iluwatar/memento/AppTest.java b/memento/src/test/java/com/iluwatar/memento/AppTest.java index b175f57ef27c..ad7ea0019c68 100644 --- a/memento/src/test/java/com/iluwatar/memento/AppTest.java +++ b/memento/src/test/java/com/iluwatar/memento/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.memento; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/memento/src/test/java/com/iluwatar/memento/StarTest.java b/memento/src/test/java/com/iluwatar/memento/StarTest.java index 9a52e83cbc29..4411efaab3fa 100644 --- a/memento/src/test/java/com/iluwatar/memento/StarTest.java +++ b/memento/src/test/java/com/iluwatar/memento/StarTest.java @@ -28,15 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * StarTest - * - */ +/** StarTest */ class StarTest { - /** - * Verify the stages of a dying sun, without going back in time - */ + /** Verify the stages of a dying sun, without going back in time */ @Test void testTimePasses() { final var star = new Star(StarType.SUN, 1, 2); @@ -61,9 +56,7 @@ void testTimePasses() { assertEquals("dead star age: 256 years mass: 0 tons", star.toString()); } - /** - * Verify some stage of a dying sun, but go back in time to test the memento - */ + /** Verify some stage of a dying sun, but go back in time to test the memento */ @Test void testSetMemento() { final var star = new Star(StarType.SUN, 1, 2); @@ -92,7 +85,5 @@ void testSetMemento() { star.setMemento(firstMemento); assertEquals("sun age: 1 years mass: 2 tons", star.toString()); - } - } diff --git a/metadata-mapping/README.md b/metadata-mapping/README.md index bd2342709a32..3ca71dc9d4d0 100644 --- a/metadata-mapping/README.md +++ b/metadata-mapping/README.md @@ -29,6 +29,10 @@ Wikipedia says > Create a "virtual [object database](https://en.wikipedia.org/wiki/Object_database)" that can be used from within the programming language. +Flowchart + +![Metadata Mapping flowchart](./etc/metadata-mapping-flowchart.png) + ## Programmatic Example of Metadata Mapping Pattern in Java Hibernate ORM Tool uses Metadata Mapping Pattern to specify the mapping between classes and tables either using XML or annotations in code. diff --git a/metadata-mapping/etc/metadata-mapping-flowchart.png b/metadata-mapping/etc/metadata-mapping-flowchart.png new file mode 100644 index 000000000000..88f7d9953cfa Binary files /dev/null and b/metadata-mapping/etc/metadata-mapping-flowchart.png differ diff --git a/metadata-mapping/pom.xml b/metadata-mapping/pom.xml index a501808061f7..7aa802d7ff2a 100644 --- a/metadata-mapping/pom.xml +++ b/metadata-mapping/pom.xml @@ -37,6 +37,14 @@ metadata-mapping + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -45,14 +53,17 @@ org.hibernate hibernate-core + 6.6.11.Final javax.xml.bind jaxb-api + 2.4.0-b180830.0359 org.glassfish.jaxb jaxb-runtime + 4.0.5 com.h2database diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java index 8d4d7ee83a1b..6b5624ac54f9 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/App.java @@ -32,20 +32,17 @@ import org.hibernate.service.ServiceRegistry; /** - * Metadata Mapping specifies the mapping - * between classes and tables so that - * we could treat a table of any database like a Java class. + * Metadata Mapping specifies the mapping between classes and tables so that we could treat a table + * of any database like a Java class. + * + *

    With hibernate, we achieve list/create/update/delete/get operations: 1)Create the H2 Database + * in {@link DatabaseUtil}. 2)Hibernate resolve hibernate.cfg.xml and generate service like + * save/list/get/delete. For learning metadata mapping pattern, we go deeper into Hibernate here: + * a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml b)create session factory to + * generate session interacting with database c)generate session with factory pattern d)create query + * object or use basic api with session, hibernate will convert all query to database query + * according to metadata 3)We encapsulate hibernate service in {@link UserService} for our use. * - *

    With hibernate, we achieve list/create/update/delete/get operations: - * 1)Create the H2 Database in {@link DatabaseUtil}. - * 2)Hibernate resolve hibernate.cfg.xml and generate service like save/list/get/delete. - * For learning metadata mapping pattern, we go deeper into Hibernate here: - * a)read properties from hibernate.cfg.xml and mapping from *.hbm.xml - * b)create session factory to generate session interacting with database - * c)generate session with factory pattern - * d)create query object or use basic api with session, - * hibernate will convert all query to database query according to metadata - * 3)We encapsulate hibernate service in {@link UserService} for our use. * @see org.hibernate.cfg.Configuration#configure(String) * @see org.hibernate.cfg.Configuration#buildSessionFactory(ServiceRegistry) * @see org.hibernate.internal.SessionFactoryImpl#openSession() @@ -92,4 +89,4 @@ public static List generateSampleUsers() { final var user3 = new User("WangWu", "ww123"); return List.of(user1, user2, user3); } -} \ No newline at end of file +} diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java index 73a7009f6303..7d2275234ec1 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/model/User.java @@ -28,9 +28,7 @@ import lombok.Setter; import lombok.ToString; -/** - * User Entity. - */ +/** User Entity. */ @Setter @Getter @ToString @@ -43,6 +41,7 @@ public User() {} /** * Get a user. + * * @param username user name * @param password user password */ @@ -50,4 +49,4 @@ public User(String username, String password) { this.username = username; this.password = password; } -} \ No newline at end of file +} diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java index cd731f8d0de8..e1943a6a944b 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/service/UserService.java @@ -32,15 +32,14 @@ import org.hibernate.HibernateException; import org.hibernate.SessionFactory; -/** - * Service layer for user. - */ +/** Service layer for user. */ @Slf4j public class UserService { private static final SessionFactory factory = HibernateUtil.getSessionFactory(); /** * List all users. + * * @return list of users */ public List listUser() { @@ -61,6 +60,7 @@ public List listUser() { /** * Add a user. + * * @param user user entity * @return user id */ @@ -80,6 +80,7 @@ public int createUser(User user) { /** * Update user. + * * @param id user id * @param user new user entity */ @@ -97,6 +98,7 @@ public void updateUser(Integer id, User user) { /** * Delete user. + * * @param id user id */ public void deleteUser(Integer id) { @@ -113,6 +115,7 @@ public void deleteUser(Integer id) { /** * Get user. + * * @param id user id * @return deleted user */ @@ -129,10 +132,8 @@ public User getUser(Integer id) { return user; } - /** - * Close hibernate. - */ + /** Close hibernate. */ public void close() { HibernateUtil.shutdown(); } -} \ No newline at end of file +} diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java index 5f5dddcbc833..9c3c8469df55 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/DatabaseUtil.java @@ -28,13 +28,12 @@ import lombok.extern.slf4j.Slf4j; import org.h2.jdbcx.JdbcDataSource; -/** - * Create h2 database. - */ +/** Create h2 database. */ @Slf4j public class DatabaseUtil { private static final String DB_URL = "jdbc:h2:mem:metamapping"; - private static final String CREATE_SCHEMA_SQL = """ + private static final String CREATE_SCHEMA_SQL = + """ DROP TABLE IF EXISTS `user_account`;CREATE TABLE `user_account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, @@ -42,9 +41,7 @@ public class DatabaseUtil { PRIMARY KEY (`id`) );"""; - /** - * Hide constructor. - */ + /** Hide constructor. */ private DatabaseUtil() {} static { @@ -57,4 +54,4 @@ private DatabaseUtil() {} LOGGER.error("unable to create h2 data source", e); } } -} \ No newline at end of file +} diff --git a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java index 68d8b4485f75..54c0b3c36640 100644 --- a/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java +++ b/metadata-mapping/src/main/java/com/iluwatar/metamapping/utils/HibernateUtil.java @@ -29,22 +29,18 @@ import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; -/** - * Manage hibernate. - */ +/** Manage hibernate. */ @Slf4j public class HibernateUtil { - @Getter - private static final SessionFactory sessionFactory = buildSessionFactory(); + @Getter private static final SessionFactory sessionFactory = buildSessionFactory(); - /** - * Hide constructor. - */ + /** Hide constructor. */ private HibernateUtil() {} /** * Build session factory. + * * @return session factory */ private static SessionFactory buildSessionFactory() { @@ -52,12 +48,9 @@ private static SessionFactory buildSessionFactory() { return new Configuration().configure().buildSessionFactory(); } - /** - * Close session factory. - */ + /** Close session factory. */ public static void shutdown() { // Close caches and connection pools getSessionFactory().close(); } - -} \ No newline at end of file +} diff --git a/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java b/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java index 37ba2e09fb64..2b159b52617a 100644 --- a/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java +++ b/metadata-mapping/src/test/java/com/iluwatar/metamapping/AppTest.java @@ -24,20 +24,18 @@ */ package com.iluwatar.metamapping; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that metadata mapping example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that metadata mapping 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#main(String[])} - * throws an exception. + * 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#main(String[])} throws an exception. */ @Test void shouldExecuteMetaMappingWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/microservices-aggregrator/README.md b/microservices-aggregrator/README.md index 69d991ba4b14..e77d997b9aeb 100644 --- a/microservices-aggregrator/README.md +++ b/microservices-aggregrator/README.md @@ -36,6 +36,10 @@ Stack Overflow says > Microservices Aggregator invokes multiple services to achieve the functionality required by the application. +Architecture diagram + +![Microservices Aggregator Architecture Diagram](./etc/microservices-aggregator-architecture-diagram.png) + ## Programmatic Example of Microservices Aggregator Pattern in Java Our web marketplace utilizes an Aggregator microservice to fetch combined product and inventory information from separate microservices, ensuring efficient data processing and improved system performance. diff --git a/microservices-aggregrator/aggregator-service/pom.xml b/microservices-aggregrator/aggregator-service/pom.xml index e0f7c2a07b56..fe83826910ce 100644 --- a/microservices-aggregrator/aggregator-service/pom.xml +++ b/microservices-aggregrator/aggregator-service/pom.xml @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java index 2e429bf08473..0fd19e7efbd8 100644 --- a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Aggregator.java @@ -37,11 +37,9 @@ @RestController public class Aggregator { - @Resource - private ProductInformationClient informationClient; + @Resource private ProductInformationClient informationClient; - @Resource - private ProductInventoryClient inventoryClient; + @Resource private ProductInventoryClient inventoryClient; /** * Retrieves product data. @@ -55,13 +53,12 @@ public Product getProduct() { var productTitle = informationClient.getProductTitle(); var productInventory = inventoryClient.getProductInventories(); - //Fallback to error message + // Fallback to error message product.setTitle(requireNonNullElse(productTitle, "Error: Fetching Product Title Failed")); - //Fallback to default error inventory + // Fallback to default error inventory product.setProductInventories(requireNonNullElse(productInventory, -1)); return product; } - } diff --git a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java index 95f7c587d92a..26424eb9c98e 100644 --- a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/App.java @@ -27,9 +27,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -/** - * Spring Boot EntryPoint Class. - */ +/** Spring Boot EntryPoint Class. */ @SpringBootApplication public class App { diff --git a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java index 56dc0155fa86..66626d814871 100644 --- a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/Product.java @@ -27,22 +27,14 @@ import lombok.Getter; import lombok.Setter; -/** - * Encapsulates all the data for a Product that clients will request. - */ +/** Encapsulates all the data for a Product that clients will request. */ @Getter @Setter public class Product { - /** - * The title of the product. - */ + /** The title of the product. */ private String title; - - /** - * The inventories of the product. - */ + /** The inventories of the product. */ private int productInventories; - } diff --git a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java index 1260e4e306ff..d183656edc09 100644 --- a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClient.java @@ -24,11 +24,8 @@ */ package com.iluwatar.aggregator.microservices; -/** - * Interface for the Information micro-service. - */ +/** Interface for the Information micro-service. */ public interface ProductInformationClient { String getProductTitle(); - } diff --git a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java index a41627cb342d..2bda000b397f 100644 --- a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInformationClientImpl.java @@ -32,19 +32,18 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -/** - * An adapter to communicate with information micro-service. - */ +/** An adapter to communicate with information micro-service. */ @Slf4j @Component public class ProductInformationClientImpl implements ProductInformationClient { @Override public String getProductTitle() { - var request = HttpRequest.newBuilder() - .GET() - .uri(URI.create("http://localhost:51515/information")) - .build(); + var request = + HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:51515/information")) + .build(); var client = HttpClient.newHttpClient(); try { var httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); diff --git a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java index 4e0ffe2edb84..02ebac8732d6 100644 --- a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClient.java @@ -24,9 +24,7 @@ */ package com.iluwatar.aggregator.microservices; -/** - * Interface to Inventory micro-service. - */ +/** Interface to Inventory micro-service. */ public interface ProductInventoryClient { Integer getProductInventories(); diff --git a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java index 7549693d07c4..d5a918e4629d 100644 --- a/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java +++ b/microservices-aggregrator/aggregator-service/src/main/java/com/iluwatar/aggregator/microservices/ProductInventoryClientImpl.java @@ -32,9 +32,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -/** - * An adapter to communicate with inventory micro-service. - */ +/** An adapter to communicate with inventory micro-service. */ @Slf4j @Component public class ProductInventoryClientImpl implements ProductInventoryClient { @@ -43,10 +41,11 @@ public class ProductInventoryClientImpl implements ProductInventoryClient { public Integer getProductInventories() { var response = ""; - var request = HttpRequest.newBuilder() - .GET() - .uri(URI.create("http://localhost:51516/inventories")) - .build(); + var request = + HttpRequest.newBuilder() + .GET() + .uri(URI.create("http://localhost:51516/inventories")) + .build(); var client = HttpClient.newHttpClient(); try { var httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString()); diff --git a/microservices-aggregrator/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java b/microservices-aggregrator/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java index 2ac246d9fbdb..914e15dad28d 100644 --- a/microservices-aggregrator/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java +++ b/microservices-aggregrator/aggregator-service/src/test/java/com/iluwatar/aggregator/microservices/AggregatorTest.java @@ -24,37 +24,30 @@ */ package com.iluwatar.aggregator.microservices; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -/** - * Test Aggregation of domain objects - */ +/** Test Aggregation of domain objects */ class AggregatorTest { - @InjectMocks - private Aggregator aggregator; + @InjectMocks private Aggregator aggregator; - @Mock - private ProductInformationClient informationClient; + @Mock private ProductInformationClient informationClient; - @Mock - private ProductInventoryClient inventoryClient; + @Mock private ProductInventoryClient inventoryClient; @BeforeEach void setup() { MockitoAnnotations.openMocks(this); } - /** - * Tests getting the data for a desktop client - */ + /** Tests getting the data for a desktop client */ @Test void testGetProduct() { var title = "The Product Title."; @@ -68,5 +61,4 @@ void testGetProduct() { assertEquals(title, testProduct.getTitle()); assertEquals(inventories, testProduct.getProductInventories()); } - } diff --git a/microservices-aggregrator/etc/microservices-aggregator-architecture-diagram.png b/microservices-aggregrator/etc/microservices-aggregator-architecture-diagram.png new file mode 100644 index 000000000000..34d5113e5a3a Binary files /dev/null and b/microservices-aggregrator/etc/microservices-aggregator-architecture-diagram.png differ diff --git a/microservices-aggregrator/etc/microservices-aggregrator.urm.puml b/microservices-aggregrator/etc/microservices-aggregrator.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/microservices-aggregrator/etc/microservices-aggregrator.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/microservices-aggregrator/information-microservice/pom.xml b/microservices-aggregrator/information-microservice/pom.xml index 69e588bd5caa..5b27df62516d 100644 --- a/microservices-aggregrator/information-microservice/pom.xml +++ b/microservices-aggregrator/information-microservice/pom.xml @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java b/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java index 6c4905340211..68ff8856eb9b 100644 --- a/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java +++ b/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationApplication.java @@ -27,9 +27,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -/** - * Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. - */ +/** Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. */ @SpringBootApplication public class InformationApplication { diff --git a/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java b/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java index 88d11e5f4703..cc826e497195 100644 --- a/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java +++ b/microservices-aggregrator/information-microservice/src/main/java/com/iluwatar/information/microservice/InformationController.java @@ -27,9 +27,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -/** - * Controller providing endpoints to retrieve information about products. - */ +/** Controller providing endpoints to retrieve information about products. */ @RestController public class InformationController { diff --git a/microservices-aggregrator/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java b/microservices-aggregrator/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java index d85e1ce0c94a..3f344d9d8d39 100644 --- a/microservices-aggregrator/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java +++ b/microservices-aggregrator/information-microservice/src/test/java/com/iluwatar/information/microservice/InformationControllerTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.information.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Information Rest Controller - */ +import org.junit.jupiter.api.Test; + +/** Test for Information Rest Controller */ class InformationControllerTest { @Test @@ -39,5 +37,4 @@ void shouldGetProductTitle() { var title = infoController.getProductTitle(); assertEquals("The Product Title.", title); } - } diff --git a/microservices-aggregrator/inventory-microservice/pom.xml b/microservices-aggregrator/inventory-microservice/pom.xml index 4d563a2a1870..64203b09edcf 100644 --- a/microservices-aggregrator/inventory-microservice/pom.xml +++ b/microservices-aggregrator/inventory-microservice/pom.xml @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java b/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java index 7d1762655de1..5627c37c9161 100644 --- a/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java +++ b/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryApplication.java @@ -27,14 +27,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -/** - * Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. - */ +/** Inventory Application starts container (Spring Boot) and exposes the Inventory micro-service. */ @SpringBootApplication public class InventoryApplication { public static void main(String[] args) { SpringApplication.run(InventoryApplication.class, args); } - } diff --git a/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java b/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java index 374da55c66fc..bf5abd510bc0 100644 --- a/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java +++ b/microservices-aggregrator/inventory-microservice/src/main/java/com/iluwatar/inventory/microservice/InventoryController.java @@ -27,9 +27,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -/** - * Controller providing endpoints to retrieve product inventories. - */ +/** Controller providing endpoints to retrieve product inventories. */ @RestController public class InventoryController { @@ -42,5 +40,4 @@ public class InventoryController { public int getProductInventories() { return 5; } - } diff --git a/microservices-aggregrator/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java b/microservices-aggregrator/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java index 1bb692042dbf..27b41594d30b 100644 --- a/microservices-aggregrator/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java +++ b/microservices-aggregrator/inventory-microservice/src/test/java/com/iluwatar/inventory/microservice/InventoryControllerTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.inventory.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test Inventory Rest Controller - */ +import org.junit.jupiter.api.Test; + +/** Test Inventory Rest Controller */ class InventoryControllerTest { @Test diff --git a/microservices-aggregrator/pom.xml b/microservices-aggregrator/pom.xml index 1463af072210..bf1edb1c8b76 100644 --- a/microservices-aggregrator/pom.xml +++ b/microservices-aggregrator/pom.xml @@ -34,17 +34,6 @@ 4.0.0 microservices-aggregrator pom - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.2.4 - import - - - information-microservice aggregator-service diff --git a/microservices-api-gateway/README.md b/microservices-api-gateway/README.md index 72b9b521a31d..ff5d4099376f 100644 --- a/microservices-api-gateway/README.md +++ b/microservices-api-gateway/README.md @@ -1,6 +1,6 @@ --- title: "Microservices API Gateway Pattern in Java: Simplifying Service Access with a Unified Endpoint" -shortTitle: Microservice API Gateway +shortTitle: Microservices API Gateway description: "Learn how the API Gateway pattern simplifies client-side development, enhances security, and optimizes communication in microservices architecture. Explore examples, benefits, and best practices." category: Integration language: en @@ -38,6 +38,10 @@ Wikipedia says > API Gateway is a server that acts as an API front-end, receives API requests, enforces throttling and security policies, passes requests to the back-end service and then passes the response back to the requester. A gateway often includes a transformation engine to orchestrate and modify the requests and responses on the fly. A gateway can also provide functionality such as collecting analytics data and providing caching. The gateway can provide functionality to support authentication, authorization, security, audit and regulatory compliance. +Sequence diagram + +![Microservices API Gateway sequence diagram](./etc/microservices-api-gateway-sequence-diagram.png) + ## Programmatic Example of Microservice API Gateway in Java This implementation shows what the API Gateway pattern could look like for an e-commerce site. The`ApiGateway` makes calls to the Image and Price microservices using the `ImageClientImpl` and `PriceClientImpl` respectively. Customers viewing the site on a desktop device can see both price information and an image of a product, so the `ApiGateway` calls both of the microservices and aggregates the data in the `DesktopProduct` model. However, mobile users only see price information; they do not see a product image. For mobile users, the `ApiGateway` only retrieves price information, which it uses to populate the `MobileProduct`. diff --git a/microservices-api-gateway/api-gateway-service/pom.xml b/microservices-api-gateway/api-gateway-service/pom.xml index f1133ec3c020..b84bebe6f237 100644 --- a/microservices-api-gateway/api-gateway-service/pom.xml +++ b/microservices-api-gateway/api-gateway-service/pom.xml @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java index b7ab7b404963..c4110509747c 100644 --- a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ApiGateway.java @@ -34,11 +34,9 @@ @RestController public class ApiGateway { - @Resource - private ImageClient imageClient; + @Resource private ImageClient imageClient; - @Resource - private PriceClient priceClient; + @Resource private PriceClient priceClient; /** * Retrieves product information that desktop clients need. diff --git a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java index 9ccf8696afec..4754ba0a55a2 100644 --- a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/App.java @@ -36,14 +36,14 @@ * sometime in the future) or if the location (host and port) of a microservice changes, then every * client that makes use of those microservices must be updated. * - *

    The intent of the API Gateway pattern is to alleviate some of these issues. In the API - * Gateway pattern, an additional entity (the API Gateway) is placed between the client and the + *

    The intent of the API Gateway pattern is to alleviate some of these issues. In the API Gateway + * pattern, an additional entity (the API Gateway) is placed between the client and the * microservices. The job of the API Gateway is to aggregate the calls to the microservices. Rather * than the client calling each microservice individually, the client calls the API Gateway a single * time. The API Gateway then calls each of the microservices that the client needs. * - *

    This implementation shows what the API Gateway pattern could look like for an e-commerce - * site. The {@link ApiGateway} makes calls to the Image and Price microservices using the {@link + *

    This implementation shows what the API Gateway pattern could look like for an e-commerce site. + * The {@link ApiGateway} makes calls to the Image and Price microservices using the {@link * ImageClientImpl} and {@link PriceClientImpl} respectively. Customers viewing the site on a * desktop device can see both price information and an image of a product, so the {@link * ApiGateway} calls both of the microservices and aggregates the data in the {@link DesktopProduct} diff --git a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java index 85917dc1c5e7..40cc795fd9a0 100644 --- a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/DesktopProduct.java @@ -27,21 +27,14 @@ import lombok.Getter; import lombok.Setter; -/** - * Encapsulates all of the information that a desktop client needs to display a product. - */ +/** Encapsulates all of the information that a desktop client needs to display a product. */ @Getter @Setter public class DesktopProduct { - /** - * The price of the product. - */ + /** The price of the product. */ private String price; - /** - * The path to the image of the product. - */ + /** The path to the image of the product. */ private String imagePath; - } diff --git a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java index 74250271435b..9f7e08c89f43 100644 --- a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClient.java @@ -24,9 +24,7 @@ */ package com.iluwatar.api.gateway; -/** - * An interface used to communicate with the Image microservice. - */ +/** An interface used to communicate with the Image microservice. */ public interface ImageClient { String getImagePath(); } diff --git a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java index 86008b74a01c..2ebef5bd39b3 100644 --- a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/ImageClientImpl.java @@ -33,9 +33,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -/** - * An adapter to communicate with the Image microservice. - */ +/** An adapter to communicate with the Image microservice. */ @Slf4j @Component public class ImageClientImpl implements ImageClient { @@ -47,11 +45,10 @@ public class ImageClientImpl implements ImageClient { */ @Override public String getImagePath() { + var httpClient = HttpClient.newHttpClient(); - var httpGet = HttpRequest.newBuilder() - .GET() - .uri(URI.create("http://localhost:50005/image-path")) - .build(); + var httpGet = + HttpRequest.newBuilder().GET().uri(URI.create("http://localhost:50005/image-path")).build(); try { LOGGER.info("Sending request to fetch image path"); diff --git a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java index f621da1a575f..ed42248eadee 100644 --- a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/MobileProduct.java @@ -27,14 +27,10 @@ import lombok.Getter; import lombok.Setter; -/** - * Encapsulates all of the information that mobile client needs to display a product. - */ +/** Encapsulates all of the information that mobile client needs to display a product. */ @Getter @Setter public class MobileProduct { - /** - * The price of the product. - */ + /** The price of the product. */ private String price; } diff --git a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java index 5294dbd6dc4a..003fa478be2f 100644 --- a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClient.java @@ -24,9 +24,7 @@ */ package com.iluwatar.api.gateway; -/** - * An interface used to communicate with the Price microservice. - */ +/** An interface used to communicate with the Price microservice. */ public interface PriceClient { String getPrice(); } diff --git a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java index b94bdb4a17d3..47fb0617db6c 100644 --- a/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java +++ b/microservices-api-gateway/api-gateway-service/src/main/java/com/iluwatar/api/gateway/PriceClientImpl.java @@ -33,10 +33,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; - -/** - * An adapter to communicate with the Price microservice. - */ +/** An adapter to communicate with the Price microservice. */ @Slf4j @Component public class PriceClientImpl implements PriceClient { @@ -49,10 +46,8 @@ public class PriceClientImpl implements PriceClient { @Override public String getPrice() { var httpClient = HttpClient.newHttpClient(); - var httpGet = HttpRequest.newBuilder() - .GET() - .uri(URI.create("http://localhost:50006/price")) - .build(); + var httpGet = + HttpRequest.newBuilder().GET().uri(URI.create("http://localhost:50006/price")).build(); try { LOGGER.info("Sending request to fetch price info"); diff --git a/microservices-api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java b/microservices-api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java index 0f1fa938e751..f177512f5976 100644 --- a/microservices-api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java +++ b/microservices-api-gateway/api-gateway-service/src/test/java/com/iluwatar/api/gateway/ApiGatewayTest.java @@ -33,28 +33,21 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * Test API Gateway Pattern - */ +/** Test API Gateway Pattern */ class ApiGatewayTest { - @InjectMocks - private ApiGateway apiGateway; + @InjectMocks private ApiGateway apiGateway; - @Mock - private ImageClient imageClient; + @Mock private ImageClient imageClient; - @Mock - private PriceClient priceClient; + @Mock private PriceClient priceClient; @BeforeEach void setup() { MockitoAnnotations.openMocks(this); } - /** - * Tests getting the data for a desktop client - */ + /** Tests getting the data for a desktop client */ @Test void testGetProductDesktop() { var imagePath = "/product-image.png"; @@ -68,9 +61,7 @@ void testGetProductDesktop() { assertEquals(imagePath, desktopProduct.getImagePath()); } - /** - * Tests getting the data for a mobile client - */ + /** Tests getting the data for a mobile client */ @Test void testGetProductMobile() { var price = "20"; diff --git a/microservices-api-gateway/etc/microservices-api-gateway-sequence-diagram.png b/microservices-api-gateway/etc/microservices-api-gateway-sequence-diagram.png new file mode 100644 index 000000000000..9186b32322a7 Binary files /dev/null and b/microservices-api-gateway/etc/microservices-api-gateway-sequence-diagram.png differ diff --git a/microservices-api-gateway/etc/microservices-api-gateway.urm.puml b/microservices-api-gateway/etc/microservices-api-gateway.urm.puml new file mode 100644 index 000000000000..02af47ddf261 --- /dev/null +++ b/microservices-api-gateway/etc/microservices-api-gateway.urm.puml @@ -0,0 +1,2 @@ +@startuml +@enduml \ No newline at end of file diff --git a/microservices-api-gateway/image-microservice/pom.xml b/microservices-api-gateway/image-microservice/pom.xml index 7f27eb5150f2..7c08fe2ec0e1 100644 --- a/microservices-api-gateway/image-microservice/pom.xml +++ b/microservices-api-gateway/image-microservice/pom.xml @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/microservices-api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java b/microservices-api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java index 0594a4fa9485..a737f0b8ddd1 100644 --- a/microservices-api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java +++ b/microservices-api-gateway/image-microservice/src/main/java/com/iluwatar/image/microservice/ImageController.java @@ -28,10 +28,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; - -/** - * Exposes the Image microservice's endpoints. - */ +/** Exposes the Image microservice's endpoints. */ @Slf4j @RestController public class ImageController { diff --git a/microservices-api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java b/microservices-api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java index bfb0af75adce..f2ff0c747ec4 100644 --- a/microservices-api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java +++ b/microservices-api-gateway/image-microservice/src/test/java/com/iluwatar/image/microservice/ImageControllerTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.image.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Image Rest Controller - */ +import org.junit.jupiter.api.Test; + +/** Test for Image Rest Controller */ class ImageControllerTest { @Test diff --git a/microservices-api-gateway/pom.xml b/microservices-api-gateway/pom.xml index 762295876e49..18022d957ed2 100644 --- a/microservices-api-gateway/pom.xml +++ b/microservices-api-gateway/pom.xml @@ -34,17 +34,6 @@ 4.0.0 microservices-api-gateway pom - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.2.4 - import - - - image-microservice price-microservice diff --git a/microservices-api-gateway/price-microservice/pom.xml b/microservices-api-gateway/price-microservice/pom.xml index 6dedbd398bfc..8e95948aff6e 100644 --- a/microservices-api-gateway/price-microservice/pom.xml +++ b/microservices-api-gateway/price-microservice/pom.xml @@ -38,6 +38,7 @@ org.springframework spring-webmvc + 6.2.5 org.springframework.boot diff --git a/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java index 9b922a50285a..695d3aadb34e 100644 --- a/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java +++ b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceController.java @@ -28,10 +28,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; - -/** - * Exposes the Price microservice's endpoints. - */ +/** Exposes the Price microservice's endpoints. */ @RestController @RequiredArgsConstructor public class PriceController { diff --git a/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceService.java b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceService.java index 1d09c5311b20..253bab4e5f81 100644 --- a/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceService.java +++ b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.price.microservice; -/** - * Service to get a product's price. - */ +/** Service to get a product's price. */ public interface PriceService { /** diff --git a/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceServiceImpl.java b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceServiceImpl.java index c4b8aa1059e3..1338313fe2de 100644 --- a/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceServiceImpl.java +++ b/microservices-api-gateway/price-microservice/src/main/java/com/iluwatar/price/microservice/PriceServiceImpl.java @@ -27,16 +27,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -/** - * {@inheritDoc} - */ +/** {@inheritDoc} */ @Service @Slf4j public class PriceServiceImpl implements PriceService { - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public String getPrice() { LOGGER.info("Successfully found price info"); diff --git a/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java b/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java index 91c9b36bef14..eeaa0c28d377 100644 --- a/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java +++ b/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceControllerTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.price.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Price Rest Controller - */ +import org.junit.jupiter.api.Test; + +/** Test for Price Rest Controller */ class PriceControllerTest { @Test diff --git a/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceServiceTest.java b/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceServiceTest.java index c6e87e7c7db1..830ac7d18423 100644 --- a/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceServiceTest.java +++ b/microservices-api-gateway/price-microservice/src/test/java/com/iluwatar/price/microservice/PriceServiceTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.price.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Price Service - */ +import org.junit.jupiter.api.Test; + +/** Test for Price Service */ class PriceServiceTest { @Test diff --git a/microservices-client-side-ui-composition/README.md b/microservices-client-side-ui-composition/README.md new file mode 100644 index 000000000000..d2b1ae7fd158 --- /dev/null +++ b/microservices-client-side-ui-composition/README.md @@ -0,0 +1,146 @@ +--- +title: "Microservices Client-Side UI Composition Pattern In Java: Assembling Modular UIs in Microservices Architecture" +shortTitle: Microservices Client-Side UI Composition +description: "Learn how the Client-Side UI Composition pattern allows the assembly of modular UIs on the client side, enabling independent teams to develop, deploy, and scale UI components in a microservices architecture. Discover the benefits, implementation examples, and best practices." +category: Architectural +language: en +tag: + - Client-server + - Cloud distributed + - Composition + - Decoupling + - Integration + - Microservices + - Modularity + - Scalability + - Web development +--- + +## Intent of Client-Side UI Composition Design Pattern + +Compose user interface from independently deployable microservices on the client side for greater flexibility and decoupling. + +## Also Known As + +* UI Aggregator +* Frontend-Driven Composition + +## Detailed Explanation of Client-Side UI Composition Pattern with Real-World Examples + +Real-world Example + +> In a SaaS dashboard, a client-side composition pattern enables various independent modules like “Billing,” “Reports,” and “Account Settings” to be developed and deployed by separate teams. These modules are composed into a unified interface for the user, with each module independently fetching data from its respective microservice. + +In Plain Words + +> The Client-Side UI Composition pattern breaks down the user interface into smaller, independent parts that can be developed, maintained, and scaled separately by different teams. + +Wikipedia says + +> UI composition refers to the practice of building a user interface from modular components, each responsible for fetching its own data and rendering its own content. This approach enables faster development cycles, easier maintenance, and better scalability in large systems. + +Sequence diagram + +![Client-Side UI Composition sequence diagram](./etc/microservices-client-side-ui-composition-sequence-diagram.png) + +## Programmatic Example of Client-Side UI Composition in Java + +This example composes an e-commerce frontend by integrating three independent modules. Each module is served by a microservice and fetched on the client side through an API Gateway. + +### `ApiGateway` Implementation + +```java +public class ApiGateway { + + private final Map routes = new HashMap<>(); + + public void registerRoute(String path, FrontendComponent component) { + routes.put(path, component); + } + + public String handleRequest(String path, Map params) { + if (routes.containsKey(path)) { + return routes.get(path).fetchData(params); + } else { + return "404 Not Found"; + } + } +} + +``` + +### `FrontendComponent` Interface + +```java +public interface FrontendComponent { + String fetchData(Map params); +} +``` + +### Example Components + +```java +public class ProductComponent implements FrontendComponent { + @Override + public String fetchData(Map params) { + return "Displaying Products: " + params.getOrDefault("category", "all"); + } +} + +public class CartComponent implements FrontendComponent { + @Override + public String fetchData(Map params) { + return "Displaying Cart for User: " + params.getOrDefault("userId", "unknown"); + } +} +``` + +This approach dynamically assembles UI components based on the route in the client-side request. Each component fetches its data asynchronously and renders it within the main interface. + +## When to Use the Client-Side UI Composition Pattern + +* When each microservice must present its own UI components +* When frequent independent deployments of UI features are required +* When teams own end-to-end functionality, including front-end modules +* When Java-based backends provide separate APIs for direct UI consumption + +## Client-Side UI Composition Pattern Tutorials + +- [Micro Frontends in Action (O'Reilly)](https://www.oreilly.com/library/view/micro-frontends-in/9781617296873/) +- [Micro Frontends with React (ThoughtWorks)](https://www.thoughtworks.com/insights/articles/building-micro-frontends-using-react) +- [API Gateway in Microservices (Spring Cloud)](https://spring.io/guides/gs/gateway/) + +## Real-World Applications of Client-Side UI Composition Pattern in Java + +* Large-scale e-commerce portals with microservices powering modular pages +* SaaS platforms requiring rapid iteration of front-end features +* Java-based microservice architectures using frameworks like Spring Boot for modular UI components + +## Benefits and Trade-offs of Client-Side UI Composition Pattern + +Benefits: + +* Facilitates independent deployment cycles for UI features +* Reduces overall coupling and fosters autonomy among teams +* Enables polyglot front-end development + +Trade-offs: + +* Increases client complexity for rendering and data aggregation +* Can cause performance challenges with multiple service calls +* Demands consistent UX guidelines across diverse microservices + +## Related Design Patterns + +* Backend for Frontend (BFF): Provides custom endpoints for specific UIs +* Micro Frontends: Splits the front-end into smaller, individually deployable fragments +* [Microservices API Gateway Pattern](https://java-design-patterns.com/patterns/microservices-api-gateway/): API Gateway serves as a routing mechanism for client-side UI requests. + +## References and Credits + +* [Building Microservices](https://amzn.to/3UACtrU) +* [Building Microservices with Micro Frontends (Martin Fowler)](https://martinfowler.com/articles/micro-frontends.html) +* [Client-Side UI Composition (Microservices.io)](https://microservices.io/patterns/client-side-ui-composition.html) +* [Cloud Native Java: Designing Resilient Systems with Spring Boot, Spring Cloud, and Cloud Foundry](https://amzn.to/44vDTat) +* [Micro Frontends Architecture (Microfrontends.org)](https://micro-frontends.org/) +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) diff --git a/microservices-client-side-ui-composition/etc/client-side-ui-composition.urm.puml b/microservices-client-side-ui-composition/etc/client-side-ui-composition.urm.puml new file mode 100644 index 000000000000..ef88e9c22ac7 --- /dev/null +++ b/microservices-client-side-ui-composition/etc/client-side-ui-composition.urm.puml @@ -0,0 +1,31 @@ +@startuml client_side_ui_composition_updated +skinparam classAttributeIconSize 0 + +class ApiGateway { + +registerRoute(path: String, component: FrontendComponent) + +handleRequest(path: String, params: Map): String +} + +class FrontendComponent { + +fetchData(params: Map): String + #getData(params: Map): String +} + +class ProductFrontend { + +getData(params: Map): String +} + +class CartFrontend { + +getData(params: Map): String +} + +class ClientSideIntegrator { + +composeUI(path: String, params: Map) +} + +ApiGateway --> FrontendComponent +FrontendComponent <|-- ProductFrontend +FrontendComponent <|-- CartFrontend +ClientSideIntegrator --> ApiGateway + +@enduml diff --git a/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition-sequence-diagram.png b/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition-sequence-diagram.png new file mode 100644 index 000000000000..cb0dd57ae2f5 Binary files /dev/null and b/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition-sequence-diagram.png differ diff --git a/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition.urm.puml b/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition.urm.puml new file mode 100644 index 000000000000..0820d3e45a17 --- /dev/null +++ b/microservices-client-side-ui-composition/etc/microservices-client-side-ui-composition.urm.puml @@ -0,0 +1,33 @@ +@startuml +package com.iluwatar.clientsideuicomposition { + class ApiGateway { + - routes : Map + + ApiGateway() + + handleRequest(path : String, params : Map) : String + + registerRoute(path : String, component : FrontendComponent) + } + class CartFrontend { + + CartFrontend() + # getData(params : Map) : String + } + class ClientSideIntegrator { + - LOGGER : Logger {static} + - apiGateway : ApiGateway + + ClientSideIntegrator(apiGateway : ApiGateway) + + composeUi(path : String, params : Map) + } + abstract class FrontendComponent { + + random : Random {static} + + FrontendComponent() + + fetchData(params : Map) : String + # getData(Map) : String {abstract} + } + class ProductFrontend { + + ProductFrontend() + # getData(params : Map) : String + } +} +ClientSideIntegrator --> "-apiGateway" ApiGateway +CartFrontend --|> FrontendComponent +ProductFrontend --|> FrontendComponent +@enduml \ No newline at end of file diff --git a/microservices-client-side-ui-composition/pom.xml b/microservices-client-side-ui-composition/pom.xml new file mode 100644 index 000000000000..2d5644a14633 --- /dev/null +++ b/microservices-client-side-ui-composition/pom.xml @@ -0,0 +1,58 @@ + + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + microservices-client-side-ui-composition + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + \ No newline at end of file diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ApiGateway.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ApiGateway.java new file mode 100644 index 000000000000..6fc1cc643a4f --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ApiGateway.java @@ -0,0 +1,73 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.HashMap; +import java.util.Map; + +/** + * ApiGateway class acts as a dynamic routing mechanism that forwards client requests to the + * appropriate frontend components based on dynamically registered routes. + * + *

    This allows for flexible, runtime-defined routing without hardcoding specific paths. + */ +public class ApiGateway { + + // A map to store routes dynamically, where the key is the path and the value + // is the associated FrontendComponent + private final Map routes = new HashMap<>(); + + /** + * Registers a route dynamically at runtime. + * + * @param path the path to access the component (e.g., "/products") + * @param component the frontend component to be accessed at the given path + */ + public void registerRoute(String path, FrontendComponent component) { + routes.put(path, component); + } + + /** + * Handles a client request by routing it to the appropriate frontend component. + * + *

    This method dynamically handles parameters passed with the request, which allows the + * frontend components to respond based on those parameters. + * + * @param path the path for which the request is made (e.g., "/products", "/cart") + * @param params a map of parameters that might influence the data fetching logic (e.g., filters, + * userId, categories, etc.) + * @return the data fetched from the appropriate component or "404 Not Found" if the path is not + * registered + */ + public String handleRequest(String path, Map params) { + if (routes.containsKey(path)) { + // Fetch data dynamically based on the provided parameters + return routes.get(path).fetchData(params); + } else { + // Return a 404 error if the path is not registered + return "404 Not Found"; + } + } +} diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/CartFrontend.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/CartFrontend.java new file mode 100644 index 000000000000..d2916408babd --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/CartFrontend.java @@ -0,0 +1,46 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.Map; + +/** + * CartFrontend is a concrete implementation of FrontendComponent that simulates fetching shopping + * cart data based on the user. + */ +public class CartFrontend extends FrontendComponent { + + /** + * Fetches the current state of the shopping cart based on dynamic parameters like user ID. + * + * @param params parameters that influence the cart data, e.g., "userId" + * @return a string representing the items in the shopping cart for a given user + */ + @Override + protected String getData(Map params) { + String userId = params.getOrDefault("userId", "anonymous"); + return "Shopping Cart for user '" + userId + "': [Item 1, Item 2]"; + } +} diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ClientSideIntegrator.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ClientSideIntegrator.java new file mode 100644 index 000000000000..ca99747a7ddf --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ClientSideIntegrator.java @@ -0,0 +1,61 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** + * ClientSideIntegrator class simulates the client-side integration layer that dynamically assembles + * various frontend components into a cohesive user interface. + */ +@Slf4j +public class ClientSideIntegrator { + + private final ApiGateway apiGateway; + + /** + * Constructor that accepts an instance of ApiGateway to handle dynamic routing. + * + * @param apiGateway the gateway that routes requests to different frontend components + */ + public ClientSideIntegrator(ApiGateway apiGateway) { + this.apiGateway = apiGateway; + } + + /** + * Composes the user interface dynamically by fetching data from different frontend components + * based on provided parameters. + * + * @param path the route of the frontend component + * @param params a map of dynamic parameters to influence the data fetching + */ + public void composeUi(String path, Map params) { + // Fetch data dynamically based on the route and parameters + String data = apiGateway.handleRequest(path, params); + LOGGER.info("Composed UI Component for path '" + path + "':"); + LOGGER.info(data); + } +} diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/FrontendComponent.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/FrontendComponent.java new file mode 100644 index 000000000000..70399c31854f --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/FrontendComponent.java @@ -0,0 +1,63 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.Map; +import java.util.Random; + +/** + * FrontendComponent is an abstract class representing an independent frontend component that + * fetches data dynamically based on the provided parameters. + */ +public abstract class FrontendComponent { + + public static final Random random = new Random(); + + /** + * Simulates asynchronous data fetching by introducing a random delay and then fetching the data + * based on dynamic input. + * + * @param params a map of parameters that may affect the data fetching logic + * @return the data fetched by the frontend component + */ + public String fetchData(Map params) { + try { + // Simulate delay in fetching data (e.g., network latency) + Thread.sleep(random.nextInt(1000)); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + // Fetch and return the data based on the given parameters + return getData(params); + } + + /** + * Abstract method to be implemented by subclasses to return data based on parameters. + * + * @param params a map of parameters that may affect the data fetching logic + * @return the data for this specific component + */ + protected abstract String getData(Map params); +} diff --git a/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ProductFrontend.java b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ProductFrontend.java new file mode 100644 index 000000000000..157f2aef3fd6 --- /dev/null +++ b/microservices-client-side-ui-composition/src/main/java/com/iluwatar/clientsideuicomposition/ProductFrontend.java @@ -0,0 +1,46 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import java.util.Map; + +/** + * ProductFrontend is a concrete implementation of FrontendComponent that simulates fetching dynamic + * product data. + */ +public class ProductFrontend extends FrontendComponent { + + /** + * Fetches a list of products based on dynamic parameters such as category. + * + * @param params parameters that influence the data fetched, e.g., "category" + * @return a string representing a filtered list of products + */ + @Override + protected String getData(Map params) { + String category = params.getOrDefault("category", "all"); + return "Product List for category '" + category + "': [Product 1, Product 2, Product 3]"; + } +} diff --git a/microservices-client-side-ui-composition/src/test/java/com/iluwatar/clientsideuicomposition/ClientSideCompositionTest.java b/microservices-client-side-ui-composition/src/test/java/com/iluwatar/clientsideuicomposition/ClientSideCompositionTest.java new file mode 100644 index 000000000000..be5aef2f7aaf --- /dev/null +++ b/microservices-client-side-ui-composition/src/test/java/com/iluwatar/clientsideuicomposition/ClientSideCompositionTest.java @@ -0,0 +1,68 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.clientsideuicomposition; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; + +/** + * ClientSideCompositionTest contains unit tests to validate dynamic route registration and UI + * composition. + */ +class ClientSideCompositionTest { + + /** Tests dynamic registration of frontend components and dynamic composition of UI. */ + @Test + void testClientSideUIComposition() { + // Create API Gateway and dynamically register frontend components + ApiGateway apiGateway = new ApiGateway(); + apiGateway.registerRoute("/products", new ProductFrontend()); + apiGateway.registerRoute("/cart", new CartFrontend()); + + // Create the Client-Side Integrator + ClientSideIntegrator integrator = new ClientSideIntegrator(apiGateway); + + // Dynamically pass parameters for data fetching + Map productParams = new HashMap<>(); + productParams.put("category", "electronics"); + + // Compose UI for products and cart with dynamic params + integrator.composeUi("/products", productParams); + + Map cartParams = new HashMap<>(); + cartParams.put("userId", "user123"); + integrator.composeUi("/cart", cartParams); + + // Validate the dynamically fetched data + String productData = apiGateway.handleRequest("/products", productParams); + String cartData = apiGateway.handleRequest("/cart", cartParams); + + assertTrue(productData.contains("Product List for category 'electronics'")); + assertTrue(cartData.contains("Shopping Cart for user 'user123'")); + } +} diff --git a/microservices-distributed-tracing/README.md b/microservices-distributed-tracing/README.md index 14b34726fc10..4fc271a68824 100644 --- a/microservices-distributed-tracing/README.md +++ b/microservices-distributed-tracing/README.md @@ -1,32 +1,32 @@ --- -title: "Microservices Distributed Tracing Pattern: Enhancing Visibility in Service Communication" -shortTitle: Distributed Tracing in Microservices +title: "Microservices Distributed Tracing Pattern In Java: Enhancing Visibility in Service Communication" +shortTitle: Microservices Distributed Tracing description: "Learn how the Distributed Tracing pattern enhances visibility into service communication across microservices. Discover its benefits, implementation examples, and best practices." -category: Integration +category: Architectural language: en tag: - - Distributed tracing - - Microservices architecture - - Service communication - - Performance monitoring - - Scalability - - Observability + - Cloud distributed + - Microservices + - Resilience + - Observability + - Scalability + - System health --- ## Intent of Microservices Distributed Tracing Design Pattern -Distributed tracing aims to monitor and track requests as they flow through different services in a microservices architecture, providing insights into performance, dependencies, and failures. +Provide a mechanism to trace and correlate requests as they traverse multiple microservices in a distributed system, enabling end-to-end visibility and easier troubleshooting. ## Also known as * Distributed Request Tracing -* End-to-End Tracing +* End-to-End Microservice Tracing ## Detailed Explanation of Microservices Distributed Tracing Pattern with Real-World Examples Real-world example -> In an e-commerce platform, distributed tracing is used to track a customer's request from the moment they add an item to the cart until the order is processed and shipped. This helps in identifying bottlenecks, errors, and latency issues across different services. +> Imagine an online food delivery platform where one microservice handles user orders, another manages restaurant menus, and yet another coordinates courier assignments. When a user places an order, the request travels through all three services in sequence. By implementing distributed tracing, each service attaches a trace identifier to its logs. This allows the operations team to follow the journey of a single order across the entire pipeline, identify any delays along the way, and quickly pinpoint which service is causing the bottleneck or experiencing an error. In plain words @@ -36,10 +36,13 @@ Wikipedia says > Tracing in software engineering refers to the process of capturing and recording information about the execution of a software program. This information is typically used by programmers for debugging purposes, and additionally, depending on the type and detail of information contained in a trace log, by experienced system administrators or technical-support personnel and by software monitoring tools to diagnose common problems with software. -## Programmatic Example of Microservices Distributed Tracing in Java +Sequence diagram + +![Microservices Distributed Tracing Sequence Diagram](./etc/microservices-distributed-tracing-sequence-diagram.png) +## Programmatic Example of Microservices Distributed Tracing in Java -This implementation shows how an e-commerce platform's `OrderService` interacts with both `PaymentService` and `ProductService`. When a customer places an order, the `OrderService` calls the `PaymentService` to process the payment and the `ProductService` to check the product inventory. Distributed tracing logs are generated for each of these interactions and can be viewed in the Zipkin interface to monitor the flow and performance of requests across these services. +This implementation shows how an e-commerce platform's `OrderService` interacts with both `PaymentService` and `ProductService`. When a customer places an order, the `OrderService` calls the `PaymentService` to process the payment and the `ProductService` to check the product inventory. By adding distributed trace instrumentation (usually via libraries like Spring Cloud Sleuth or OpenTelemetry), each service attaches trace context to outgoing requests and logs. These logs can then be viewed in the Zipkin interface (or other tracing tools, such as Jaeger) to observe the entire flow of the request and quickly identify any performance bottlenecks or failures across multiple services. Here's the `Order microservice` implementation. @@ -155,12 +158,13 @@ public class ProductController { } ``` -## When to Use the Microservices Distributed Tracing Pattern in Java +In this example, each microservice would typically be configured with tracing libraries (like Sleuth or OpenTelemetry). The trace context is propagated via HTTP headers, enabling the logs and metrics for each service call to be grouped together and visualized in Zipkin or another compatible tool. This ensures complete end-to-end visibility into each request’s journey. -* When you have a microservices architecture and need to monitor the flow of requests across multiple services. -* When troubleshooting performance issues or errors in a distributed system. -* When you need to gain insights into system bottlenecks and optimize overall performance. +## When to Use the Microservices Distributed Tracing Pattern in Java +* When multiple services form a single user request path and debugging failures requires visibility across service boundaries. +* When monitoring or diagnosing performance bottlenecks is critical in a multi-service environment. +* When correlation of logs and metrics from independent services is needed to understand overall system health. ## Microservices Distributed Tracing Pattern Java Tutorials @@ -168,12 +172,18 @@ public class ProductController { * [Reactive Observability (Spring Academy)](https://spring.academy/guides/microservices-observability-reactive-spring-boot-3) * [Spring Cloud – Tracing Services with Zipkin (Baeldung)](https://dzone.com/articles/getting-started-with-spring-cloud-gateway) +## Real-World Applications of Microservices Distributed Tracing Pattern in Java + +* OpenTelemetry for tracing instrumentation in Java services. +* Spring Cloud Sleuth for automatic tracing in Spring Boot microservices. +* Jaeger and Zipkin for collecting and visualizing distributed traces in Java-based systems. + ## Benefits and Trade-offs of Microservices Distributed Tracing Pattern Benefits: -* Provides end-to-end visibility into requests. -* Helps in identifying performance bottlenecks. +* Centralized insight into request paths across services, reducing time to diagnose issues. +* Improved observability enables proactive identification of system bottlenecks. * Aids in debugging and troubleshooting complex systems. Trade-offs: @@ -182,20 +192,17 @@ Trade-offs: * Requires additional infrastructure (e.g., Zipkin, Jaeger) for collecting and visualizing traces. * Can become complex to manage in large-scale systems. -## Real-World Applications of Microservices Distributed Tracing Pattern in Java - -* Monitoring and troubleshooting e-commerce platforms. -* Performance monitoring in financial transaction systems. -* Observability in large-scale SaaS applications. - ## Related Java Design Patterns -* [Log Aggregation Microservice](https://java-design-patterns.com/patterns/microservices-log-aggregation/) - Distributed tracing works well in conjunction with log aggregation to provide comprehensive observability and troubleshooting capabilities. -* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/) - Distributed tracing can be used alongside the Circuit Breaker pattern to monitor and handle failures gracefully, preventing cascading failures in microservices. -* [API Gateway Microservice](https://java-design-patterns.com/patterns/microservices-api-gateway/) - The API Gateway pattern can be integrated with distributed tracing to provide a single entry point for tracing requests across multiple microservices. +* [API Gateway Microservice](https://java-design-patterns.com/patterns/microservices-api-gateway/): Acts as an entry point to microservices and can propagate trace information to downstream services. +* [Circuit Breaker](https://java-design-patterns.com/patterns/circuit-breaker/): Distributed tracing can be used alongside the Circuit Breaker pattern to monitor and handle failures gracefully, preventing cascading failures in microservices. +* [Log Aggregation Microservice](https://java-design-patterns.com/patterns/microservices-log-aggregation/): Distributed tracing works well in conjunction with log aggregation to provide comprehensive observability and troubleshooting capabilities. +* [Saga](https://java-design-patterns.com/patterns/saga/): Orchestrates distributed transactions, which benefit from trace identifiers to correlate steps across services. ## References and Credits * [Building Microservices](https://amzn.to/3UACtrU) -* [OpenTelemetry Documentation](https://opentelemetry.io/docs/) * [Distributed tracing (microservices.io)](https://microservices.io/patterns/observability/distributed-tracing.html) +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) +* [OpenTelemetry Documentation](https://opentelemetry.io/docs/) +* [Release It! Design and Deploy Production-Ready Software](https://amzn.to/3Uul4kF) diff --git a/microservices-distributed-tracing/etc/microservices-distributed-tracing-sequence-diagram.png b/microservices-distributed-tracing/etc/microservices-distributed-tracing-sequence-diagram.png new file mode 100644 index 000000000000..7840697100da Binary files /dev/null and b/microservices-distributed-tracing/etc/microservices-distributed-tracing-sequence-diagram.png differ diff --git a/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/Main.java b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/Main.java index 2b4d3f41fd65..b6b6da79faae 100644 --- a/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/Main.java +++ b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/Main.java @@ -52,10 +52,9 @@ * {@code docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin } * * - *

    Start Zipkin with the command above. Once Zipkin is running, you can - * access the Zipkin UI at `...` - * to view the tracing logs and analyze the request flows across your microservices. - * + *

    Start Zipkin with the command above. Once Zipkin is running, you can access the Zipkin UI at + * `...` to view the tracing logs and analyze the request flows + * across your microservices. * *

    To place an order and generate tracing data, you can use the following curl command: * @@ -65,7 +64,6 @@ * *

    This command sends a POST request to create an order, which will trigger interactions with the * payment and product microservices, generating tracing logs that can be viewed in Zipkin. - * */ @SpringBootApplication public class Main { diff --git a/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderController.java b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderController.java index 5f50e1f25fa0..a7c67868c49a 100644 --- a/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderController.java +++ b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderController.java @@ -30,9 +30,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -/** - * This controller handles order processing by calling necessary microservices. - */ +/** This controller handles order processing by calling necessary microservices. */ @Slf4j @RestController public class OrderController { diff --git a/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderService.java b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderService.java index 046fc9fff769..b4be09309d26 100644 --- a/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderService.java +++ b/microservices-distributed-tracing/order-microservice/src/main/java/com/iluwatar/order/microservice/OrderService.java @@ -31,9 +31,7 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.ResourceAccessException; -/** - * Service to handle order processing logic. - */ +/** Service to handle order processing logic. */ @Slf4j @Service public class OrderService { @@ -50,9 +48,8 @@ public OrderService(final RestTemplateBuilder restTemplateBuilder) { } /** - * Processes an order by calling - * {@link OrderService#validateProduct()} and - * {@link OrderService#processPayment()}. + * Processes an order by calling {@link OrderService#validateProduct()} and {@link + * OrderService#processPayment()}. * * @return A string indicating whether the order was processed successfully or failed. */ @@ -70,10 +67,11 @@ public String processOrder() { */ Boolean validateProduct() { try { - ResponseEntity productValidationResult = restTemplateBuilder - .build() - .postForEntity("http://localhost:30302/product/validate", "validating product", - Boolean.class); + ResponseEntity productValidationResult = + restTemplateBuilder + .build() + .postForEntity( + "http://localhost:30302/product/validate", "validating product", Boolean.class); LOGGER.info("Product validation result: {}", productValidationResult.getBody()); return productValidationResult.getBody(); } catch (ResourceAccessException | HttpClientErrorException e) { @@ -89,10 +87,11 @@ Boolean validateProduct() { */ Boolean processPayment() { try { - ResponseEntity paymentProcessResult = restTemplateBuilder - .build() - .postForEntity("http://localhost:30301/payment/process", "processing payment", - Boolean.class); + ResponseEntity paymentProcessResult = + restTemplateBuilder + .build() + .postForEntity( + "http://localhost:30301/payment/process", "processing payment", Boolean.class); LOGGER.info("Payment processing result: {}", paymentProcessResult.getBody()); return paymentProcessResult.getBody(); } catch (ResourceAccessException | HttpClientErrorException e) { diff --git a/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/MainTest.java b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/MainTest.java index 369690947062..97f9295ff0c1 100644 --- a/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/MainTest.java +++ b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/MainTest.java @@ -28,12 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class MainTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> Main.main(new String[]{})); + assertDoesNotThrow(() -> Main.main(new String[] {})); } } diff --git a/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderControllerTest.java b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderControllerTest.java index f6a593943fa5..05ecc03df0d7 100644 --- a/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderControllerTest.java +++ b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderControllerTest.java @@ -1,4 +1,4 @@ -package com.iluwatar.order.microservice;/* +/* * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). * * The MIT License @@ -22,6 +22,34 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +package com.iluwatar.order.microservice; /* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.InjectMocks; @@ -29,28 +57,19 @@ import org.mockito.MockitoAnnotations; import org.springframework.http.ResponseEntity; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.when; - -/** - * OrderControllerTest class to test the OrderController. - */ +/** OrderControllerTest class to test the OrderController. */ class OrderControllerTest { - @InjectMocks - private OrderController orderController; + @InjectMocks private OrderController orderController; - @Mock - private OrderService orderService; + @Mock private OrderService orderService; @BeforeEach void setup() { MockitoAnnotations.openMocks(this); } - /** - * Test to process the order successfully. - */ + /** Test to process the order successfully. */ @Test void processOrderShouldReturnSuccessStatus() { // Arrange @@ -61,9 +80,7 @@ void processOrderShouldReturnSuccessStatus() { assertEquals("Order processed successfully", response.getBody()); } - /** - * Test to process the order with failure. - */ + /** Test to process the order with failure. */ @Test void ProcessOrderShouldReturnFailureStatusWhen() { // Arrange diff --git a/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderServiceTest.java b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderServiceTest.java index c331c5abbca2..10a3fcacb670 100644 --- a/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderServiceTest.java +++ b/microservices-distributed-tracing/order-microservice/src/test/java/com/iluwatar/order/microservice/OrderServiceTest.java @@ -36,23 +36,18 @@ import org.mockito.MockitoAnnotations; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.http.ResponseEntity; -import org.springframework.web.client.RestTemplate; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.RestTemplate; -/** - * OrderServiceTest class to test the OrderService. - */ +/** OrderServiceTest class to test the OrderService. */ class OrderServiceTest { - @InjectMocks - private OrderService orderService; + @InjectMocks private OrderService orderService; - @Mock - private RestTemplateBuilder restTemplateBuilder; + @Mock private RestTemplateBuilder restTemplateBuilder; - @Mock - private RestTemplate restTemplate; + @Mock private RestTemplate restTemplate; @BeforeEach void setup() { @@ -60,15 +55,15 @@ void setup() { when(restTemplateBuilder.build()).thenReturn(restTemplate); } - /** - * Test to process the order successfully. - */ + /** Test to process the order successfully. */ @Test void testProcessOrder_Success() { // Arrange - when(restTemplate.postForEntity(eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) .thenReturn(ResponseEntity.ok(true)); - when(restTemplate.postForEntity(eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) .thenReturn(ResponseEntity.ok(true)); // Act String result = orderService.processOrder(); @@ -76,13 +71,12 @@ void testProcessOrder_Success() { assertEquals("Order processed successfully", result); } - /** - * Test to process the order with failure caused by product validation failure. - */ + /** Test to process the order with failure caused by product validation failure. */ @Test void testProcessOrder_FailureWithProductValidationFailure() { // Arrange - when(restTemplate.postForEntity(eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) .thenReturn(ResponseEntity.ok(false)); // Act String result = orderService.processOrder(); @@ -90,15 +84,15 @@ void testProcessOrder_FailureWithProductValidationFailure() { assertEquals("Order processing failed", result); } - /** - * Test to process the order with failure caused by payment processing failure. - */ + /** Test to process the order with failure caused by payment processing failure. */ @Test void testProcessOrder_FailureWithPaymentProcessingFailure() { // Arrange - when(restTemplate.postForEntity(eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) .thenReturn(ResponseEntity.ok(true)); - when(restTemplate.postForEntity(eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) .thenReturn(ResponseEntity.ok(false)); // Act String result = orderService.processOrder(); @@ -106,13 +100,12 @@ void testProcessOrder_FailureWithPaymentProcessingFailure() { assertEquals("Order processing failed", result); } - /** - * Test to validate the product. - */ + /** Test to validate the product. */ @Test void testValidateProduct() { // Arrange - when(restTemplate.postForEntity(eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) .thenReturn(ResponseEntity.ok(true)); // Act Boolean result = orderService.validateProduct(); @@ -120,13 +113,12 @@ void testValidateProduct() { assertEquals(true, result); } - /** - * Test to process the payment. - */ + /** Test to process the payment. */ @Test void testProcessPayment() { // Arrange - when(restTemplate.postForEntity(eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) .thenReturn(ResponseEntity.ok(true)); // Act Boolean result = orderService.processPayment(); @@ -134,13 +126,12 @@ void testProcessPayment() { assertEquals(true, result); } - /** - * Test to validate the product with ResourceAccessException. - */ + /** Test to validate the product with ResourceAccessException. */ @Test void testValidateProduct_ResourceAccessException() { // Arrange - when(restTemplate.postForEntity(eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) .thenThrow(new ResourceAccessException("Service unavailable")); // Act Boolean result = orderService.validateProduct(); @@ -148,27 +139,27 @@ void testValidateProduct_ResourceAccessException() { assertEquals(false, result); } - /** - * Test to validate the product with HttpClientErrorException. - */ + /** Test to validate the product with HttpClientErrorException. */ @Test void testValidateProduct_HttpClientErrorException() { // Arrange - when(restTemplate.postForEntity(eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) - .thenThrow(new HttpClientErrorException(org.springframework.http.HttpStatus.BAD_REQUEST, "Bad request")); + when(restTemplate.postForEntity( + eq("http://localhost:30302/product/validate"), anyString(), eq(Boolean.class))) + .thenThrow( + new HttpClientErrorException( + org.springframework.http.HttpStatus.BAD_REQUEST, "Bad request")); // Act Boolean result = orderService.validateProduct(); // Assert assertEquals(false, result); } - /** - * Test to process the payment with ResourceAccessException. - */ + /** Test to process the payment with ResourceAccessException. */ @Test void testProcessPayment_ResourceAccessException() { // Arrange - when(restTemplate.postForEntity(eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) .thenThrow(new ResourceAccessException("Service unavailable")); // Act Boolean result = orderService.processPayment(); @@ -176,14 +167,15 @@ void testProcessPayment_ResourceAccessException() { assertEquals(false, result); } - /** - * Test to process the payment with HttpClientErrorException. - */ + /** Test to process the payment with HttpClientErrorException. */ @Test void testProcessPayment_HttpClientErrorException() { // Arrange - when(restTemplate.postForEntity(eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) - .thenThrow(new HttpClientErrorException(org.springframework.http.HttpStatus.BAD_REQUEST, "Bad request")); + when(restTemplate.postForEntity( + eq("http://localhost:30301/payment/process"), anyString(), eq(Boolean.class))) + .thenThrow( + new HttpClientErrorException( + org.springframework.http.HttpStatus.BAD_REQUEST, "Bad request")); // Act Boolean result = orderService.processPayment(); // Assert diff --git a/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/Main.java b/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/Main.java index e41f1c01bb47..506096f4e909 100644 --- a/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/Main.java +++ b/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/Main.java @@ -41,10 +41,9 @@ * journey. * *

    This implementation demonstrates distributed tracing in a microservices architecture for an - * e-commerce platform. When a customer places an order, the OrderService interacts with - * both the PaymentService to process the payment and the ProductService to check the - * product inventory. Tracing logs are generated for each interaction, and these logs can be - * visualized using Zipkin. + * e-commerce platform. When a customer places an order, the OrderService interacts with both the + * PaymentService to process the payment and the ProductService to check the product inventory. + * Tracing logs are generated for each interaction, and these logs can be visualized using Zipkin. * *

    To run Zipkin and view the tracing logs, you can use the following Docker command: * @@ -52,9 +51,9 @@ * {@code docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin } * * - *

    Start Zipkin with the command above. Once Zipkin is running, you can - * access the Zipkin UI at http://localhost:9411 - * to view the tracing logs and analyze the request flows across your microservices. + *

    Start Zipkin with the command above. Once Zipkin is running, you can access the Zipkin UI at + * http://localhost:9411 to view the tracing logs and analyze + * the request flows across your microservices. * *

    To place an order and generate tracing data, you can use the following curl command: * @@ -64,7 +63,6 @@ * *

    This command sends a POST request to create an order, which will trigger interactions with the * payment and product microservices, generating tracing logs that can be viewed in Zipkin. - * */ @SpringBootApplication public class Main { diff --git a/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/PaymentController.java b/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/PaymentController.java index 2662589a03ef..834c88c07942 100644 --- a/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/PaymentController.java +++ b/microservices-distributed-tracing/payment-microservice/src/main/java/com/iluwatar/payment/microservice/PaymentController.java @@ -30,9 +30,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -/** - * Controller for handling payment processing requests. - */ +/** Controller for handling payment processing requests. */ @Slf4j @RestController public class PaymentController { diff --git a/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/MainTest.java b/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/MainTest.java index b8b3b6e56ba5..2b500440ebc0 100644 --- a/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/MainTest.java +++ b/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/MainTest.java @@ -24,16 +24,14 @@ */ package com.iluwatar.payment.microservice; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application Context loads test - */ +import org.junit.jupiter.api.Test; + +/** Application Context loads test */ class MainTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> Main.main(new String[]{})); + assertDoesNotThrow(() -> Main.main(new String[] {})); } } diff --git a/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/ProductControllerTest.java b/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/ProductControllerTest.java index 4b32d7662bc0..7fbbf2c1a7b8 100644 --- a/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/ProductControllerTest.java +++ b/microservices-distributed-tracing/payment-microservice/src/test/java/com/iluwatar/payment/microservice/ProductControllerTest.java @@ -24,15 +24,13 @@ */ package com.iluwatar.payment.microservice; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.ResponseEntity; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Payment controller test. - */ +/** Payment controller test. */ class ProductControllerTest { private PaymentController paymentController; @@ -41,9 +39,8 @@ class ProductControllerTest { void setUp() { paymentController = new PaymentController(); } - /** - * Test to process the payment. - */ + + /** Test to process the payment. */ @Test void testValidateProduct() { // Arrange @@ -54,9 +51,7 @@ void testValidateProduct() { assertEquals(ResponseEntity.ok(true), response); } - /** - * Test to process the payment with null request. - */ + /** Test to process the payment with null request. */ @Test void testValidateProductWithNullRequest() { // Arrange diff --git a/microservices-distributed-tracing/pom.xml b/microservices-distributed-tracing/pom.xml index 72eded678e0d..c7dddf0fa0b6 100644 --- a/microservices-distributed-tracing/pom.xml +++ b/microservices-distributed-tracing/pom.xml @@ -34,17 +34,6 @@ 4.0.0 microservices-distributed-tracing pom - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.3.1 - import - - - org.springframework.boot @@ -57,11 +46,13 @@ io.micrometer micrometer-tracing-bridge-brave + 1.4.5 compile io.zipkin.reporter2 zipkin-reporter-brave + 3.5.0 org.junit.jupiter diff --git a/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/Main.java b/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/Main.java index 93b2a838313f..91e41631c190 100644 --- a/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/Main.java +++ b/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/Main.java @@ -41,10 +41,9 @@ * journey. * *

    This implementation demonstrates distributed tracing in a microservices architecture for an - * e-commerce platform. When a customer places an order, the OrderService interacts with - * both the PaymentService to process the payment and the ProductService to check the - * product inventory. Tracing logs are generated for each interaction, and these logs can be - * visualized using Zipkin. + * e-commerce platform. When a customer places an order, the OrderService interacts with both the + * PaymentService to process the payment and the ProductService to check the product inventory. + * Tracing logs are generated for each interaction, and these logs can be visualized using Zipkin. * *

    To run Zipkin and view the tracing logs, you can use the following Docker command: * @@ -52,9 +51,9 @@ * {@code docker run -d -p 9411:9411 --name zipkin openzipkin/zipkin } * * - *

    Start Zipkin with the command above. Once Zipkin is running, you can - * access the Zipkin UI at http://localhost:9411 - * to view the tracing logs and analyze the request flows across your microservices. + *

    Start Zipkin with the command above. Once Zipkin is running, you can access the Zipkin UI at + * http://localhost:9411 to view the tracing logs and analyze + * the request flows across your microservices. * *

    To place an order and generate tracing data, you can use the following curl command: * @@ -64,7 +63,6 @@ * *

    This command sends a POST request to create an order, which will trigger interactions with the * payment and product microservices, generating tracing logs that can be viewed in Zipkin. - * */ @SpringBootApplication public class Main { diff --git a/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/ProductController.java b/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/ProductController.java index c9972e5b2c8c..cc4b21475cc1 100644 --- a/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/ProductController.java +++ b/microservices-distributed-tracing/product-microservice/src/main/java/com/iluwatar/product/microservice/microservice/ProductController.java @@ -30,9 +30,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -/** - * Controller for handling product validation requests. - */ +/** Controller for handling product validation requests. */ @Slf4j @RestController public class ProductController { diff --git a/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/MainTest.java b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/MainTest.java index f7c6b8800c45..1534943abd02 100644 --- a/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/MainTest.java +++ b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/MainTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.product.microservice; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.iluwatar.product.microservice.microservice.Main; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Application test - */ +/** Application test */ class MainTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> Main.main(new String[]{})); + assertDoesNotThrow(() -> Main.main(new String[] {})); } } diff --git a/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java index 139cae40b915..b1053be8f137 100644 --- a/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java +++ b/microservices-distributed-tracing/product-microservice/src/test/java/com/iluwatar/product/microservice/ProductControllerTest.java @@ -24,26 +24,23 @@ */ package com.iluwatar.product.microservice; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.iluwatar.product.microservice.microservice.ProductController; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.ResponseEntity; -import static org.junit.jupiter.api.Assertions.assertEquals; - - class ProductControllerTest { private ProductController productController; @BeforeEach - public void setUp() { + void setUp() { productController = new ProductController(); } - /** - * Test to validate the product. - */ + /** Test to validate the product. */ @Test void testValidateProduct() { // Arrange @@ -54,9 +51,7 @@ void testValidateProduct() { assertEquals(ResponseEntity.ok(true), response); } - /** - * Test to validate the product with null request. - */ + /** Test to validate the product with null request. */ @Test void testValidateProductWithNullRequest() { // Arrange diff --git a/microservices-idempotent-consumer/README.md b/microservices-idempotent-consumer/README.md index 794eb6e644aa..738bbcda5a3d 100644 --- a/microservices-idempotent-consumer/README.md +++ b/microservices-idempotent-consumer/README.md @@ -1,56 +1,56 @@ --- -title: "Idempotent Consumer Pattern in Java: Ensuring Reliable Message Processing" -shortTitle: Idempotent Consumer +title: "Microservices Idempotent Consumer Pattern in Java: Ensuring Reliable Message Processing" +shortTitle: Microservices Idempotent Consumer description: "Learn about the Idempotent Consumer pattern in Java. Discover how it ensures reliable and consistent message processing, even in cases of duplicate messages." category: Messaging language: en tag: - - Messaging - - Fault tolerance + - Asynchronous + - Decoupling - Event-driven - - Reliability + - Messaging + - Microservices + - Resilience + - Retry --- ## Also known as -* Idempotency Pattern +* Idempotent Subscriber +* Repeatable Message Consumer +* Safe Consumer ## Intent of Idempotent Consumer Pattern -The Idempotent Consumer pattern is used to handle duplicate messages in distributed systems, ensuring that multiple processing of the same message does not cause undesired side effects. This pattern guarantees that the same message can be processed repeatedly with the same outcome, which is critical in ensuring reliable communication and data consistency in systems where message duplicates are possible. +Ensure that consuming the same message multiple times does not cause unintended side effects in a microservices-based architecture. ## Detailed Explanation of Idempotent Consumer Pattern with Real-World Examples -### Real-world Example +Real-world example > In a payment processing system, ensuring that payment messages are idempotent prevents duplicate transactions. For example, if a user’s payment message is accidentally processed twice, the system should recognize the second message as a duplicate and prevent it from executing a second time. By storing unique identifiers for each processed message, such as a transaction ID, the system can skip any duplicate messages. This ensures that a user is not charged twice for the same transaction, maintaining system integrity and customer satisfaction. -### In Plain Words +In plain words > The Idempotent Consumer pattern prevents duplicate messages from causing unintended side effects by ensuring that processing the same message multiple times results in the same outcome. This makes message processing safe in distributed systems where duplicates may occur. -### Wikipedia says -> In computing, idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application. +Wikipedia says -## When to Use the Idempotent Consumer Pattern +> In computing, idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application. -The Idempotent Consumer pattern is particularly useful in scenarios: +Flowchart -* When messages can be duplicated due to network retries or communication issues. -* In distributed systems where message ordering is not guaranteed, making deduplication necessary to avoid repeated processing. -* In financial or critical systems, where duplicate processing would have significant side effects. +![Microservices Idempotent Consumer flowchart](./etc/microservices-idempotent-consumer-flowchart.png) -## Real-World Applications of Idempotent Consumer Pattern +## Programmatic example of Idempotent Consumer Pattern -* Payment processing systems that avoid duplicate transactions. -* E-commerce systems to prevent multiple entries of the same order. -* Inventory management systems to prevent multiple entries when updating stock levels. +In this Java example, we have an idempotent service that creates and updates orders. The `create` method is idempotent, meaning multiple calls with the same order ID return the same result without duplicates. For state changes (like starting or completing an order), the service checks whether the transition is valid and throws an exception if it’s not allowed. The `RequestStateMachine` ensures that order statuses move forward in a valid sequence (e.g., PENDING → STARTED → COMPLETED). -## Programmatic example of Idempotent Consumer Pattern -In this Java example, we have an idempotent service that offers functionality to create and update (start, complete, etc.) orders. The service ensures that the **create order** operation is idempotent, meaning that performing it multiple times with the same order ID will lead to the same result without creating duplicates. For state transitions (such as starting or completing an order), the service enforces valid state changes and throws exceptions if an invalid transition is attempted. The state machine governs the valid order status transitions, ensuring that statuses progress in a defined and consistent sequence. ### RequestService - Managing Idempotent Order Operations -The `RequestService` class is responsible for handling the creation and state transitions of orders. The `create` method is designed to be idempotent, ensuring that it either returns an existing order or creates a new one without any side effects if invoked multiple times with the same order ID. + +The `RequestService` class provides methods to create and transition an order. The `create` method returns an existing order if it already exists, making it idempotent. + ```java public class RequestService { // Idempotent: ensures that the same request is returned if it already exists @@ -79,8 +79,11 @@ public class RequestService { } } ``` + ### RequestStateMachine - Managing Order Transitions -The `RequestStateMachine` ensures that state transitions occur in a valid order. It handles the progression of an order's status, ensuring the correct sequence (e.g., from `PENDING` to `STARTED` to `COMPLETED`). + +The `RequestStateMachine` enforces valid state changes. If a requested transition is not allowed based on the current status, an exception is thrown. + ```java public class RequestStateMachine { @@ -105,9 +108,10 @@ public class RequestStateMachine { } } ``` + ### Main Application - Running the Idempotent Consumer Example -In the main application, we demonstrate how the `RequestService` can be used to perform idempotent operations. Whether the order creation or state transition is invoked once or multiple times, the result is consistent and does not produce unexpected side effects. +Here, we demonstrate how `RequestService` can be called multiple times without creating duplicate orders. We also show how invalid transitions (like trying to start an order twice) result in exceptions, while valid transitions proceed normally. ```java Request req = requestService.create(UUID.randomUUID()); @@ -130,32 +134,49 @@ req = requestService.complete(req.getUuid()); // Log the final status of the Request to confirm it's been completed LOGGER.info("Request: {}", req); ``` + Program output: + ``` 19:01:54.382 INFO [main] com.iluwatar.idempotentconsumer.App : Nb of requests : 1 19:01:54.395 ERROR [main] com.iluwatar.idempotentconsumer.App : Cannot start request twice! 19:01:54.399 INFO [main] com.iluwatar.idempotentconsumer.App : Request: Request(uuid=2d5521ef-6b6b-4003-9ade-81e381fe9a63, status=COMPLETED) ``` + +## When to Use the Idempotent Consumer Pattern + +* When messages can arrive more than once due to network glitches or retries +* When microservices must guarantee consistent state changes regardless of duplicates +* When fault-tolerant event-driven communication is critical to system reliability +* When horizontal scaling requires stateless consumer operations + +## Real-World Applications of Idempotent Consumer Pattern + +* Payment processing systems that receive duplicate charge events +* E-commerce order services that handle duplicate purchase requests +* Notification services that retry failed message deliveries +* Distributed transaction systems where duplicated events are common + ## Benefits and Trade-offs of the Idempotent Consumer Pattern -### Benefits +Benefits -* **Reliability**: Ensures that messages can be processed without unwanted side effects from duplicates. -* **Consistency**: Maintains data integrity by ensuring that duplicate messages do not cause redundant updates or actions. -* **Fault Tolerance**: Handles message retries gracefully, preventing them from causing errors. +* Prevents duplicate side effects +* Increases reliability under repeated or delayed messages +* Simplifies error handling and retry logic -### Trade-offs +Trade-offs -* **State Management**: Requires storing processed message IDs, which can add memory overhead. -* **Complexity**: Implementing deduplication mechanisms can increase the complexity of the system. -* **Scalability**: In high-throughput systems, maintaining a large set of processed messages can impact performance and resource usage. +* Requires careful design to track processed messages +* Can add overhead for maintaining idempotency tokens or state +* May require additional storage or database transactions ## Related Patterns in Java -* [Retry Pattern](https://java-design-patterns.com/patterns/retry/): Works well with the Idempotent Consumer pattern to handle failed messages. -* [Circuit Breaker Pattern](https://java-design-patterns.com/patterns/circuitbreaker/): Often used alongside idempotent consumers to prevent repeated failures from causing overload. +* Outbox Pattern: Uses a dedicated table or storage to reliably publish events and handle deduplication at the source. ## References and Credits +* [Building Microservices](https://amzn.to/3UACtrU) * [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/4dznP2Y) -* [Designing Data-Intensive Applications](https://amzn.to/3UADv7Q) +* [Microservices Patterns: With examples in Java](https://amzn.to/3UyWD5O) diff --git a/microservices-idempotent-consumer/etc/microservices-idempotent-consumer-flowchart.png b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer-flowchart.png new file mode 100644 index 000000000000..e5bd5d83ac8b Binary files /dev/null and b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer-flowchart.png differ diff --git a/microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml new file mode 100644 index 000000000000..43fe77181375 --- /dev/null +++ b/microservices-idempotent-consumer/etc/microservices-idempotent-consumer.urm.puml @@ -0,0 +1,49 @@ +@startuml +package com.iluwatar.idempotentconsumer { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + + run(requestService : RequestService, requestRepository : RequestRepository) : CommandLineRunner + } + class Request { + - status : Status + - uuid : UUID + + Request() + + Request(uuid : UUID) + + Request(uuid : UUID, status : Status) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getStatus() : Status + + getUuid() : UUID + + hashCode() : int + + setStatus(status : Status) + + setUuid(uuid : UUID) + + toString() : String + } + ~enum Status { + + COMPLETED {static} + + PENDING {static} + + STARTED {static} + + valueOf(name : String) : Status {static} + + values() : Status[] {static} + } + interface RequestRepository { + } + class RequestService { + ~ requestRepository : RequestRepository + ~ requestStateMachine : RequestStateMachine + + RequestService(requestRepository : RequestRepository, requestStateMachine : RequestStateMachine) + + complete(uuid : UUID) : Request + + create(uuid : UUID) : Request + + start(uuid : UUID) : Request + } + class RequestStateMachine { + + RequestStateMachine() + + next(req : Request, nextStatus : Status) : Request + } +} +RequestService --> "-requestRepository" RequestRepository +Request --> "-status" Status +RequestService --> "-requestStateMachine" RequestStateMachine +@enduml \ No newline at end of file diff --git a/microservices-idempotent-consumer/pom.xml b/microservices-idempotent-consumer/pom.xml index 453e4a1ec762..665be3abef56 100644 --- a/microservices-idempotent-consumer/pom.xml +++ b/microservices-idempotent-consumer/pom.xml @@ -36,22 +36,6 @@ microservices-idempotent-consumer - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.2.3 - import - - - org.hibernate - hibernate-core - 6.4.4.Final - - - @@ -89,6 +73,12 @@ runtime + + org.hibernate + hibernate-core + 6.4.4.Final + + diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/App.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/App.java index 4fcc2801673f..fe099eab415f 100644 --- a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/App.java +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/App.java @@ -32,13 +32,14 @@ import org.springframework.context.annotation.Bean; /** - * The main entry point for the idempotent-consumer application. - * This application demonstrates the use of the Idempotent Consumer - * pattern which ensures that a message is processed exactly once - * in scenarios where the same message can be delivered multiple times. + * The main entry point for the idempotent-consumer application. This application demonstrates the + * use of the Idempotent Consumer pattern which ensures that a message is processed exactly once in + * scenarios where the same message can be delivered multiple times. * * @see Idempotence (Wikipedia) - * @see Idempotent Consumer Pattern (Apache Camel) + * @see Idempotent + * Consumer Pattern (Apache Camel) */ @SpringBootApplication @Slf4j @@ -46,9 +47,9 @@ public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } + /** - * The starting point of the CommandLineRunner - * where the main program is run. + * The starting point of the CommandLineRunner where the main program is run. * * @param requestService idempotent request service * @param requestRepository request jpa repository @@ -59,7 +60,8 @@ public CommandLineRunner run(RequestService requestService, RequestRepository re Request req = requestService.create(UUID.randomUUID()); requestService.create(req.getUuid()); requestService.create(req.getUuid()); - LOGGER.info("Nb of requests : {}", requestRepository.count()); // 1, processRequest is idempotent + LOGGER.info( + "Nb of requests : {}", requestRepository.count()); // 1, processRequest is idempotent req = requestService.start(req.getUuid()); try { req = requestService.start(req.getUuid()); @@ -71,4 +73,3 @@ public CommandLineRunner run(RequestService requestService, RequestRepository re }; } } - diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/InvalidNextStateException.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/InvalidNextStateException.java index c29e913aa33f..e280d37a8a1f 100644 --- a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/InvalidNextStateException.java +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/InvalidNextStateException.java @@ -25,9 +25,9 @@ package com.iluwatar.idempotentconsumer; /** - * This exception is thrown when an invalid transition is attempted in the Statemachine - * for the request status. This can occur when attempting to move to a state that is not valid - * from the current state. + * This exception is thrown when an invalid transition is attempted in the Statemachine for the + * request status. This can occur when attempting to move to a state that is not valid from the + * current state. */ public class InvalidNextStateException extends RuntimeException { public InvalidNextStateException(String s) { diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/Request.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/Request.java index 29f5d6fba375..dfd68346bb7f 100644 --- a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/Request.java +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/Request.java @@ -31,8 +31,8 @@ import lombok.NoArgsConstructor; /** - * The {@code Request} class represents a request with a unique UUID and a status. - * The status of a request can be one of four values: PENDING, STARTED, COMPLETED, or INERROR. + * The {@code Request} class represents a request with a unique UUID and a status. The status of a + * request can be one of four values: PENDING, STARTED, COMPLETED, or INERROR. */ @Entity @NoArgsConstructor @@ -44,8 +44,7 @@ enum Status { COMPLETED } - @Id - private UUID uuid; + @Id private UUID uuid; private Status status; public Request(UUID uuid) { diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestNotFoundException.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestNotFoundException.java index 5294298d65f2..aedc94ef7517 100644 --- a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestNotFoundException.java +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestNotFoundException.java @@ -28,9 +28,8 @@ /** * This class extends the RuntimeException class to handle scenarios where a Request is not found. - * It is intended to be used where you would like to have a custom exception that signals that a requested object or action - * was not found in the system, based on the UUID of the request. - * + * It is intended to be used where you would like to have a custom exception that signals that a + * requested object or action was not found in the system, based on the UUID of the request. */ public class RequestNotFoundException extends RuntimeException { RequestNotFoundException(UUID uuid) { diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestRepository.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestRepository.java index 0d8d3744b3dd..e0b273e9fb5d 100644 --- a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestRepository.java +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestRepository.java @@ -29,12 +29,11 @@ import org.springframework.stereotype.Repository; /** - * This is a repository interface for the "Request" entity. It extends the JpaRepository interface from Spring Data JPA. - * JpaRepository comes with many operations out of the box, including standard CRUD operations. - * With JpaRepository, we are also able to leverage the power of Spring Data's query methods. - * The UUID parameter in JpaRepository refers to the type of the ID in the "Request" entity. - * + * This is a repository interface for the "Request" entity. It extends the JpaRepository interface + * from Spring Data JPA. JpaRepository comes with many operations out of the box, including standard + * CRUD operations. With JpaRepository, we are also able to leverage the power of Spring Data's + * query methods. The UUID parameter in JpaRepository refers to the type of the ID in the "Request" + * entity. */ @Repository -public interface RequestRepository extends JpaRepository { -} +public interface RequestRepository extends JpaRepository {} diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java index 066e52e66917..39d496120cc7 100644 --- a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestService.java @@ -29,34 +29,30 @@ import org.springframework.stereotype.Service; /** - * This service is responsible for handling request operations including - * creation, start, and completion of requests. + * This service is responsible for handling request operations including creation, start, and + * completion of requests. */ @Service public class RequestService { RequestRepository requestRepository; RequestStateMachine requestStateMachine; - public RequestService(RequestRepository requestRepository, - RequestStateMachine requestStateMachine) { + public RequestService( + RequestRepository requestRepository, RequestStateMachine requestStateMachine) { this.requestRepository = requestRepository; this.requestStateMachine = requestStateMachine; } /** - * Creates a new Request or returns an existing one by it's UUID. - * This operation is idempotent: performing it once or several times - * successively leads to an equivalent result. + * Creates a new Request or returns an existing one by its UUID. This operation is idempotent: + * performing it once or several times successively leads to an equivalent result. * * @param uuid The unique identifier for the Request. * @return Return existing Request or save and return a new Request. */ public Request create(UUID uuid) { Optional optReq = requestRepository.findById(uuid); - if (!optReq.isEmpty()) { - return optReq.get(); - } - return requestRepository.save(new Request(uuid)); + return optReq.orElseGet(() -> requestRepository.save(new Request(uuid))); } /** diff --git a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestStateMachine.java b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestStateMachine.java index 5861276ca033..a67affdf1253 100644 --- a/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestStateMachine.java +++ b/microservices-idempotent-consumer/src/main/java/com/iluwatar/idempotentconsumer/RequestStateMachine.java @@ -27,8 +27,8 @@ import org.springframework.stereotype.Component; /** - * This class represents a state machine for managing request transitions. - * It supports transitions to the statuses: PENDING, STARTED, and COMPLETED. + * This class represents a state machine for managing request transitions. It supports transitions + * to the statuses: PENDING, STARTED, and COMPLETED. */ @Component public class RequestStateMachine { @@ -36,8 +36,10 @@ public class RequestStateMachine { /** * Provides the next possible state of the request based on the current and next status. * - * @param req The actual request object. This object MUST NOT be null and SHOULD have a valid status. - * @param nextStatus Represents the next status that the request could transition to. MUST NOT be null. + * @param req The actual request object. This object MUST NOT be null and SHOULD have a valid + * status. + * @param nextStatus Represents the next status that the request could transition to. MUST NOT be + * null. * @return A new Request object with updated status if the transition is valid. * @throws InvalidNextStateException If an invalid state transition is attempted. */ diff --git a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java index e161b2edc30d..f6cd88e0f505 100644 --- a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java +++ b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/AppTest.java @@ -1,29 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.idempotentconsumer; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import org.springframework.boot.CommandLineRunner; - -import java.util.UUID; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -/** - * Application test - */ +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.boot.CommandLineRunner; + +/** Application test */ class AppTest { @Test - void main() { + void testMain() { assertDoesNotThrow(() -> App.main(new String[] {})); } @Test - void run() throws Exception { + void testRun() throws Exception { RequestService requestService = Mockito.mock(RequestService.class); RequestRepository requestRepository = Mockito.mock(RequestRepository.class); UUID uuid = UUID.randomUUID(); @@ -43,4 +64,4 @@ void run() throws Exception { verify(requestService, times(1)).complete(any()); verify(requestRepository, times(1)).count(); } -} \ No newline at end of file +} diff --git a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java index 1d5eebd34bcd..20470d5151c7 100644 --- a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java +++ b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestServiceTests.java @@ -42,12 +42,11 @@ @ExtendWith(MockitoExtension.class) class RequestServiceTests { private RequestService requestService; - @Mock - private RequestRepository requestRepository; - private RequestStateMachine requestStateMachine; + @Mock private RequestRepository requestRepository; + @BeforeEach void setUp() { - requestStateMachine = new RequestStateMachine(); + RequestStateMachine requestStateMachine = new RequestStateMachine(); requestService = new RequestService(requestRepository, requestStateMachine); } @@ -61,6 +60,7 @@ void createRequest_whenNotExists() { verify(requestRepository, times(1)).findById(uuid); verify(requestRepository, times(1)).save(any()); } + @Test void createRequest_whenExists() { UUID uuid = UUID.randomUUID(); @@ -75,7 +75,7 @@ void createRequest_whenExists() { void startRequest_whenNotExists_shouldThrowError() { UUID uuid = UUID.randomUUID(); when(requestRepository.findById(any())).thenReturn(Optional.empty()); - assertThrows(RequestNotFoundException.class, ()->requestService.start(uuid)); + assertThrows(RequestNotFoundException.class, () -> requestService.start(uuid)); verify(requestRepository, times(1)).findById(uuid); verify(requestRepository, times(0)).save(any()); } @@ -97,7 +97,7 @@ void startRequest_whenIsStarted_shouldThrowError() { UUID uuid = UUID.randomUUID(); Request requestStarted = new Request(uuid, Request.Status.STARTED); when(requestRepository.findById(any())).thenReturn(Optional.of(requestStarted)); - assertThrows(InvalidNextStateException.class, ()->requestService.start(uuid)); + assertThrows(InvalidNextStateException.class, () -> requestService.start(uuid)); verify(requestRepository, times(1)).findById(uuid); verify(requestRepository, times(0)).save(any()); } @@ -107,7 +107,7 @@ void startRequest_whenIsCompleted_shouldThrowError() { UUID uuid = UUID.randomUUID(); Request requestStarted = new Request(uuid, Request.Status.COMPLETED); when(requestRepository.findById(any())).thenReturn(Optional.of(requestStarted)); - assertThrows(InvalidNextStateException.class, ()->requestService.start(uuid)); + assertThrows(InvalidNextStateException.class, () -> requestService.start(uuid)); verify(requestRepository, times(1)).findById(uuid); verify(requestRepository, times(0)).save(any()); } @@ -123,6 +123,7 @@ void completeRequest_whenStarted() { verify(requestRepository, times(1)).findById(uuid); verify(requestRepository, times(1)).save(completedEntity); } + @Test void completeRequest_whenNotInprogress() { UUID uuid = UUID.randomUUID(); @@ -132,4 +133,4 @@ void completeRequest_whenNotInprogress() { verify(requestRepository, times(1)).findById(uuid); verify(requestRepository, times(0)).save(any()); } -} \ No newline at end of file +} diff --git a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java index af779648c8a3..d160814a00ed 100644 --- a/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java +++ b/microservices-idempotent-consumer/src/test/java/com/iluwatar/idempotentconsumer/RequestStateMachineTests.java @@ -1,59 +1,96 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package com.iluwatar.idempotentconsumer; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import java.util.UUID; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class RequestStateMachineTests { private RequestStateMachine requestStateMachine; @BeforeEach - public void setUp() { + void setUp() { requestStateMachine = new RequestStateMachine(); } @Test void transitionPendingToStarted() { - Request startedRequest = requestStateMachine.next(new Request(UUID.randomUUID(), Request.Status.PENDING), - Request.Status.STARTED); + Request startedRequest = + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.PENDING), Request.Status.STARTED); assertEquals(Request.Status.STARTED, startedRequest.getStatus()); } @Test void transitionAnyToPending_shouldThrowError() { - assertThrows(InvalidNextStateException.class, - () -> requestStateMachine.next(new Request(UUID.randomUUID(), Request.Status.PENDING), - Request.Status.PENDING)); - assertThrows(InvalidNextStateException.class, - () -> requestStateMachine.next(new Request(UUID.randomUUID(), Request.Status.STARTED), - Request.Status.PENDING)); - assertThrows(InvalidNextStateException.class, - () -> requestStateMachine.next(new Request(UUID.randomUUID(), Request.Status.COMPLETED), - Request.Status.PENDING)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.PENDING), Request.Status.PENDING)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.STARTED), Request.Status.PENDING)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.COMPLETED), Request.Status.PENDING)); } @Test void transitionCompletedToAny_shouldThrowError() { - assertThrows(InvalidNextStateException.class, - () -> requestStateMachine.next(new Request(UUID.randomUUID(), Request.Status.COMPLETED), - Request.Status.PENDING)); - assertThrows(InvalidNextStateException.class, - () -> requestStateMachine.next(new Request(UUID.randomUUID(), Request.Status.COMPLETED), - Request.Status.STARTED)); - assertThrows(InvalidNextStateException.class, - () -> requestStateMachine.next(new Request(UUID.randomUUID(), Request.Status.COMPLETED), - Request.Status.COMPLETED)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.COMPLETED), Request.Status.PENDING)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.COMPLETED), Request.Status.STARTED)); + assertThrows( + InvalidNextStateException.class, + () -> + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.COMPLETED), + Request.Status.COMPLETED)); } @Test void transitionStartedToCompleted() { - Request completedRequest = requestStateMachine.next(new Request(UUID.randomUUID(), Request.Status.STARTED), - Request.Status.COMPLETED); + Request completedRequest = + requestStateMachine.next( + new Request(UUID.randomUUID(), Request.Status.STARTED), Request.Status.COMPLETED); assertEquals(Request.Status.COMPLETED, completedRequest.getStatus()); } - - -} \ No newline at end of file +} diff --git a/microservices-log-aggregation/README.md b/microservices-log-aggregation/README.md index c23b1fe4319b..2fcef9b70cf2 100644 --- a/microservices-log-aggregation/README.md +++ b/microservices-log-aggregation/README.md @@ -38,6 +38,10 @@ Wikipedia says > You have applied the Microservice architecture pattern. The application consists of multiple services and service instances that are running on multiple machines. Requests often span multiple service instances. Each service instance generates writes information about what it is doing to a log file in a standardized format. The log file contains errors, warnings, information and debug messages. +Flowchart + +![Microservices Log Aggregration flowchart](./etc/microservices-log-aggregation-flowchart.png) + ## Programmatic Example of Microservices Log Aggregation Pattern in Java Log Aggregation is a pattern that centralizes the collection, storage, and analysis of logs from multiple sources to facilitate monitoring, debugging, and operational intelligence. It is particularly useful in distributed systems where logs from various components need to be centralized for better management and analysis. diff --git a/microservices-log-aggregation/etc/microservices-log-aggregation-flowchart.png b/microservices-log-aggregation/etc/microservices-log-aggregation-flowchart.png new file mode 100644 index 000000000000..a73f948dd855 Binary files /dev/null and b/microservices-log-aggregation/etc/microservices-log-aggregation-flowchart.png differ diff --git a/microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml b/microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml new file mode 100644 index 000000000000..1d4551ed025f --- /dev/null +++ b/microservices-log-aggregation/etc/microservices-log-aggregation.urm.puml @@ -0,0 +1,68 @@ +@startuml +package com.iluwatar.logaggregation { + class App { + + App() + + main(args : String[]) {static} + } + class CentralLogStore { + - LOGGER : Logger {static} + - logs : ConcurrentLinkedQueue + + CentralLogStore() + + displayLogs() + + storeLog(logEntry : LogEntry) + } + class LogAggregator { + - BUFFER_THRESHOLD : int {static} + - LOGGER : Logger {static} + - buffer : ConcurrentLinkedQueue + - centralLogStore : CentralLogStore + - executorService : ExecutorService + - logCount : AtomicInteger + - minLogLevel : LogLevel + + LogAggregator(centralLogStore : CentralLogStore, minLogLevel : LogLevel) + + collectLog(logEntry : LogEntry) + - flushBuffer() + - startBufferFlusher() + + stop() + } + class LogEntry { + - level : LogLevel + - message : String + - serviceName : String + - timestamp : LocalDateTime + + LogEntry(serviceName : String, level : LogLevel, message : String, timestamp : LocalDateTime) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getLevel() : LogLevel + + getMessage() : String + + getServiceName() : String + + getTimestamp() : LocalDateTime + + hashCode() : int + + setLevel(level : LogLevel) + + setMessage(message : String) + + setServiceName(serviceName : String) + + setTimestamp(timestamp : LocalDateTime) + + toString() : String + } + enum LogLevel { + + DEBUG {static} + + ERROR {static} + + INFO {static} + + valueOf(name : String) : LogLevel {static} + + values() : LogLevel[] {static} + } + class LogProducer { + - LOGGER : Logger {static} + - aggregator : LogAggregator + - serviceName : String + + LogProducer(serviceName : String, aggregator : LogAggregator) + + generateLog(level : LogLevel, message : String) + } +} +LogAggregator --> "-centralLogStore" CentralLogStore +LogEntry --> "-level" LogLevel +CentralLogStore --> "-logs" LogEntry +LogAggregator --> "-buffer" LogEntry +LogAggregator --> "-minLogLevel" LogLevel +LogProducer --> "-aggregator" LogAggregator +@enduml \ No newline at end of file diff --git a/microservices-log-aggregation/pom.xml b/microservices-log-aggregation/pom.xml index b10760e37750..fd5548661d3d 100644 --- a/microservices-log-aggregation/pom.xml +++ b/microservices-log-aggregation/pom.xml @@ -38,6 +38,14 @@ microservices-log-aggregation + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -46,13 +54,27 @@ org.mockito mockito-junit-jupiter + 5.16.1 test - - 17 - 17 - UTF-8 - - + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.logaggregation.App + + + + + + + + diff --git a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java index 32727916188a..3f525fa9e65f 100644 --- a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/CentralLogStore.java @@ -28,9 +28,9 @@ import lombok.extern.slf4j.Slf4j; /** - * A centralized store for logs. It collects logs from various services and stores them. - * This class is thread-safe, ensuring that logs from different services are safely stored - * concurrently without data races. + * A centralized store for logs. It collects logs from various services and stores them. This class + * is thread-safe, ensuring that logs from different services are safely stored concurrently without + * data races. */ @Slf4j public class CentralLogStore { @@ -50,9 +50,7 @@ public void storeLog(LogEntry logEntry) { logs.offer(logEntry); } - /** - * Displays all logs currently stored in the central log store. - */ + /** Displays all logs currently stored in the central log store. */ public void displayLogs() { LOGGER.info("----- Centralized Logs -----"); for (LogEntry logEntry : logs) { diff --git a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java index 37417e21267d..0acdc9fedc62 100644 --- a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogAggregator.java @@ -32,11 +32,10 @@ import lombok.extern.slf4j.Slf4j; /** - * Responsible for collecting and buffering logs from different services. - * Once the logs reach a certain threshold or after a certain time interval, - * they are flushed to the central log store. This class ensures logs are collected - * and processed asynchronously and efficiently, providing both an immediate collection - * and periodic flushing. + * Responsible for collecting and buffering logs from different services. Once the logs reach a + * certain threshold or after a certain time interval, they are flushed to the central log store. + * This class ensures logs are collected and processed asynchronously and efficiently, providing + * both an immediate collection and periodic flushing. */ @Slf4j public class LogAggregator { @@ -84,8 +83,7 @@ public void collectLog(LogEntry logEntry) { } /** - * Stops the log aggregator service and flushes any remaining logs to - * the central log store. + * Stops the log aggregator service and flushes any remaining logs to the central log store. * * @throws InterruptedException If any thread has interrupted the current thread. */ @@ -106,15 +104,16 @@ private void flushBuffer() { } private void startBufferFlusher() { - executorService.execute(() -> { - while (!Thread.currentThread().isInterrupted()) { - try { - Thread.sleep(5000); // Flush every 5 seconds. - flushBuffer(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - }); + executorService.execute( + () -> { + while (!Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(5000); // Flush every 5 seconds. + flushBuffer(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }); } } diff --git a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java index 1dd467293b37..93fe30d7c246 100644 --- a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogEntry.java @@ -29,8 +29,8 @@ import lombok.Data; /** - * Represents a single log entry, capturing essential details like the service name, - * log level, message, and the timestamp when the log was generated. + * Represents a single log entry, capturing essential details like the service name, log level, + * message, and the timestamp when the log was generated. */ @Data @AllArgsConstructor diff --git a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java index 8d90dbe9502f..da6c69afb179 100644 --- a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogLevel.java @@ -25,14 +25,17 @@ package com.iluwatar.logaggregation; /** - * Enum representing different log levels. - * Defines the severity of a log message, helping in filtering and prioritization. + * Enum representing different log levels. Defines the severity of a log message, helping in + * filtering and prioritization. + * *

      - *
    • DEBUG: Detailed information, typically of interest only when diagnosing problems.
    • - *
    • INFO: Confirmation that things are working as expected.
    • - *
    • ERROR: Indicates a problem that needs attention.
    • + *
    • DEBUG: Detailed information, typically of interest only when diagnosing problems. + *
    • INFO: Confirmation that things are working as expected. + *
    • ERROR: Indicates a problem that needs attention. *
    */ public enum LogLevel { - DEBUG, INFO, ERROR + DEBUG, + INFO, + ERROR } diff --git a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java index a586588bc6d1..a1d3f4c71ad9 100644 --- a/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java +++ b/microservices-log-aggregation/src/main/java/com/iluwatar/logaggregation/LogProducer.java @@ -29,9 +29,9 @@ import lombok.extern.slf4j.Slf4j; /** - * Represents a service that produces logs. - * The logs are generated based on certain activities or events within the service. - * Once a log is generated, it's passed on to the aggregator for further processing. + * Represents a service that produces logs. The logs are generated based on certain activities or + * events within the service. Once a log is generated, it's passed on to the aggregator for further + * processing. */ @AllArgsConstructor @Slf4j diff --git a/microservices-log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java b/microservices-log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java index 6c385f35c6c3..219bb4c48fad 100644 --- a/microservices-log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java +++ b/microservices-log-aggregation/src/test/java/com/iluwatar/logaggregation/LogAggregatorTest.java @@ -38,8 +38,7 @@ @ExtendWith(MockitoExtension.class) class LogAggregatorTest { - @Mock - private CentralLogStore centralLogStore; + @Mock private CentralLogStore centralLogStore; private LogAggregator logAggregator; @BeforeEach diff --git a/model-view-controller/README.md b/model-view-controller/README.md index 42ef81fc16af..536cb2ca4a2a 100644 --- a/model-view-controller/README.md +++ b/model-view-controller/README.md @@ -34,6 +34,11 @@ Wikipedia says > Model–view–controller (MVC) is commonly used for developing user interfaces that divide the related program logic into three interconnected elements. This is done to separate internal representations of information from the ways information is presented to and accepted from the user. +Architecture diagram + +![Model-View-Controller Architecture Diagram](./etc/mvc-architecture-diagram.png) + + ## Programmatic Example of Model-View-Controller Pattern in Java Consider following `GiantModel` model class that provides the health, fatigue & nourishment information. diff --git a/model-view-controller/etc/mvc-architecture-diagram.png b/model-view-controller/etc/mvc-architecture-diagram.png new file mode 100644 index 000000000000..33371d41f22c Binary files /dev/null and b/model-view-controller/etc/mvc-architecture-diagram.png differ diff --git a/model-view-controller/pom.xml b/model-view-controller/pom.xml index 78072af4455e..807596459140 100644 --- a/model-view-controller/pom.xml +++ b/model-view-controller/pom.xml @@ -34,6 +34,14 @@ model-view-controller + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java index 00cced2407a2..a4a3c69cbb4f 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Fatigue.java @@ -26,9 +26,7 @@ import lombok.AllArgsConstructor; -/** - * Fatigue enumeration. - */ +/** Fatigue enumeration. */ @AllArgsConstructor public enum Fatigue { ALERT("alert"), @@ -37,7 +35,6 @@ public enum Fatigue { private final String title; - @Override public String toString() { return title; diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java index 35881fbff83d..5fb8f1acab14 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantController.java @@ -24,9 +24,7 @@ */ package com.iluwatar.model.view.controller; -/** - * GiantController can update the giant data and redraw it using the view. - */ +/** GiantController can update the giant data and redraw it using the view. */ public class GiantController { private final GiantModel giant; diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantModel.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantModel.java index 3de47cdcba6d..8be7e6e4e47c 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantModel.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantModel.java @@ -30,9 +30,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; -/** - * GiantModel contains the giant data. - */ +/** GiantModel contains the giant data. */ @Getter @Setter @Builder @@ -44,7 +42,6 @@ public class GiantModel { private Fatigue fatigue; private Nourishment nourishment; - @Override public String toString() { return String.format("The giant looks %s, %s and %s.", health, fatigue, nourishment); diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantView.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantView.java index b73204d1dd38..a9696d898c78 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantView.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/GiantView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * GiantView displays the giant. - */ +/** GiantView displays the giant. */ @Slf4j public class GiantView { diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java index 0f54f5cd3825..7fba2f54f43e 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Health.java @@ -26,9 +26,7 @@ import lombok.AllArgsConstructor; -/** - * Health enumeration. - */ +/** Health enumeration. */ @AllArgsConstructor public enum Health { HEALTHY("healthy"), @@ -37,7 +35,6 @@ public enum Health { private final String title; - @Override public String toString() { return title; diff --git a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java index f08a9c5bced3..d385588bbd2d 100644 --- a/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java +++ b/model-view-controller/src/main/java/com/iluwatar/model/view/controller/Nourishment.java @@ -26,9 +26,7 @@ import lombok.AllArgsConstructor; -/** - * Nourishment enumeration. - */ +/** Nourishment enumeration. */ @AllArgsConstructor public enum Nourishment { SATURATED("saturated"), @@ -37,7 +35,6 @@ public enum Nourishment { private final String title; - @Override public String toString() { return title; diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java index 8b44e47d2f38..b435b393061d 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.model.view.controller; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java index 1cacb52c3fa6..9dc9f2807e74 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantControllerTest.java @@ -30,15 +30,10 @@ import org.junit.jupiter.api.Test; -/** - * GiantControllerTest - * - */ +/** GiantControllerTest */ class GiantControllerTest { - /** - * Verify if the controller passes the health level through to the model and vice versa - */ + /** Verify if the controller passes the health level through to the model and vice versa */ @Test void testSetHealth() { final var model = mock(GiantModel.class); @@ -60,9 +55,7 @@ void testSetHealth() { verifyNoMoreInteractions(model, view); } - /** - * Verify if the controller passes the fatigue level through to the model and vice versa - */ + /** Verify if the controller passes the fatigue level through to the model and vice versa */ @Test void testSetFatigue() { final var model = mock(GiantModel.class); @@ -84,9 +77,7 @@ void testSetFatigue() { verifyNoMoreInteractions(model, view); } - /** - * Verify if the controller passes the nourishment level through to the model and vice versa - */ + /** Verify if the controller passes the nourishment level through to the model and vice versa */ @Test void testSetNourishment() { final var model = mock(GiantModel.class); @@ -121,5 +112,4 @@ void testUpdateView() { verifyNoMoreInteractions(model, view); } - -} \ No newline at end of file +} diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java index f87a71267fd8..ed9eefc4ea62 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantModelTest.java @@ -28,15 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * GiantModelTest - * - */ +/** GiantModelTest */ class GiantModelTest { - /** - * Verify if the health value is set properly though the constructor and setter - */ + /** Verify if the health value is set properly though the constructor and setter */ @Test void testSetHealth() { final var model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); @@ -49,9 +44,7 @@ void testSetHealth() { } } - /** - * Verify if the fatigue level is set properly though the constructor and setter - */ + /** Verify if the fatigue level is set properly though the constructor and setter */ @Test void testSetFatigue() { final var model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); @@ -64,9 +57,7 @@ void testSetFatigue() { } } - /** - * Verify if the nourishment level is set properly though the constructor and setter - */ + /** Verify if the nourishment level is set properly though the constructor and setter */ @Test void testSetNourishment() { final var model = new GiantModel(Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); @@ -78,5 +69,4 @@ void testSetNourishment() { assertEquals(String.format(messageFormat, nourishment), model.toString()); } } - } diff --git a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java index b38452b61310..da2032b7fd94 100644 --- a/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java +++ b/model-view-controller/src/test/java/com/iluwatar/model/view/controller/GiantViewTest.java @@ -37,10 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * GiantViewTest - * - */ +/** GiantViewTest */ class GiantViewTest { private InMemoryAppender appender; @@ -70,9 +67,7 @@ void testDisplayGiant() { assertEquals(1, appender.getLogSize()); } - /** - * Logging Appender Implementation - */ + /** Logging Appender Implementation */ public static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/model-view-intent/README.md b/model-view-intent/README.md index 659bf1b95a1e..1febe76b0327 100644 --- a/model-view-intent/README.md +++ b/model-view-intent/README.md @@ -37,6 +37,11 @@ In plain words > The Model-View-Intent (MVI) pattern is a reactive architectural approach where user actions (Intent) modify the application state (Model), and the updated state is then reflected back in the user interface (View) in a unidirectional and cyclical data flow. +Architecture diagram + +![Model-View-Intent Architecture Diagram](./etc/mvi-architecture-diagram.png) + + ## Programmatic Example of Model-View-Intent Pattern in Java The Model-View-Intent (MVI) pattern for Java is a modern approach to structuring your application's logic, ensuring a smooth, unidirectional flow of data and events. It's a variation of the Model-View-Presenter (MVP) and Model-View-ViewModel (MVVM) patterns, but with a more streamlined flow of data and events. diff --git a/model-view-intent/etc/mvi-architecture-diagram.png b/model-view-intent/etc/mvi-architecture-diagram.png new file mode 100644 index 000000000000..a0672ddc051f Binary files /dev/null and b/model-view-intent/etc/mvi-architecture-diagram.png differ diff --git a/model-view-intent/pom.xml b/model-view-intent/pom.xml index 9ca57f8819af..048acd19952f 100644 --- a/model-view-intent/pom.xml +++ b/model-view-intent/pom.xml @@ -34,6 +34,14 @@ model-view-intent + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/App.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/App.java index 7d17f71049dd..4bb699cf2834 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/App.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/App.java @@ -25,23 +25,17 @@ package com.iluwatar.model.view.intent; /** - * Model-View-Intent is a pattern for implementing user interfaces. - * Its main advantage over MVVM which it closely mirrors is a - * minimal public api with which user events can be exposed to the ViewModel. - * In case of the MVI every event is exposed by using a single method - * with 1 argument which implements UserEvent interface. - * Specific parameters can be expressed as its parameters. In this case, - * we'll be using MVI to implement a simple calculator - * with +, -, /, * operations and the ability to set the variable. - * It's important to note, that every user action happens through the + * Model-View-Intent is a pattern for implementing user interfaces. Its main advantage over MVVM + * which it closely mirrors is a minimal public api with which user events can be exposed to the + * ViewModel. In case of the MVI every event is exposed by using a single method with 1 argument + * which implements UserEvent interface. Specific parameters can be expressed as its parameters. In + * this case, we'll be using MVI to implement a simple calculator with +, -, /, * operations and the + * ability to set the variable. It's important to note, that every user action happens through the * view, we never interact with the ViewModel directly. */ public final class App { - - /** - * To avoid magic value lint error. - */ + /** To avoid magic value lint error. */ private static final double RANDOM_VARIABLE = 10.0; /** @@ -61,10 +55,10 @@ public static void main(final String[] args) { // add calculator variable to output -> calculator output = 10.0 view.add(); - view.displayTotal(); // display output + view.displayTotal(); // display output variable1 = 2.0; - view.setVariable(variable1); // calculator variable = 2.0 + view.setVariable(variable1); // calculator variable = 2.0 // subtract calculator variable from output -> calculator output = 8 view.subtract(); @@ -77,9 +71,6 @@ public static void main(final String[] args) { view.displayTotal(); } - /** - * Avoid default constructor lint error. - */ - private App() { - } + /** Avoid default constructor lint error. */ + private App() {} } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorModel.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorModel.java index 4d513ebe7418..3ef9c9934248 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorModel.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorModel.java @@ -27,21 +27,13 @@ import lombok.Data; import lombok.Getter; -/** - * Current state of calculator. - */ +/** Current state of calculator. */ @Data public class CalculatorModel { - /** - * Current calculator variable used for operations. - **/ - @Getter - private final Double variable; + /** Current calculator variable used for operations. */ + @Getter private final Double variable; - /** - * Current calculator output -> is affected by operations. - **/ - @Getter - private final Double output; + /** Current calculator output -> is affected by operations. */ + @Getter private final Double output; } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorView.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorView.java index 13de1cbb4823..a5ceb587db6e 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorView.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorView.java @@ -34,55 +34,38 @@ import lombok.extern.slf4j.Slf4j; /** - * Exposes changes to the state of calculator - * to {@link CalculatorViewModel} through - * {@link com.iluwatar.model.view.intent.actions.CalculatorAction} - * and displays its updated {@link CalculatorModel}. + * Exposes changes to the state of calculator to {@link CalculatorViewModel} through {@link + * com.iluwatar.model.view.intent.actions.CalculatorAction} and displays its updated {@link + * CalculatorModel}. */ @Slf4j @Data public class CalculatorView { - /** - * View model param handling the operations. - */ - @Getter - private final CalculatorViewModel viewModel; + /** View model param handling the operations. */ + @Getter private final CalculatorViewModel viewModel; - /** - * Display current view model output with logger. - */ + /** Display current view model output with logger. */ void displayTotal() { - LOGGER.info( - "Total value = {}", - viewModel.getCalculatorModel().getOutput().toString() - ); + LOGGER.info("Total value = {}", viewModel.getCalculatorModel().getOutput().toString()); } - /** - * Handle addition action. - */ + /** Handle addition action. */ void add() { viewModel.handleAction(new AdditionCalculatorAction()); } - /** - * Handle subtraction action. - */ + /** Handle subtraction action. */ void subtract() { viewModel.handleAction(new SubtractionCalculatorAction()); } - /** - * Handle multiplication action. - */ + /** Handle multiplication action. */ void multiply() { viewModel.handleAction(new MultiplicationCalculatorAction()); } - /** - * Handle division action. - */ + /** Handle division action. */ void divide() { viewModel.handleAction(new DivisionCalculatorAction()); } diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorViewModel.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorViewModel.java index fc5d182ade29..9c1fee801720 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorViewModel.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/CalculatorViewModel.java @@ -32,16 +32,12 @@ import com.iluwatar.model.view.intent.actions.SubtractionCalculatorAction; /** - * Handle transformations to {@link CalculatorModel} - * based on intercepted {@link CalculatorAction}. + * Handle transformations to {@link CalculatorModel} based on intercepted {@link CalculatorAction}. */ public final class CalculatorViewModel { - /** - * Current calculator model (can be changed). - */ - private CalculatorModel model = - new CalculatorModel(0.0, 0.0); + /** Current calculator model (can be changed). */ + private CalculatorModel model = new CalculatorModel(0.0, 0.0); /** * Handle calculator action. @@ -55,8 +51,7 @@ void handleAction(final CalculatorAction action) { case MultiplicationCalculatorAction.MULTIPLICATION -> multiply(); case DivisionCalculatorAction.DIVISION -> divide(); case SetVariableCalculatorAction.SET_VARIABLE -> { - SetVariableCalculatorAction setVariableAction = - (SetVariableCalculatorAction) action; + SetVariableCalculatorAction setVariableAction = (SetVariableCalculatorAction) action; setVariable(setVariableAction.getVariable()); } default -> throw new IllegalArgumentException("Unknown tag"); @@ -78,49 +73,26 @@ public CalculatorModel getCalculatorModel() { * @param variable -> value of new calculator model variable. */ private void setVariable(final Double variable) { - model = new CalculatorModel( - variable, - model.getOutput() - ); + model = new CalculatorModel(variable, model.getOutput()); } - /** - * Add variable to model output. - */ + /** Add variable to model output. */ private void add() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() + model.getVariable() - ); + model = new CalculatorModel(model.getVariable(), model.getOutput() + model.getVariable()); } - /** - * Subtract variable from model output. - */ + /** Subtract variable from model output. */ private void subtract() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() - model.getVariable() - ); + model = new CalculatorModel(model.getVariable(), model.getOutput() - model.getVariable()); } - /** - * Multiply model output by variable. - */ + /** Multiply model output by variable. */ private void multiply() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() * model.getVariable() - ); + model = new CalculatorModel(model.getVariable(), model.getOutput() * model.getVariable()); } - /** - * Divide model output by variable. - */ + /** Divide model output by variable. */ private void divide() { - model = new CalculatorModel( - model.getVariable(), - model.getOutput() / model.getVariable() - ); + model = new CalculatorModel(model.getVariable(), model.getOutput() / model.getVariable()); } -} \ No newline at end of file +} diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/AdditionCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/AdditionCalculatorAction.java index a93308e7fddb..21aca2a54eed 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/AdditionCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/AdditionCalculatorAction.java @@ -24,20 +24,14 @@ */ package com.iluwatar.model.view.intent.actions; -/** - * Addition {@link CalculatorAction}. - * */ +/** Addition {@link CalculatorAction}. */ public class AdditionCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - * */ + /** Subclass tag. */ public static final String ADDITION = "ADDITION"; - /** - * Makes checking subclass type trivial. - * */ + /** Makes checking subclass type trivial. */ @Override public String tag() { return ADDITION; } -} \ No newline at end of file +} diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/CalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/CalculatorAction.java index 18b7c8463d0a..b9566dc53ff5 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/CalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/CalculatorAction.java @@ -24,16 +24,13 @@ */ package com.iluwatar.model.view.intent.actions; -/** - * Defines what outside interactions can be consumed by view model. - * */ +/** Defines what outside interactions can be consumed by view model. */ public interface CalculatorAction { /** * Makes identifying action trivial. * * @return subclass tag. - * */ + */ String tag(); } - diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/DivisionCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/DivisionCalculatorAction.java index 7622e4f2fbc9..28e2ad362cec 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/DivisionCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/DivisionCalculatorAction.java @@ -24,20 +24,14 @@ */ package com.iluwatar.model.view.intent.actions; -/** - * Division {@link CalculatorAction}. - * */ +/** Division {@link CalculatorAction}. */ public class DivisionCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - * */ + /** Subclass tag. */ public static final String DIVISION = "DIVISION"; - /** - * Makes checking subclass type trivial. - * */ + /** Makes checking subclass type trivial. */ @Override public String tag() { return DIVISION; } -} \ No newline at end of file +} diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/MultiplicationCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/MultiplicationCalculatorAction.java index a163bd5d6e0a..9c5bcd044049 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/MultiplicationCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/MultiplicationCalculatorAction.java @@ -24,20 +24,14 @@ */ package com.iluwatar.model.view.intent.actions; -/** - * Multiplication {@link CalculatorAction}. - * */ +/** Multiplication {@link CalculatorAction}. */ public class MultiplicationCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - * */ + /** Subclass tag. */ public static final String MULTIPLICATION = "MULTIPLICATION"; - /** - * Makes checking subclass type trivial. - * */ + /** Makes checking subclass type trivial. */ @Override public String tag() { return MULTIPLICATION; } -} \ No newline at end of file +} diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SetVariableCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SetVariableCalculatorAction.java index c178d54002da..ce438e22a5d1 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SetVariableCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SetVariableCalculatorAction.java @@ -27,28 +27,19 @@ import lombok.Data; import lombok.Getter; -/** - * SetVariable {@link CalculatorAction}. - */ +/** SetVariable {@link CalculatorAction}. */ @Data public final class SetVariableCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - */ + /** Subclass tag. */ public static final String SET_VARIABLE = "SET_VARIABLE"; - /** - * Used by {@link com.iluwatar.model.view.intent.CalculatorViewModel}. - */ - @Getter - private final Double variable; + /** Used by {@link com.iluwatar.model.view.intent.CalculatorViewModel}. */ + @Getter private final Double variable; - /** - * Makes checking subclass type trivial. - */ + /** Makes checking subclass type trivial. */ @Override public String tag() { return SET_VARIABLE; } -} \ No newline at end of file +} diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SubtractionCalculatorAction.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SubtractionCalculatorAction.java index 68c576ed1b48..4a20d32a1fe4 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SubtractionCalculatorAction.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/SubtractionCalculatorAction.java @@ -24,20 +24,14 @@ */ package com.iluwatar.model.view.intent.actions; -/** - * Subtraction {@link CalculatorAction}. - * */ +/** Subtraction {@link CalculatorAction}. */ public class SubtractionCalculatorAction implements CalculatorAction { - /** - * Subclass tag. - * */ + /** Subclass tag. */ public static final String SUBTRACTION = "SUBTRACTION"; - /** - * Makes checking subclass type trivial. - * */ + /** Makes checking subclass type trivial. */ @Override public String tag() { return SUBTRACTION; } -} \ No newline at end of file +} diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/package-info.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/package-info.java index 1e0b6da49a62..062fc309dda2 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/package-info.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/actions/package-info.java @@ -23,8 +23,7 @@ * THE SOFTWARE. */ /** - * Handle actions for {@link com.iluwatar.model.view.intent.CalculatorModel} - * defined by {@link com.iluwatar.model.view.intent.actions.CalculatorAction}. + * Handle actions for {@link com.iluwatar.model.view.intent.CalculatorModel} defined by {@link + * com.iluwatar.model.view.intent.actions.CalculatorAction}. */ - package com.iluwatar.model.view.intent.actions; diff --git a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/package-info.java b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/package-info.java index 7b00c1d1a42e..654eb228b704 100644 --- a/model-view-intent/src/main/java/com/iluwatar/model/view/intent/package-info.java +++ b/model-view-intent/src/main/java/com/iluwatar/model/view/intent/package-info.java @@ -22,9 +22,5 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -/** - * Define Model, View and ViewModel. - * Use them in {@link com.iluwatar.model.view.intent.App} - */ - +/** Define Model, View and ViewModel. Use them in {@link com.iluwatar.model.view.intent.App} */ package com.iluwatar.model.view.intent; diff --git a/model-view-intent/src/test/java/com/iluwatar/model/view/intent/AppTest.java b/model-view-intent/src/test/java/com/iluwatar/model/view/intent/AppTest.java index 6adc84c64c2b..b28ad3cabf31 100644 --- a/model-view-intent/src/test/java/com/iluwatar/model/view/intent/AppTest.java +++ b/model-view-intent/src/test/java/com/iluwatar/model/view/intent/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.model.view.intent; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/model-view-intent/src/test/java/com/iluwatar/model/view/intent/CalculatorViewModelTest.java b/model-view-intent/src/test/java/com/iluwatar/model/view/intent/CalculatorViewModelTest.java index 453def145e6e..492c965fe079 100644 --- a/model-view-intent/src/test/java/com/iluwatar/model/view/intent/CalculatorViewModelTest.java +++ b/model-view-intent/src/test/java/com/iluwatar/model/view/intent/CalculatorViewModelTest.java @@ -24,12 +24,12 @@ */ package com.iluwatar.model.view.intent; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.iluwatar.model.view.intent.actions.*; -import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; class CalculatorViewModelTest { @@ -50,9 +50,7 @@ void testSetup() { @Test void testSetVariable() { - List actions = List.of( - new SetVariableCalculatorAction(10.0) - ); + List actions = List.of(new SetVariableCalculatorAction(10.0)); CalculatorModel model = modelAfterExecutingActions(actions); assertEquals(10.0, model.getVariable()); assertEquals(0, model.getOutput()); @@ -60,13 +58,13 @@ void testSetVariable() { @Test void testAddition() { - List actions = List.of( - new SetVariableCalculatorAction(2.0), - new AdditionCalculatorAction(), - new AdditionCalculatorAction(), - new SetVariableCalculatorAction(7.0), - new AdditionCalculatorAction() - ); + List actions = + List.of( + new SetVariableCalculatorAction(2.0), + new AdditionCalculatorAction(), + new AdditionCalculatorAction(), + new SetVariableCalculatorAction(7.0), + new AdditionCalculatorAction()); CalculatorModel model = modelAfterExecutingActions(actions); assertEquals(7.0, model.getVariable()); assertEquals(11.0, model.getOutput()); @@ -74,12 +72,12 @@ void testAddition() { @Test void testSubtraction() { - List actions = List.of( - new SetVariableCalculatorAction(2.0), - new AdditionCalculatorAction(), - new AdditionCalculatorAction(), - new SubtractionCalculatorAction() - ); + List actions = + List.of( + new SetVariableCalculatorAction(2.0), + new AdditionCalculatorAction(), + new AdditionCalculatorAction(), + new SubtractionCalculatorAction()); CalculatorModel model = modelAfterExecutingActions(actions); assertEquals(2.0, model.getVariable()); assertEquals(2.0, model.getOutput()); @@ -87,12 +85,12 @@ void testSubtraction() { @Test void testMultiplication() { - List actions = List.of( - new SetVariableCalculatorAction(2.0), - new AdditionCalculatorAction(), - new AdditionCalculatorAction(), - new MultiplicationCalculatorAction() - ); + List actions = + List.of( + new SetVariableCalculatorAction(2.0), + new AdditionCalculatorAction(), + new AdditionCalculatorAction(), + new MultiplicationCalculatorAction()); CalculatorModel model = modelAfterExecutingActions(actions); assertEquals(2.0, model.getVariable()); assertEquals(8.0, model.getOutput()); @@ -100,15 +98,15 @@ void testMultiplication() { @Test void testDivision() { - List actions = List.of( - new SetVariableCalculatorAction(2.0), - new AdditionCalculatorAction(), - new AdditionCalculatorAction(), - new SetVariableCalculatorAction(2.0), - new DivisionCalculatorAction() - ); + List actions = + List.of( + new SetVariableCalculatorAction(2.0), + new AdditionCalculatorAction(), + new AdditionCalculatorAction(), + new SetVariableCalculatorAction(2.0), + new DivisionCalculatorAction()); CalculatorModel model = modelAfterExecutingActions(actions); assertEquals(2.0, model.getVariable()); assertEquals(2.0, model.getOutput()); } -} \ No newline at end of file +} diff --git a/model-view-presenter/README.md b/model-view-presenter/README.md index f3bbbb01f76b..58cafed55b43 100644 --- a/model-view-presenter/README.md +++ b/model-view-presenter/README.md @@ -43,6 +43,11 @@ Wikipedia says > Model–view–presenter (MVP) is a derivation of the model–view–controller (MVC) architectural pattern, and is used mostly for building user interfaces. In MVP, the presenter assumes the functionality of the "middle-man". In MVP, all presentation logic is pushed to the presenter. +Architecture diagram + +![Model-View-Presenter Architecture Diagram](./etc/mvp-architecture-diagram.png) + + ## Programmatic Example of Model-View-Presenter Pattern in Java The Model-View-Presenter (MVP) design pattern is a derivative of the well-known Model-View-Controller (MVC) pattern. It aims to separate the application's logic (Model), GUIs (View), and the way that the user's actions update the application's logic (Presenter). This separation of concerns makes the application easier to manage, extend, and test. diff --git a/model-view-presenter/etc/mvp-architecture-diagram.png b/model-view-presenter/etc/mvp-architecture-diagram.png new file mode 100644 index 000000000000..df324b0ab6b0 Binary files /dev/null and b/model-view-presenter/etc/mvp-architecture-diagram.png differ diff --git a/model-view-presenter/pom.xml b/model-view-presenter/pom.xml index ab49c8f2f20b..9dcd55f610e2 100644 --- a/model-view-presenter/pom.xml +++ b/model-view-presenter/pom.xml @@ -36,6 +36,14 @@ model-view-presenter http://maven.apache.org + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java index 7bee50111411..45f7725604b4 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/App.java @@ -34,8 +34,8 @@ * FileSelectorJframe} is the GUI and the {@link FileSelectorPresenter} is responsible to respond to * users' actions. * - *

    Finally, please notice the wiring between the Presenter and the View and between the - * Presenter and the Model. + *

    Finally, please notice the wiring between the Presenter and the View and between the Presenter + * and the Model. */ public class App { diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java index 871cbedd20ad..b2678c811a7e 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileLoader.java @@ -43,27 +43,18 @@ @Getter public class FileLoader implements Serializable { - /** - * Generated serial version UID. - */ - @Serial - private static final long serialVersionUID = -4745803872902019069L; + /** Generated serial version UID. */ + @Serial private static final long serialVersionUID = -4745803872902019069L; private static final Logger LOGGER = LoggerFactory.getLogger(FileLoader.class); - /** - * Indicates if the file is loaded or not. - */ + /** Indicates if the file is loaded or not. */ private boolean loaded; - /** - * The name of the file that we want to load. - */ + /** The name of the file that we want to load. */ private String fileName; - /** - * Loads the data of the file specified. - */ + /** Loads the data of the file specified. */ public String loadData() { var dataFileName = this.fileName; try (var br = new BufferedReader(new FileReader(dataFileName))) { diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJframe.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJframe.java index fa7f3cdaf66f..1680e5455a8f 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJframe.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorJframe.java @@ -45,45 +45,28 @@ */ public class FileSelectorJframe extends JFrame implements FileSelectorView, ActionListener { - /** - * Default serial version ID. - */ - @Serial - private static final long serialVersionUID = 1L; - - /** - * The "OK" button for loading the file. - */ + /** Default serial version ID. */ + @Serial private static final long serialVersionUID = 1L; + + /** The "OK" button for loading the file. */ private final JButton ok; - /** - * The cancel button. - */ + /** The cancel button. */ private final JButton cancel; - /** - * The text field for giving the name of the file that we want to open. - */ + /** The text field for giving the name of the file that we want to open. */ private final JTextField input; - /** - * A text area that will keep the contents of the file opened. - */ + /** A text area that will keep the contents of the file opened. */ private final JTextArea area; - /** - * The Presenter component that the frame will interact with. - */ + /** The Presenter component that the frame will interact with. */ private FileSelectorPresenter presenter; - /** - * The name of the file that we want to read it's contents. - */ + /** The name of the file that we want to read it's contents. */ private String fileName; - /** - * Constructor. - */ + /** Constructor. */ public FileSelectorJframe() { super("File Loader"); this.setDefaultCloseOperation(EXIT_ON_CLOSE); diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java index 170c0f1cef99..3c15408bddcf 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorPresenter.java @@ -35,20 +35,13 @@ */ public class FileSelectorPresenter implements Serializable { - /** - * Generated serial version UID. - */ - @Serial - private static final long serialVersionUID = 1210314339075855074L; + /** Generated serial version UID. */ + @Serial private static final long serialVersionUID = 1210314339075855074L; - /** - * The View component that the presenter interacts with. - */ + /** The View component that the presenter interacts with. */ private final FileSelectorView view; - /** - * The Model component that the presenter interacts with. - */ + /** The Model component that the presenter interacts with. */ private FileLoader loader; /** @@ -69,24 +62,18 @@ public void setLoader(FileLoader loader) { this.loader = loader; } - /** - * Starts the presenter. - */ + /** Starts the presenter. */ public void start() { view.setPresenter(this); view.open(); } - /** - * An "event" that fires when the name of the file to be loaded changes. - */ + /** An "event" that fires when the name of the file to be loaded changes. */ public void fileNameChanged() { loader.setFileName(view.getFileName()); } - /** - * Ok button handler. - */ + /** Ok button handler. */ public void confirmed() { if (loader.getFileName() == null || loader.getFileName().isEmpty()) { view.showMessage("Please give the name of the file first!"); @@ -101,9 +88,7 @@ public void confirmed() { } } - /** - * Cancels the file loading process. - */ + /** Cancels the file loading process. */ public void cancelled() { view.close(); } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorStub.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorStub.java index ed09627a21e7..3eda80eb27c4 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorStub.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorStub.java @@ -36,34 +36,22 @@ */ public class FileSelectorStub implements FileSelectorView { - /** - * Indicates whether or not the view is opened. - */ + /** Indicates whether or not the view is opened. */ private boolean opened; - /** - * The presenter Component. - */ + /** The presenter Component. */ private FileSelectorPresenter presenter; - /** - * The current name of the file. - */ + /** The current name of the file. */ private String name; - /** - * Indicates the number of messages that were "displayed" to the user. - */ + /** Indicates the number of messages that were "displayed" to the user. */ private int numOfMessageSent; - /** - * Indicates if the data of the file where displayed or not. - */ + /** Indicates if the data of the file where displayed or not. */ private boolean dataDisplayed; - /** - * Constructor. - */ + /** Constructor. */ public FileSelectorStub() { this.opened = false; this.presenter = null; @@ -117,9 +105,7 @@ public void displayData(String data) { this.dataDisplayed = true; } - /** - * Returns the number of messages that were displayed to the user. - */ + /** Returns the number of messages that were displayed to the user. */ public int getMessagesSent() { return this.numOfMessageSent; } diff --git a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java index b2d916ac1d0f..9a0b86a0b61a 100644 --- a/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java +++ b/model-view-presenter/src/main/java/com/iluwatar/model/view/presenter/FileSelectorView.java @@ -32,14 +32,10 @@ */ public interface FileSelectorView extends Serializable { - /** - * Opens the view. - */ + /** Opens the view. */ void open(); - /** - * Closes the view. - */ + /** Closes the view. */ void close(); /** diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java index 78579518982c..91c7e8dbda7b 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/AppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.model.view.presenter; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java index 8edf9b40b6c1..b12877d35b63 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileLoaderTest.java @@ -28,10 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * FileLoaderTest - * - */ +/** FileLoaderTest */ class FileLoaderTest { @Test @@ -40,5 +37,4 @@ void testLoadData() { fileLoader.setFileName("non-existing-file"); assertNull(fileLoader.loadData()); } - -} \ No newline at end of file +} diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorJframeTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorJframeTest.java index 66107ebb5971..47b10737db52 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorJframeTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorJframeTest.java @@ -27,25 +27,19 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import java.awt.event.ActionEvent; - import org.junit.jupiter.api.Test; -/** - * FileSelectorJframeTest - * - */ +/** FileSelectorJframeTest */ class FileSelectorJframeTest { - - /** - * Tests if the jframe action event is triggered without any exception. - */ - @Test - void testActionEvent() { - assertDoesNotThrow(() ->{ - FileSelectorJframe jFrame = new FileSelectorJframe(); - ActionEvent action = new ActionEvent("dummy", 1, "dummy"); - jFrame.actionPerformed(action); - }); - } + /** Tests if the jframe action event is triggered without any exception. */ + @Test + void testActionEvent() { + assertDoesNotThrow( + () -> { + FileSelectorJframe jFrame = new FileSelectorJframe(); + ActionEvent action = new ActionEvent("dummy", 1, "dummy"); + jFrame.actionPerformed(action); + }); + } } diff --git a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java index b9a31253b622..dca646c20b87 100644 --- a/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java +++ b/model-view-presenter/src/test/java/com/iluwatar/model/view/presenter/FileSelectorPresenterTest.java @@ -38,24 +38,16 @@ */ class FileSelectorPresenterTest { - /** - * The Presenter component. - */ + /** The Presenter component. */ private FileSelectorPresenter presenter; - /** - * The View component, implemented this time as a Stub!!! - */ + /** The View component, implemented this time as a Stub!!! */ private FileSelectorStub stub; - /** - * The Model component. - */ + /** The Model component. */ private FileLoader loader; - /** - * Initializes the components of the test case. - */ + /** Initializes the components of the test case. */ @BeforeEach void setUp() { this.stub = new FileSelectorStub(); @@ -64,9 +56,7 @@ void setUp() { presenter.setLoader(loader); } - /** - * Tests if the Presenter was successfully connected with the View. - */ + /** Tests if the Presenter was successfully connected with the View. */ @Test void wiring() { presenter.start(); @@ -75,9 +65,7 @@ void wiring() { assertTrue(stub.isOpened()); } - /** - * Tests if the name of the file changes. - */ + /** Tests if the name of the file changes. */ @Test void updateFileNameToLoader() { var expectedFile = "Stamatis"; @@ -105,9 +93,7 @@ void fileConfirmationWhenNameIsNull() { assertEquals(1, stub.getMessagesSent()); } - /** - * Tests if we receive a confirmation when we attempt to open a file that it doesn't exist. - */ + /** Tests if we receive a confirmation when we attempt to open a file that it doesn't exist. */ @Test void fileConfirmationWhenFileDoesNotExist() { stub.setFileName("RandomName.txt"); @@ -120,9 +106,7 @@ void fileConfirmationWhenFileDoesNotExist() { assertEquals(1, stub.getMessagesSent()); } - /** - * Tests if we can open the file, when it exists. - */ + /** Tests if we can open the file, when it exists. */ @Test void fileConfirmationWhenFileExists() { stub.setFileName("etc/data/test.txt"); @@ -134,9 +118,7 @@ void fileConfirmationWhenFileExists() { assertTrue(stub.dataDisplayed()); } - /** - * Tests if the view closes after cancellation. - */ + /** Tests if the view closes after cancellation. */ @Test void cancellation() { presenter.start(); @@ -155,5 +137,4 @@ void testNullFile() { assertFalse(loader.isLoaded()); assertFalse(stub.dataDisplayed()); } - } diff --git a/model-view-viewmodel/README.md b/model-view-viewmodel/README.md index 335b4222a271..46597bedd9d8 100644 --- a/model-view-viewmodel/README.md +++ b/model-view-viewmodel/README.md @@ -42,6 +42,11 @@ Wikipedia says > Model–view–viewmodel (MVVM) is a software architectural pattern that facilitates the separation of the development of the graphical user interface (the view) – be it via a markup language or GUI code – from the development of the business logic or back-end logic (the model) so that the view is not dependent on any specific model platform. +Architecture diagram + +![Model-View-ViewModel Architecture Diagram](./etc/mvvm-architecture-diagram.png) + + ## Programmatic Example of Model-View-ViewModel Pattern in Java ViewModel will hold the business logic and expose the data from model to View. diff --git a/model-view-viewmodel/etc/mvvm-architecture-diagram.png b/model-view-viewmodel/etc/mvvm-architecture-diagram.png new file mode 100644 index 000000000000..279c734cdbff Binary files /dev/null and b/model-view-viewmodel/etc/mvvm-architecture-diagram.png differ diff --git a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/Book.java b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/Book.java index 27f6794025f0..e7f8f035803d 100644 --- a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/Book.java +++ b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/Book.java @@ -27,9 +27,7 @@ import lombok.AllArgsConstructor; import lombok.Data; -/** - * Book class. - */ +/** Book class. */ @AllArgsConstructor @Data public class Book { @@ -37,5 +35,4 @@ public class Book { private String name; private String author; private String description; - } diff --git a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookService.java b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookService.java index b5f19b7c0900..34bb25c07b8a 100644 --- a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookService.java +++ b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookService.java @@ -27,9 +27,7 @@ import java.util.List; -/** - * Class representing a service to load books. - */ +/** Class representing a service to load books. */ public interface BookService { /* List all books * @return all books diff --git a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookServiceImpl.java b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookServiceImpl.java index a407b1e8f492..7d7257fcc816 100644 --- a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookServiceImpl.java +++ b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookServiceImpl.java @@ -27,39 +27,44 @@ import java.util.ArrayList; import java.util.List; -/** - * Class that actually implement the books to load. - */ +/** Class that actually implement the books to load. */ public class BookServiceImpl implements BookService { private List designPatternBooks = new ArrayList<>(); - /** Initializes Book Data. - * To be used and passed along in load method - * In this case, list design pattern books are initialized to be loaded. - */ + /** + * Initializes Book Data. To be used and passed along in load method In this case, list design + * pattern books are initialized to be loaded. + */ public BookServiceImpl() { - designPatternBooks.add(new Book( - "Head First Design Patterns: A Brain-Friendly Guide", - "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", - "Head First Design Patterns Description")); - designPatternBooks.add(new Book( - "Design Patterns: Elements of Reusable Object-Oriented Software", - "Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides", - "Design Patterns Description")); - designPatternBooks.add(new Book( - "Patterns of Enterprise Application Architecture", "Martin Fowler", - "Patterns of Enterprise Application Architecture Description")); - designPatternBooks.add(new Book( - "Design Patterns Explained", "Alan Shalloway, James Trott", - "Design Patterns Explained Description")); - designPatternBooks.add(new Book( - "Applying UML and Patterns: An Introduction to " - + "Object-Oriented Analysis and Design and Iterative Development", - "Craig Larman", "Applying UML and Patterns Description")); + designPatternBooks.add( + new Book( + "Head First Design Patterns: A Brain-Friendly Guide", + "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", + "Head First Design Patterns Description")); + designPatternBooks.add( + new Book( + "Design Patterns: Elements of Reusable Object-Oriented Software", + "Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides", + "Design Patterns Description")); + designPatternBooks.add( + new Book( + "Patterns of Enterprise Application Architecture", + "Martin Fowler", + "Patterns of Enterprise Application Architecture Description")); + designPatternBooks.add( + new Book( + "Design Patterns Explained", + "Alan Shalloway, James Trott", + "Design Patterns Explained Description")); + designPatternBooks.add( + new Book( + "Applying UML and Patterns: An Introduction to " + + "Object-Oriented Analysis and Design and Iterative Development", + "Craig Larman", + "Applying UML and Patterns Description")); } public List load() { return designPatternBooks; } - } diff --git a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookViewModel.java b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookViewModel.java index e00cb623ff7a..7eb81d1f0224 100644 --- a/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookViewModel.java +++ b/model-view-viewmodel/src/main/java/com/iluwatar/model/view/viewmodel/BookViewModel.java @@ -26,22 +26,17 @@ import java.util.List; import lombok.Getter; -import lombok.Setter; import org.zkoss.bind.annotation.Command; import org.zkoss.bind.annotation.NotifyChange; import org.zkoss.zk.ui.select.annotation.WireVariable; -/** - * BookViewModel class. - */ +/** BookViewModel class. */ public class BookViewModel { - - @WireVariable - private List bookList; - @Getter - private Book selectedBook; + + @WireVariable private List bookList; + @Getter private Book selectedBook; private BookService bookService = new BookServiceImpl(); - + @NotifyChange("selectedBook") public void setSelectedBook(Book selectedBook) { this.selectedBook = selectedBook; @@ -50,11 +45,11 @@ public void setSelectedBook(Book selectedBook) { public List getBookList() { return bookService.load(); } - - /** Deleting a book. - * When event is triggered on click of Delete button, - * this method will be notified with the selected entry that will be referenced - * and used to delete the selected book from the list of books. + + /** + * Deleting a book. When event is triggered on click of Delete button, this method will be + * notified with the selected entry that will be referenced and used to delete the selected book + * from the list of books. */ @Command @NotifyChange({"selectedBook", "bookList"}) @@ -64,5 +59,4 @@ public void deleteBook() { selectedBook = null; } } - } diff --git a/model-view-viewmodel/src/test/java/com/iluwatar/model/view/viewmodel/BookTest.java b/model-view-viewmodel/src/test/java/com/iluwatar/model/view/viewmodel/BookTest.java index bec79d011171..71f3d8a5194c 100644 --- a/model-view-viewmodel/src/test/java/com/iluwatar/model/view/viewmodel/BookTest.java +++ b/model-view-viewmodel/src/test/java/com/iluwatar/model/view/viewmodel/BookTest.java @@ -30,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; + import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -41,27 +42,33 @@ class BookTest { List testBookList; Book testBookTwo; Book testBookThree; - + @BeforeEach void setUp() { bvm = new BookViewModel(); - testBook = new Book("Head First Design Patterns: A Brain-Friendly Guide", - "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", - "Head First Design Patterns Description"); + testBook = + new Book( + "Head First Design Patterns: A Brain-Friendly Guide", + "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", + "Head First Design Patterns Description"); testBookList = bvm.getBookList(); - testBookTwo = new Book("Head First Design Patterns: A Brain-Friendly Guide", - "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", - "Head First Design Patterns Description"); - testBookThree = new Book("Design Patterns: Elements of Reusable Object-Oriented Software", + testBookTwo = + new Book( + "Head First Design Patterns: A Brain-Friendly Guide", + "Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson", + "Head First Design Patterns Description"); + testBookThree = + new Book( + "Design Patterns: Elements of Reusable Object-Oriented Software", "Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides", "Design Patterns Description"); } @Test void testBookModel() { - assertNotNull(testBook); + assertNotNull(testBook); } - + @Test void testEquals() { assertEquals(testBook, testBookTwo); @@ -72,13 +79,13 @@ void testToString() { assertEquals(testBook.toString(), testBookTwo.toString()); assertNotEquals(testBook.toString(), testBookThree.toString()); } - + @Test void testHashCode() { assertTrue(testBook.equals(testBookTwo) && testBookTwo.equals(testBook)); assertEquals(testBook.hashCode(), testBookTwo.hashCode()); } - + @Test void testLoadData() { assertNotNull(testBookList); @@ -87,7 +94,7 @@ void testLoadData() { @Test void testSelectedData() { - bvm.setSelectedBook(testBook); + bvm.setSelectedBook(testBook); assertNotNull(bvm.getSelectedBook()); assertEquals(testBook.toString(), bvm.getSelectedBook().toString()); assertTrue(true, bvm.getSelectedBook().toString()); @@ -102,5 +109,4 @@ void testDeleteData() { assertNull(bvm.getSelectedBook()); assertFalse(testBookList.get(0).toString().contains("Head First Design Patterns")); } - -} \ No newline at end of file +} diff --git a/monad/README.md b/monad/README.md index e63d830a7eb2..4fdf1476385f 100644 --- a/monad/README.md +++ b/monad/README.md @@ -43,6 +43,10 @@ Wikipedia says > In functional programming, a monad is a structure that combines program fragments (functions) and wraps their return values in a type with additional computation. In addition to defining a wrapping monadic type, monads define two operators: one to wrap a value in the monad type, and another to compose together functions that output values of the monad type (these are known as monadic functions). General-purpose languages use monads to reduce boilerplate code needed for common operations (such as dealing with undefined values or fallible functions, or encapsulating bookkeeping code). Functional languages use monads to turn complicated sequences of functions into succinct pipelines that abstract away control flow, and side effects. +Flowchart + +![Monad flowchart](./etc/monad-flowchart.png) + ## Programmatic Example of Monad Pattern in Java Here’s the Monad implementation in Java. The `Validator` class encapsulates an object and performs validation steps in a monadic fashion, showcasing the benefits of using the Monad pattern for error handling and state management. diff --git a/monad/etc/monad-flowchart.png b/monad/etc/monad-flowchart.png new file mode 100644 index 000000000000..0db0e5e12195 Binary files /dev/null and b/monad/etc/monad-flowchart.png differ diff --git a/monad/pom.xml b/monad/pom.xml index e48e6538b7d8..7837b60c846a 100644 --- a/monad/pom.xml +++ b/monad/pom.xml @@ -34,6 +34,14 @@ monad + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/monad/src/main/java/com/iluwatar/monad/App.java b/monad/src/main/java/com/iluwatar/monad/App.java index 7ee04f1b8c31..56e1c59dae42 100644 --- a/monad/src/main/java/com/iluwatar/monad/App.java +++ b/monad/src/main/java/com/iluwatar/monad/App.java @@ -1,63 +1,66 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.monad; - -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; -import lombok.extern.slf4j.Slf4j; - -/** - * The Monad pattern defines a monad structure, that enables chaining operations in pipelines and - * processing data step by step. Formally, monad consists of a type constructor M and two - * operations: - *
    bind - that takes monadic object and a function from plain object to the - * monadic value and returns monadic value. - *
    return - that takes plain type object and returns this object wrapped in a monadic value. - * - *

    In the given example, the Monad pattern is represented as a {@link Validator} that takes an - * instance of a plain object with {@link Validator#of(Object)} and validates it {@link - * Validator#validate(Function, Predicate, String)} against given predicates. - * - *

    As a validation result {@link Validator#get()} either returns valid object - * or throws {@link IllegalStateException} with list of exceptions collected during validation. - */ -@Slf4j -public class App { - - /** - * Program entry point. - * - * @param args command line args - */ - public static void main(String[] args) { - var user = new User("user", 24, Sex.FEMALE, "foobar.com"); - LOGGER.info(Validator.of(user).validate(User::name, Objects::nonNull, "name is null") - .validate(User::name, name -> !name.isEmpty(), "name is empty") - .validate(User::email, email -> !email.contains("@"), "email doesn't contains '@'") - .validate(User::age, age -> age > 20 && age < 30, "age isn't between...").get() - .toString()); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monad; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; +import lombok.extern.slf4j.Slf4j; + +/** + * The Monad pattern defines a monad structure, that enables chaining operations in pipelines and + * processing data step by step. Formally, monad consists of a type constructor M and two + * operations:
    + * bind - that takes monadic object and a function from plain object to the monadic value and + * returns monadic value.
    + * return - that takes plain type object and returns this object wrapped in a monadic value. + * + *

    In the given example, the Monad pattern is represented as a {@link Validator} that takes an + * instance of a plain object with {@link Validator#of(Object)} and validates it {@link + * Validator#validate(Function, Predicate, String)} against given predicates. + * + *

    As a validation result {@link Validator#get()} either returns valid object or throws {@link + * IllegalStateException} with list of exceptions collected during validation. + */ +@Slf4j +public class App { + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + var user = new User("user", 24, Sex.FEMALE, "foobar.com"); + LOGGER.info( + Validator.of(user) + .validate(User::name, Objects::nonNull, "name is null") + .validate(User::name, name -> !name.isEmpty(), "name is empty") + .validate(User::email, email -> !email.contains("@"), "email doesn't contains '@'") + .validate(User::age, age -> age > 20 && age < 30, "age isn't between...") + .get() + .toString()); + } +} diff --git a/monad/src/main/java/com/iluwatar/monad/Sex.java b/monad/src/main/java/com/iluwatar/monad/Sex.java index 5979187a57ff..7a5189f53629 100644 --- a/monad/src/main/java/com/iluwatar/monad/Sex.java +++ b/monad/src/main/java/com/iluwatar/monad/Sex.java @@ -1,32 +1,31 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.monad; - -/** - * Enumeration of Types of Sex. - */ -public enum Sex { - MALE, FEMALE -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monad; + +/** Enumeration of Types of Sex. */ +public enum Sex { + MALE, + FEMALE +} diff --git a/monad/src/main/java/com/iluwatar/monad/User.java b/monad/src/main/java/com/iluwatar/monad/User.java index 089279a8db59..dd419a36dbe8 100644 --- a/monad/src/main/java/com/iluwatar/monad/User.java +++ b/monad/src/main/java/com/iluwatar/monad/User.java @@ -27,11 +27,9 @@ /** * Record class. * - * @param name - name - * @param age - age - * @param sex - sex + * @param name - name + * @param age - age + * @param sex - sex * @param email - email address */ -public record User(String name, int age, Sex sex, String email) { -} - +public record User(String name, int age, Sex sex, String email) {} diff --git a/monad/src/main/java/com/iluwatar/monad/Validator.java b/monad/src/main/java/com/iluwatar/monad/Validator.java index 757343e75608..0aef2f67c02a 100644 --- a/monad/src/main/java/com/iluwatar/monad/Validator.java +++ b/monad/src/main/java/com/iluwatar/monad/Validator.java @@ -1,121 +1,116 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.monad; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Function; -import java.util.function.Predicate; - -/** - * Class representing Monad design pattern. Monad is a way of chaining operations on the given - * object together step by step. In Validator each step results in either success or failure - * indicator, giving a way of receiving each of them easily and finally getting validated object or - * list of exceptions. - * - * @param Placeholder for an object. - */ -public class Validator { - /** - * Object that is validated. - */ - private final T obj; - - /** - * List of exception thrown during validation. - */ - private final List exceptions = new ArrayList<>(); - - /** - * Creates a monadic value of given object. - * - * @param obj object to be validated - */ - private Validator(T obj) { - this.obj = obj; - } - - /** - * Creates validator against given object. - * - * @param t object to be validated - * @param object's type - * @return new instance of a validator - */ - public static Validator of(T t) { - return new Validator<>(Objects.requireNonNull(t)); - } - - /** - * Checks if the validation is successful. - * - * @param validation one argument boolean-valued function that represents one step of validation. - * Adds exception to main validation exception list when single step validation - * ends with failure. - * @param message error message when object is invalid - * @return this - */ - public Validator validate(Predicate validation, String message) { - if (!validation.test(obj)) { - exceptions.add(new IllegalStateException(message)); - } - return this; - } - - /** - * Extension for the {@link Validator#validate(Predicate, String)} method, dedicated for objects, - * that need to be projected before requested validation. - * - * @param projection function that gets an objects, and returns projection representing element to - * be validated. - * @param validation see {@link Validator#validate(Predicate, String)} - * @param message see {@link Validator#validate(Predicate, String)} - * @param see {@link Validator#validate(Predicate, String)} - * @return this - */ - public Validator validate( - Function projection, - Predicate validation, - String message - ) { - return validate(projection.andThen(validation::test)::apply, message); - } - - /** - * Receives validated object or throws exception when invalid. - * - * @return object that was validated - * @throws IllegalStateException when any validation step results with failure - */ - public T get() throws IllegalStateException { - if (exceptions.isEmpty()) { - return obj; - } - var e = new IllegalStateException(); - exceptions.forEach(e::addSuppressed); - throw e; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monad; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Class representing Monad design pattern. Monad is a way of chaining operations on the given + * object together step by step. In Validator each step results in either success or failure + * indicator, giving a way of receiving each of them easily and finally getting validated object or + * list of exceptions. + * + * @param Placeholder for an object. + */ +public class Validator { + /** Object that is validated. */ + private final T obj; + + /** List of exception thrown during validation. */ + private final List exceptions = new ArrayList<>(); + + /** + * Creates a monadic value of given object. + * + * @param obj object to be validated + */ + private Validator(T obj) { + this.obj = obj; + } + + /** + * Creates validator against given object. + * + * @param t object to be validated + * @param object's type + * @return new instance of a validator + */ + public static Validator of(T t) { + return new Validator<>(Objects.requireNonNull(t)); + } + + /** + * Checks if the validation is successful. + * + * @param validation one argument boolean-valued function that represents one step of validation. + * Adds exception to main validation exception list when single step validation ends with + * failure. + * @param message error message when object is invalid + * @return this + */ + public Validator validate(Predicate validation, String message) { + if (!validation.test(obj)) { + exceptions.add(new IllegalStateException(message)); + } + return this; + } + + /** + * Extension for the {@link Validator#validate(Predicate, String)} method, dedicated for objects, + * that need to be projected before requested validation. + * + * @param projection function that gets an objects, and returns projection representing element to + * be validated. + * @param validation see {@link Validator#validate(Predicate, String)} + * @param message see {@link Validator#validate(Predicate, String)} + * @param see {@link Validator#validate(Predicate, String)} + * @return this + */ + public Validator validate( + Function projection, + Predicate validation, + String message) { + return validate(projection.andThen(validation::test)::apply, message); + } + + /** + * Receives validated object or throws exception when invalid. + * + * @return object that was validated + * @throws IllegalStateException when any validation step results with failure + */ + public T get() throws IllegalStateException { + if (exceptions.isEmpty()) { + return obj; + } + var e = new IllegalStateException(); + exceptions.forEach(e::addSuppressed); + throw e; + } +} diff --git a/monad/src/test/java/com/iluwatar/monad/AppTest.java b/monad/src/test/java/com/iluwatar/monad/AppTest.java index 864ede813a54..9320d4b1c440 100644 --- a/monad/src/test/java/com/iluwatar/monad/AppTest.java +++ b/monad/src/test/java/com/iluwatar/monad/AppTest.java @@ -28,15 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application Test - */ - +/** Application Test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/monad/src/test/java/com/iluwatar/monad/MonadTest.java b/monad/src/test/java/com/iluwatar/monad/MonadTest.java index 1eed42b12bc4..58916488491f 100644 --- a/monad/src/test/java/com/iluwatar/monad/MonadTest.java +++ b/monad/src/test/java/com/iluwatar/monad/MonadTest.java @@ -30,9 +30,7 @@ import java.util.Objects; import org.junit.jupiter.api.Test; -/** - * Test for Monad Pattern - */ +/** Test for Monad Pattern */ class MonadTest { @Test @@ -40,10 +38,8 @@ void testForInvalidName() { var tom = new User(null, 21, Sex.MALE, "tom@foo.bar"); assertThrows( IllegalStateException.class, - () -> Validator.of(tom) - .validate(User::name, Objects::nonNull, "name cannot be null") - .get() - ); + () -> + Validator.of(tom).validate(User::name, Objects::nonNull, "name cannot be null").get()); } @Test @@ -51,22 +47,23 @@ void testForInvalidAge() { var john = new User("John", 17, Sex.MALE, "john@qwe.bar"); assertThrows( IllegalStateException.class, - () -> Validator.of(john) - .validate(User::name, Objects::nonNull, "name cannot be null") - .validate(User::age, age -> age > 21, "user is underage") - .get() - ); + () -> + Validator.of(john) + .validate(User::name, Objects::nonNull, "name cannot be null") + .validate(User::age, age -> age > 21, "user is underage") + .get()); } @Test void testForValid() { var sarah = new User("Sarah", 42, Sex.FEMALE, "sarah@det.org"); - var validated = Validator.of(sarah) - .validate(User::name, Objects::nonNull, "name cannot be null") - .validate(User::age, age -> age > 21, "user is underage") - .validate(User::sex, sex -> sex == Sex.FEMALE, "user is not female") - .validate(User::email, email -> email.contains("@"), "email does not contain @ sign") - .get(); + var validated = + Validator.of(sarah) + .validate(User::name, Objects::nonNull, "name cannot be null") + .validate(User::age, age -> age > 21, "user is underage") + .validate(User::sex, sex -> sex == Sex.FEMALE, "user is not female") + .validate(User::email, email -> email.contains("@"), "email does not contain @ sign") + .get(); assertSame(validated, sarah); } } diff --git a/money/README.md b/money/README.md new file mode 100644 index 000000000000..23535688f851 --- /dev/null +++ b/money/README.md @@ -0,0 +1,131 @@ +--- +title: "Money Pattern in Java: Encapsulating Monetary Values with Currency Consistency" +shortTitle: Money +description: "Learn how the Money design pattern in Java ensures currency safety, precision handling, and maintainable financial operations. Explore examples, applicability, and benefits of the pattern." +category: Structural +language: en +tag: + - Business + - Domain + - Encapsulation + - Immutable +--- + +## Also known as + +* Monetary Value Object + +## Intent of Money Design Pattern + +Encapsulate monetary values and their associated currency in a domain-specific object. + +## Detailed Explanation of Money Pattern with Real-World Examples + +Real-world example + +> Imagine an online gift card system, where each gift card holds a specific balance in a particular currency. Instead of just using a floating-point value for the balance, the system uses a Money object to precisely track the amount and currency. Whenever someone uses the gift card, it updates the balance with accurate calculations that avoid floating-point rounding errors, ensuring the domain logic stays consistent and accurate. + +In plain words + +> The Money pattern encapsulates both an amount and its currency, ensuring financial operations are precise, consistent, and maintainable. + +Wikipedia says + +> The Money design pattern encapsulates a monetary value and its currency, allowing for safe arithmetic operations and conversions while preserving accuracy and consistency in financial calculations. + +Mind map + +![Money Pattern Mind Map](./etc/money-mind-map.png) + +Flowchart + +![Money Pattern Flowchart](./etc/money-flowchart.png) + +## Programmatic Example of Money Pattern in Java + +In this example, we're creating a `Money` class to demonstrate how monetary values can be encapsulated along with their currency. This approach helps avoid floating-point inaccuracies, ensures arithmetic operations are handled consistently, and provides a clear domain-centric way of working with money. + +```java +@AllArgsConstructor +@Getter +public class Money { + private double amount; + private String currency; + + public Money(double amnt, String curr) { + this.amount = amnt; + this.currency = curr; + } + + private double roundToTwoDecimals(double value) { + return Math.round(value * 100.0) / 100.0; + } + + public void addMoney(Money moneyToBeAdded) throws CannotAddTwoCurrienciesException { + if (!moneyToBeAdded.getCurrency().equals(this.currency)) { + throw new CannotAddTwoCurrienciesException("You are trying to add two different currencies"); + } + this.amount = roundToTwoDecimals(this.amount + moneyToBeAdded.getAmount()); + } + + public void subtractMoney(Money moneyToBeSubtracted) throws CannotSubtractException { + if (!moneyToBeSubtracted.getCurrency().equals(this.currency)) { + throw new CannotSubtractException("You are trying to subtract two different currencies"); + } else if (moneyToBeSubtracted.getAmount() > this.amount) { + throw new CannotSubtractException("The amount you are trying to subtract is larger than the amount you have"); + } + this.amount = roundToTwoDecimals(this.amount - moneyToBeSubtracted.getAmount()); + } + + public void multiply(int factor) { + if (factor < 0) { + throw new IllegalArgumentException("Factor must be non-negative"); + } + this.amount = roundToTwoDecimals(this.amount * factor); + } + + public void exchangeCurrency(String currencyToChangeTo, double exchangeRate) { + if (exchangeRate < 0) { + throw new IllegalArgumentException("Exchange rate must be non-negative"); + } + this.amount = roundToTwoDecimals(this.amount * exchangeRate); + this.currency = currencyToChangeTo; + } +} +``` + +By encapsulating all money-related logic in a single class, we reduce the risk of mixing different currencies, improve clarity of the codebase, and facilitate future modifications such as adding new currencies or refining rounding rules. This pattern ultimately strengthens the domain model by treating money as a distinct concept rather than just another numeric value. + +## When to Use the Money Pattern + +* When financial calculations or money manipulations are part of the business logic +* When precise handling of currency amounts is required to avoid floating-point inaccuracies +* When domain-driven design principles and strong typing are desired + +## Real-World Applications of Monad Pattern in Java + +* JSR 354 (Java Money and Currency) library in Java +* Custom domain models in e-commerce and accounting systems + +## Benefits and Trade-offs of Money Pattern + +Benefits + +* Provides a single, type-safe representation of monetary amounts and currency +* Encourages encapsulation of related operations such as addition, subtraction, and formatting +* Avoids floating-point errors by using integers or specialized decimal libraries + +Trade-offs + +* Requires additional classes and infrastructure to handle currency conversions and formatting +* Might introduce performance overhead when performing large numbers of money operations + +## Related Design Patterns + +* [Value Object](https://java-design-patterns.com/patterns/value-object/): Money is typically a prime example of a domain-driven design value object. + +## References and Credits + +* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze) +* [Implementing Domain-Driven Design](https://amzn.to/4dmBjrB) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) diff --git a/money/etc/money-flowchart.png b/money/etc/money-flowchart.png new file mode 100644 index 000000000000..022233a0d4e9 Binary files /dev/null and b/money/etc/money-flowchart.png differ diff --git a/money/etc/money-mind-map.png b/money/etc/money-mind-map.png new file mode 100644 index 000000000000..0f27143fb85c Binary files /dev/null and b/money/etc/money-mind-map.png differ diff --git a/money/etc/money.urm.puml b/money/etc/money.urm.puml new file mode 100644 index 000000000000..3567f5b7d63f --- /dev/null +++ b/money/etc/money.urm.puml @@ -0,0 +1,21 @@ +@startuml +package com.iluwatar { + class App { + - logger : Logger {static} + + App() + + main(args : String[]) {static} + } + class Money { + - amount : double + - currency : String + + Money(amnt : double, curr : String) + + addMoney(moneyToBeAdded : Money) + + exchangeCurrency(currencyToChangeTo : String, exchangeRate : double) + + getAmount() : double + + getCurrency() : String + + multiply(factor : int) + - roundToTwoDecimals(value : double) : double + + subtractMoney(moneyToBeSubtracted : Money) + } +} +@enduml \ No newline at end of file diff --git a/money/pom.xml b/money/pom.xml new file mode 100644 index 000000000000..fbe6296465d9 --- /dev/null +++ b/money/pom.xml @@ -0,0 +1,68 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + money + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.App + + + + + + + + + + \ No newline at end of file diff --git a/money/src/main/java/com/iluwatar/App.java b/money/src/main/java/com/iluwatar/App.java new file mode 100644 index 000000000000..3a31b8b7dc83 --- /dev/null +++ b/money/src/main/java/com/iluwatar/App.java @@ -0,0 +1,92 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The `App` class demonstrates the functionality of the {@link Money} class, which encapsulates + * monetary values and their associated currencies. It showcases operations like addition, + * subtraction, multiplication, and currency conversion, while ensuring validation and immutability. + * + *

    Through this example, the handling of invalid operations (e.g., mismatched currencies or + * invalid inputs) is demonstrated using custom exceptions. Logging is used for transparency. + * + *

    This highlights the practical application of object-oriented principles such as encapsulation + * and validation in a financial context. + */ +public class App { + + // Initialize the logger + private static final Logger logger = Logger.getLogger(App.class.getName()); + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + // Create instances of Money + Money usdAmount1 = new Money(50.00, "USD"); + Money usdAmount2 = new Money(20.00, "USD"); + + // Demonstrate addition + try { + usdAmount1.addMoney(usdAmount2); + logger.log(Level.INFO, "Sum in USD: {0}", usdAmount1.getAmount()); + } catch (CannotAddTwoCurrienciesException e) { + logger.log(Level.SEVERE, "Error adding money: {0}", e.getMessage()); + } + + // Demonstrate subtraction + try { + usdAmount1.subtractMoney(usdAmount2); + logger.log(Level.INFO, "Difference in USD: {0}", usdAmount1.getAmount()); + } catch (CannotSubtractException e) { + logger.log(Level.SEVERE, "Error subtracting money: {0}", e.getMessage()); + } + + // Demonstrate multiplication + try { + usdAmount1.multiply(2); + logger.log(Level.INFO, "Multiplied Amount in USD: {0}", usdAmount1.getAmount()); + } catch (IllegalArgumentException e) { + logger.log(Level.SEVERE, "Error multiplying money: {0}", e.getMessage()); + } + + // Demonstrate currency conversion + try { + double exchangeRateUsdToEur = 0.85; // Example exchange rate + usdAmount1.exchangeCurrency("EUR", exchangeRateUsdToEur); + logger.log( + Level.INFO, + "USD converted to EUR: {0} {1}", + new Object[] {usdAmount1.getAmount(), usdAmount1.getCurrency()}); + } catch (IllegalArgumentException e) { + logger.log(Level.SEVERE, "Error converting currency: {0}", e.getMessage()); + } + } +} diff --git a/money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java b/money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java new file mode 100644 index 000000000000..0222055584bc --- /dev/null +++ b/money/src/main/java/com/iluwatar/CannotAddTwoCurrienciesException.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +/** An exception for when the user tries to add two diffrent currencies. */ +public class CannotAddTwoCurrienciesException extends Exception { + /** + * Constructs an exception with the specified message. + * + * @param message the message shown in the terminal (as a String). + */ + public CannotAddTwoCurrienciesException(String message) { + super(message); + } +} diff --git a/money/src/main/java/com/iluwatar/CannotSubtractException.java b/money/src/main/java/com/iluwatar/CannotSubtractException.java new file mode 100644 index 000000000000..f0403415d05e --- /dev/null +++ b/money/src/main/java/com/iluwatar/CannotSubtractException.java @@ -0,0 +1,40 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +/** + * An exception for when the user tries to subtract two different currencies or subtract an amount + * he doesn't have. + */ +public class CannotSubtractException extends Exception { + /** + * Constructs an exception with the specified message. + * + * @param message the message shown in the terminal (as a String). + */ + public CannotSubtractException(String message) { + super(message); + } +} diff --git a/money/src/main/java/com/iluwatar/Money.java b/money/src/main/java/com/iluwatar/Money.java new file mode 100644 index 000000000000..5c2cda9bf74c --- /dev/null +++ b/money/src/main/java/com/iluwatar/Money.java @@ -0,0 +1,108 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * Represents a monetary value with an associated currency. Provides operations for basic arithmetic + * (addition, subtraction, multiplication), as well as currency conversion while ensuring proper + * rounding. + */ +@AllArgsConstructor +@Getter +public class Money { + private double amount; + private String currency; + + /** + * Rounds the given value to two decimal places. + * + * @param value the value to round. + * @return the rounded value, up to two decimal places. + */ + private double roundToTwoDecimals(double value) { + return Math.round(value * 100.0) / 100.0; + } + + /** + * Adds another Money object to the current instance. + * + * @param moneyToBeAdded the Money object to add. + * @throws CannotAddTwoCurrienciesException if the currencies do not match. + */ + public void addMoney(Money moneyToBeAdded) throws CannotAddTwoCurrienciesException { + if (!moneyToBeAdded.getCurrency().equals(this.currency)) { + throw new CannotAddTwoCurrienciesException("You are trying to add two different currencies"); + } + this.amount = roundToTwoDecimals(this.amount + moneyToBeAdded.getAmount()); + } + + /** + * Subtracts another Money object from the current instance. + * + * @param moneyToBeSubtracted the Money object to subtract. + * @throws CannotSubtractException if the currencies do not match or if the amount to subtract is + * larger than the current amount. + */ + public void subtractMoney(Money moneyToBeSubtracted) throws CannotSubtractException { + if (!moneyToBeSubtracted.getCurrency().equals(this.currency)) { + throw new CannotSubtractException("You are trying to subtract two different currencies"); + } else if (moneyToBeSubtracted.getAmount() > this.amount) { + throw new CannotSubtractException( + "The amount you are trying to subtract is larger than the amount you have"); + } + this.amount = roundToTwoDecimals(this.amount - moneyToBeSubtracted.getAmount()); + } + + /** + * Multiplies the current amount of money by a factor. + * + * @param factor the factor to multiply by. + * @throws IllegalArgumentException if the factor is negative. + */ + public void multiply(int factor) { + if (factor < 0) { + throw new IllegalArgumentException("Factor must be non-negative"); + } + this.amount = roundToTwoDecimals(this.amount * factor); + } + + /** + * Converts the current amount of money to another currency using the provided exchange rate. + * + * @param currencyToChangeTo the new currency to convert to. + * @param exchangeRate the exchange rate to convert from the current currency to the new currency. + * @throws IllegalArgumentException if the exchange rate is negative. + */ + public void exchangeCurrency(String currencyToChangeTo, double exchangeRate) { + if (exchangeRate < 0) { + throw new IllegalArgumentException("Exchange rate must be non-negative"); + } + this.amount = roundToTwoDecimals(this.amount * exchangeRate); + this.currency = currencyToChangeTo; + } +} diff --git a/money/src/test/java/com/iluwater/money/MoneyTest.java b/money/src/test/java/com/iluwater/money/MoneyTest.java new file mode 100644 index 000000000000..6ee01283fdb6 --- /dev/null +++ b/money/src/test/java/com/iluwater/money/MoneyTest.java @@ -0,0 +1,136 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwater.money; + +import static org.junit.jupiter.api.Assertions.*; + +import com.iluwatar.App; +import com.iluwatar.CannotAddTwoCurrienciesException; +import com.iluwatar.CannotSubtractException; +import com.iluwatar.Money; +import org.junit.jupiter.api.Test; + +class MoneyTest { + + @Test + void testConstructor() { + // Test the constructor + Money money = new Money(100.00, "USD"); + assertEquals(100.00, money.getAmount()); + assertEquals("USD", money.getCurrency()); + } + + @Test + void testAddMoney_SameCurrency() throws CannotAddTwoCurrienciesException { + // Test adding two Money objects with the same currency + Money money1 = new Money(100.00, "USD"); + Money money2 = new Money(50.25, "USD"); + + money1.addMoney(money2); + + assertEquals(150.25, money1.getAmount(), "Amount after addition should be 150.25"); + } + + @Test + void testAddMoney_DifferentCurrency() { + // Test adding two Money objects with different currencies + Money money1 = new Money(100.00, "USD"); + Money money2 = new Money(50.25, "EUR"); + + assertThrows(CannotAddTwoCurrienciesException.class, () -> money1.addMoney(money2)); + } + + @Test + void testSubtractMoney_SameCurrency() throws CannotSubtractException { + // Test subtracting two Money objects with the same currency + Money money1 = new Money(100.00, "USD"); + Money money2 = new Money(50.25, "USD"); + + money1.subtractMoney(money2); + + assertEquals(49.75, money1.getAmount(), "Amount after subtraction should be 49.75"); + } + + @Test + void testSubtractMoney_DifferentCurrency() { + // Test subtracting two Money objects with different currencies + Money money1 = new Money(100.00, "USD"); + Money money2 = new Money(50.25, "EUR"); + + assertThrows(CannotSubtractException.class, () -> money1.subtractMoney(money2)); + } + + @Test + void testSubtractMoney_AmountTooLarge() { + // Test subtracting an amount larger than the current amount + Money money1 = new Money(50.00, "USD"); + Money money2 = new Money(60.00, "USD"); + + assertThrows(CannotSubtractException.class, () -> money1.subtractMoney(money2)); + } + + @Test + void testMultiply() { + // Test multiplying the money amount by a factor + Money money = new Money(100.00, "USD"); + + money.multiply(3); + + assertEquals(300.00, money.getAmount(), "Amount after multiplication should be 300.00"); + } + + @Test + void testMultiply_NegativeFactor() { + // Test multiplying by a negative factor + Money money = new Money(100.00, "USD"); + + assertThrows(IllegalArgumentException.class, () -> money.multiply(-2)); + } + + @Test + void testExchangeCurrency() { + // Test converting currency using an exchange rate + Money money = new Money(100.00, "USD"); + + money.exchangeCurrency("EUR", 0.85); + + assertEquals("EUR", money.getCurrency(), "Currency after conversion should be EUR"); + assertEquals(85.00, money.getAmount(), "Amount after conversion should be 85.00"); + } + + @Test + void testExchangeCurrency_NegativeExchangeRate() { + // Test converting currency with a negative exchange rate + Money money = new Money(100.00, "USD"); + + assertThrows(IllegalArgumentException.class, () -> money.exchangeCurrency("EUR", -0.85)); + } + + @Test + void testAppExecution() { + assertDoesNotThrow( + () -> App.main(new String[] {}), "App execution should not throw any exceptions"); + } +} diff --git a/monitor/README.md b/monitor/README.md index 46d5b686721b..ecf229d712d9 100644 --- a/monitor/README.md +++ b/monitor/README.md @@ -36,6 +36,10 @@ Wikipedia says > In concurrent programming (also known as parallel programming), a monitor is a synchronization construct that allows threads to have both mutual exclusion and the ability to wait (block) for a certain condition to become false. Monitors also have a mechanism for signaling other threads that their condition has been met. +Sequence diagram + +![Monitor sequence diagram](./etc/monitor-sequence-diagram.png) + ## Programmatic Example of Monitor Pattern in Java The Monitor design pattern is a synchronization technique used in concurrent programming to ensure that only one thread can execute a particular section of code at a time. It is a method of wrapping and hiding the synchronization primitives (like semaphores or locks) within the methods of an object. This pattern is useful in situations where race conditions could occur. diff --git a/monitor/etc/monitor-sequence-diagram.png b/monitor/etc/monitor-sequence-diagram.png new file mode 100644 index 000000000000..94f85d2753f9 Binary files /dev/null and b/monitor/etc/monitor-sequence-diagram.png differ diff --git a/monitor/pom.xml b/monitor/pom.xml index 1f87196d4828..67e24c3bb4a0 100644 --- a/monitor/pom.xml +++ b/monitor/pom.xml @@ -34,6 +34,14 @@ monitor + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -52,7 +60,7 @@ - com.iluwatar.abstractdocument.Main + com.iluwatar.monitor.Main diff --git a/monitor/src/main/java/com/iluwatar/monitor/Bank.java b/monitor/src/main/java/com/iluwatar/monitor/Bank.java index ade21985a969..bc89c26eaad7 100644 --- a/monitor/src/main/java/com/iluwatar/monitor/Bank.java +++ b/monitor/src/main/java/com/iluwatar/monitor/Bank.java @@ -23,27 +23,27 @@ * THE SOFTWARE. */ /* -*The MIT License -*Copyright © 2014-2021 Ilkka Seppälä -* -*Permission is hereby granted, free of charge, to any person obtaining a copy -*of this software and associated documentation files (the "Software"), to deal -*in the Software without restriction, including without limitation the rights -*to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -*copies of the Software, and to permit persons to whom the Software is -*furnished to do so, subject to the following conditions: -* -*The above copyright notice and this permission notice shall be included in -*all copies or substantial portions of the Software. -* -*THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -*IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -*FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -*AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -*LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -*OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -*THE SOFTWARE. -*/ + *The MIT License + *Copyright © 2014-2021 Ilkka Seppälä + * + *Permission is hereby granted, free of charge, to any person obtaining a copy + *of this software and associated documentation files (the "Software"), to deal + *in the Software without restriction, including without limitation the rights + *to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + *copies of the Software, and to permit persons to whom the Software is + *furnished to do so, subject to the following conditions: + * + *The above copyright notice and this permission notice shall be included in + *all copies or substantial portions of the Software. + * + *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + *IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + *FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + *AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + *LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + *OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + *THE SOFTWARE. + */ package com.iluwatar.monitor; @@ -55,8 +55,7 @@ @Slf4j public class Bank { - @Getter - private final int[] accounts; + @Getter private final int[] accounts; /** * Constructor. @@ -74,7 +73,7 @@ public Bank(int accountNum, int baseAmount) { * * @param accountA - source account * @param accountB - destination account - * @param amount - amount to be transferred + * @param amount - amount to be transferred */ public synchronized void transfer(int accountA, int accountB, int amount) { if (accounts[accountA] >= amount && accountA != accountB) { diff --git a/monitor/src/main/java/com/iluwatar/monitor/Main.java b/monitor/src/main/java/com/iluwatar/monitor/Main.java index 7324e6e7ac9b..37d0af0dde41 100644 --- a/monitor/src/main/java/com/iluwatar/monitor/Main.java +++ b/monitor/src/main/java/com/iluwatar/monitor/Main.java @@ -28,6 +28,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import lombok.extern.slf4j.Slf4j; + /** * The Monitor pattern is used in concurrent algorithms to achieve mutual exclusion. * @@ -46,7 +47,7 @@ public class Main { /** * Runner to perform a bunch of transfers and handle exception. * - * @param bank bank object + * @param bank bank object * @param latch signal finished execution */ public static void runner(Bank bank, CountDownLatch latch) { diff --git a/monitor/src/test/java/com/iluwatar/monitor/BankTest.java b/monitor/src/test/java/com/iluwatar/monitor/BankTest.java index 6d0c671ebe6f..6f3b9a145df4 100644 --- a/monitor/src/test/java/com/iluwatar/monitor/BankTest.java +++ b/monitor/src/test/java/com/iluwatar/monitor/BankTest.java @@ -24,11 +24,12 @@ */ package com.iluwatar.monitor; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assumptions.*; class BankTest { diff --git a/monitor/src/test/java/com/iluwatar/monitor/MainTest.java b/monitor/src/test/java/com/iluwatar/monitor/MainTest.java index 4d69aeebd5ef..fa067cf9dd2f 100644 --- a/monitor/src/test/java/com/iluwatar/monitor/MainTest.java +++ b/monitor/src/test/java/com/iluwatar/monitor/MainTest.java @@ -24,10 +24,11 @@ */ package com.iluwatar.monitor; -import org.junit.jupiter.api.Test; -import java.util.concurrent.CountDownLatch; import static org.junit.jupiter.api.Assertions.*; +import java.util.concurrent.CountDownLatch; +import org.junit.jupiter.api.Test; + /** Test if the application starts without throwing an exception. */ class MainTest { diff --git a/monolithic-architecture/README.md b/monolithic-architecture/README.md new file mode 100644 index 000000000000..6bb63e04fbfe --- /dev/null +++ b/monolithic-architecture/README.md @@ -0,0 +1,152 @@ +--- +title: "Monolithic Architecture in Java: A Cohesive Application Model" +shortTitle: Monolithic Architecture +description: "Explore the Monolithic Architecture application structure, its design intent, benefits, limitations, and real-world applications. Understand its simplicity and practical use cases." +category: Architectural +language: en +tag: + - Architecture + - Cohesion + - Encapsulation + - Layered architecture + - Modularity +--- + +## Also known as + +* Single-tier architecture +* Monolith + +## The Intent of Monolithic Design pattern + +Encapsulate all the functionality of an application within a single, cohesive codebase. + +## Detailed Explanation of the Monolithic Architecture + +Real-world example + +> An analogous real-world example of the Monolithic Architecture pattern is a department store. Just like a monolithic Java application, a department store hosts all product categories, sales, storage, and customer services within a single, large building. It simplifies shopping by consolidating everything under one roof, making operations straightforward and easy to manage. However, expanding or rearranging specific departments becomes increasingly challenging over time, similar to how scaling individual functionalities within a monolithic system can become complex and cumbersome. + +In plain words + +> The monolithic design pattern structures an application as a single unified unit, where all components are tightly coupled and run within a single process. + +Wikipedia says + +> In software engineering, a monolithic application is a single unified software application that is self-contained and independent of other applications, but typically lacks flexibility. There are advantages and disadvantages of building applications in a monolithic style of software architecture, depending on requirements. Monolith applications are relatively simple and have a low cost but their shortcomings are lack of elasticity, fault tolerance and scalability. + +Mind map + +![Monolithic Architecture Mind Map](./etc/monolithic-architecture-mind-map.png) + +Flowchart + +![Monolithic Architecture Flowchart](./etc/monolithic-architecture-flowchart.png) + +## Programmatic Example of Monolithic Architecture in Java + +This is a simplified version of the main application, demonstrating how a monolithic architecture can be implemented. Here, all the essential services—such as user management (`UserCon`), product management (`ProductCon`), and order processing (`OrderCon`) — are tightly integrated within a single executable Java application. The CLI provides a straightforward user interaction point where operations like user registration, adding products, and placing orders are handled. + +```java +@SpringBootApplication +public class EcommerceApp implements CommandLineRunner { + + private static final Logger log = LogManager.getLogger(EcommerceApp.class); + private final UserCon userService; + private final ProductCon productService; + private final OrderCon orderService; + public EcommerceApp(UserCon userService, ProductCon productService, OrderCon orderService) { + this.userService = userService; + this.productService = productService; + this.orderService = orderService; + } + public static void main(String... args) { + SpringApplication.run(EcommerceApp.class, args); + } + @Override + public void run(String... args) { + Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8); + + log.info("Welcome to the Monolithic E-commerce CLI!"); + while (true) { + log.info("\nChoose an option:"); + log.info("1. Register User"); + log.info("2. Add Product"); + log.info("3. Place Order"); + log.info("4. Exit"); + log.info("Enter your choice: "); + + int userInput = scanner.nextInt(); + scanner.nextLine(); + + switch (userInput) { + case 1 -> registerUser(scanner); + case 2 -> addProduct(scanner); + case 3 -> placeOrder(scanner); + case 4 -> { + log.info("Exiting the application. Goodbye!"); + return; + } + default -> log.info("Invalid choice! Please try again."); + } + } + } + protected void registerUser(Scanner scanner) { + log.info("Enter user details:"); + log.info("Name: "); + String name = scanner.nextLine(); + log.info("Email: "); + String email = scanner.nextLine(); + log.info("Password: "); + String password = scanner.nextLine(); + + User user = new User(null, name, email, password); + userService.registerUser(user); + log.info("User registered successfully!"); + } + +} +``` + +In this example, the core business functionalities are closely interconnected, sharing common resources and residing within the same codebase. This approach simplifies initial application development, testing, and deployment, as each component can easily access the others directly without the overhead of inter-service communication. However, as the application grows, scaling individual components independently becomes more challenging, highlighting the key trade-off inherent in the monolithic architecture. + +## When to Use the Monolithic Architecture in Java + +* Suitable for small to medium-sized applications where simplicity, cohesive development, and ease of deployment outweigh scalability and flexibility requirements. +* Applicable in contexts where initial rapid development and ease of testing are prioritized. + +## Real-World Applications of Monolithic Architecture in Java + +* Early-stage Java web applications developed using frameworks like Spring MVC, Java EE (Servlets/JSP), or frameworks like Play. +* Traditional Java enterprise applications packaged and deployed as WAR or EAR files on application servers such as Apache Tomcat, Jetty, or WildFly. +* Standalone Java applications and desktop applications packaged as single executable JAR files. + +## Benefits and Trade-offs of Monolithic Architecture + +Benefits: + +* Simpler to develop, test, and deploy as the application is a single unit. +* Easier debugging and performance monitoring due to the single unified runtime. +* Typically faster initial development and straightforward management of dependencies. + +Trade-offs: + +* Poor scalability and potential performance bottlenecks as the application grows. +* Limited modularity, leading to increased complexity and harder maintainability over time. +* Slow deployments and updates due to a single large codebase. +* Difficult to scale individual functionalities independently. + +## Related Patterns + +* Microservices Architecture: Breaks down a monolithic application into independently deployable services, directly addressing the scalability and maintainability limitations of monoliths. +* [Layered Architecture](https://java-design-patterns.com/patterns/layered-architecture/): Often used internally within monoliths to provide clear separation between presentation, business logic, and persistence layers. + +## References + +* [Building Microservices](https://amzn.to/3UACtrU) +* [Fundamentals of Software Architecture: An Engineering Approach](https://amzn.to/4cx4A2N) +* [Monolithic Architecture - System Design (GeeksforGeeks)](https://www.geeksforgeeks.org/monolithic-architecture-system-design/) +* [Monolithic Application (Wikipedia)](https://en.wikipedia.org/wiki/Monolithic_application) +* [Pattern: Monolithic Architecture (Microservices.io)](https://microservices.io/patterns/monolithic.html) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [What Is Monolithic Architecture? (IBM)](https://www.ibm.com/think/topics/monolithic-architecture) diff --git a/monolithic-architecture/etc/Monolithic-Ecommerce.urm.puml b/monolithic-architecture/etc/Monolithic-Ecommerce.urm.puml new file mode 100644 index 000000000000..0f1c6d96e735 --- /dev/null +++ b/monolithic-architecture/etc/Monolithic-Ecommerce.urm.puml @@ -0,0 +1,123 @@ +@startuml +package com.iluwatar.monolithic.model { + class Orders { + - id : Long + - product : Products + - quantity : Integer + - totalPrice : Double + - user : User + + Orders() + + Orders(id : Long, user : User, product : Products, quantity : Integer, totalPrice : Double) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : Long + + getProduct() : Products + + getQuantity() : Integer + + getTotalPrice() : Double + + getUser() : User + + hashCode() : int + + setId(id : Long) + + setProduct(product : Products) + + setQuantity(quantity : Integer) + + setTotalPrice(totalPrice : Double) + + setUser(user : User) + + toString() : String + } + class Products { + - description : String + - id : Long + - name : String + - price : Double + - stock : Integer + + Products() + + Products(id : Long, name : String, description : String, price : Double, stock : Integer) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getDescription() : String + + getId() : Long + + getName() : String + + getPrice() : Double + + getStock() : Integer + + hashCode() : int + + setDescription(description : String) + + setId(id : Long) + + setName(name : String) + + setPrice(price : Double) + + setStock(stock : Integer) + + toString() : String + } + class User { + - email : String + - id : Long + - name : String + - password : String + + User() + + User(id : Long, name : String, email : String, password : String) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getEmail() : String + + getId() : Long + + getName() : String + + getPassword() : String + + hashCode() : int + + setEmail(email : String) + + setId(id : Long) + + setName(name : String) + + setPassword(password : String) + + toString() : String + } +} +package com.iluwatar.monolithic.repository { + interface OrderRepo { + } + interface ProductRepo { + } + interface UserRepo { + + findByEmail(String) : User {abstract} + } +} +package com.iluwatar.monolithic.controller { + class OrderCon { + - orderRepository : OrderRepo + - productRepository : ProductRepo + - userRepository : UserRepo + + OrderCon(orderRepository : OrderRepo, userRepository : UserRepo, productRepository : ProductRepo) + + placeOrder(userId : Long, productId : Long, quantity : Integer) : Orders + } + class ProductCon { + - productRepository : ProductRepo + + ProductCon(productRepository : ProductRepo) + + addProduct(product : Products) : Products + + getAllProducts() : List + } + class UserCon { + - userRepository : UserRepo + + UserCon(userRepository : UserRepo) + + registerUser(user : User) : User + } +} +package com.iluwatar.monolithic { + class EcommerceApp { + - log : Logger {static} + - orderService : OrderCon + - productService : ProductCon + - userService : UserCon + + EcommerceApp(userService : UserCon, productService : ProductCon, orderService : OrderCon) + # addProduct(scanner : Scanner) + + main(args : String[]) {static} + # placeOrder(scanner : Scanner) + # registerUser(scanner : Scanner) + + run(args : String[]) + } +} +UserCon --> "-userRepository" UserRepo +Orders --> "-user" User +OrderCon --> "-productRepository" ProductRepo +OrderCon --> "-userRepository" UserRepo +OrderCon --> "-orderRepository" OrderRepo +EcommerceApp --> "-userService" UserCon +EcommerceApp --> "-productService" ProductCon +ProductCon --> "-productRepository" ProductRepo +Orders --> "-product" Products +EcommerceApp --> "-orderService" OrderCon +@enduml \ No newline at end of file diff --git a/monolithic-architecture/etc/monolithic-architecture-flowchart.png b/monolithic-architecture/etc/monolithic-architecture-flowchart.png new file mode 100644 index 000000000000..179faaf7909b Binary files /dev/null and b/monolithic-architecture/etc/monolithic-architecture-flowchart.png differ diff --git a/monolithic-architecture/etc/monolithic-architecture-mind-map.png b/monolithic-architecture/etc/monolithic-architecture-mind-map.png new file mode 100644 index 000000000000..cf955379806a Binary files /dev/null and b/monolithic-architecture/etc/monolithic-architecture-mind-map.png differ diff --git a/monolithic-architecture/etc/monolithic-architecture.urm.puml b/monolithic-architecture/etc/monolithic-architecture.urm.puml new file mode 100644 index 000000000000..73e7506373b3 --- /dev/null +++ b/monolithic-architecture/etc/monolithic-architecture.urm.puml @@ -0,0 +1,123 @@ +@startuml +package com.iluwatar.monolithic.model { + class Orders { + - id : Long + - product : Products + - quantity : Integer + - totalPrice : Double + - user : User + + Orders() + + Orders(id : Long, user : User, product : Products, quantity : Integer, totalPrice : Double) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getId() : Long + + getProduct() : Products + + getQuantity() : Integer + + getTotalPrice() : Double + + getUser() : User + + hashCode() : int + + setId(id : Long) + + setProduct(product : Products) + + setQuantity(quantity : Integer) + + setTotalPrice(totalPrice : Double) + + setUser(user : User) + + toString() : String + } + class Products { + - description : String + - id : Long + - name : String + - price : Double + - stock : Integer + + Products() + + Products(id : Long, name : String, description : String, price : Double, stock : Integer) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getDescription() : String + + getId() : Long + + getName() : String + + getPrice() : Double + + getStock() : Integer + + hashCode() : int + + setDescription(description : String) + + setId(id : Long) + + setName(name : String) + + setPrice(price : Double) + + setStock(stock : Integer) + + toString() : String + } + class User { + - email : String + - id : Long + - name : String + - password : String + + User() + + User(id : Long, name : String, email : String, password : String) + # canEqual(other : Object) : boolean + + equals(o : Object) : boolean + + getEmail() : String + + getId() : Long + + getName() : String + + getPassword() : String + + hashCode() : int + + setEmail(email : String) + + setId(id : Long) + + setName(name : String) + + setPassword(password : String) + + toString() : String + } +} +package com.iluwatar.monolithic.repository { + interface OrderRepo { + } + interface ProductRepo { + } + interface UserRepo { + + findByEmail(String) : User {abstract} + } +} +package com.iluwatar.monolithic.controller { + class OrderCon { + - orderRepository : OrderRepo + - productRepository : ProductRepo + - userRepository : UserRepo + + OrderCon(orderRepository : OrderRepo, userRepository : UserRepo, productRepository : ProductRepo) + + placeOrder(userId : Long, productId : Long, quantity : Integer) : Orders + } + class ProductCon { + - productRepository : ProductRepo + + ProductCon(productRepository : ProductRepo) + + addProduct(product : Products) : Products + + getAllProducts() : List + } + class UserCon { + - userRepository : UserRepo + + UserCon(userRepository : UserRepo) + + registerUser(user : User) : User + } +} +package com.iluwatar.monolithic { + class EcommerceApp { + - log : Logger {static} + - orderService : OrderCon + - productService : ProductCon + - userService : UserCon + + EcommerceApp(userService : UserCon, productService : ProductCon, orderService : OrderCon) + # addProduct(scanner : Scanner) + + main(args : String[]) {static} + # placeOrder(scanner : Scanner) + # registerUser(scanner : Scanner) + + run(args : String[]) + } +} +UserCon --> "-userRepository" UserRepo +Orders --> "-user" User +OrderCon --> "-productRepository" ProductRepo +OrderCon --> "-userRepository" UserRepo +OrderCon --> "-orderRepository" OrderRepo +EcommerceApp --> "-userService" UserCon +Orders --> "-product" Products +ProductCon --> "-productRepository" ProductRepo +EcommerceApp --> "-productService" ProductCon +EcommerceApp --> "-orderService" OrderCon +@enduml \ No newline at end of file diff --git a/monolithic-architecture/etc/mvc-architecture-diagram.png b/monolithic-architecture/etc/mvc-architecture-diagram.png new file mode 100644 index 000000000000..33371d41f22c Binary files /dev/null and b/monolithic-architecture/etc/mvc-architecture-diagram.png differ diff --git a/monolithic-architecture/pom.xml b/monolithic-architecture/pom.xml new file mode 100644 index 000000000000..c8a15f80cdc4 --- /dev/null +++ b/monolithic-architecture/pom.xml @@ -0,0 +1,107 @@ + + + + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + monolithic-architecture + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + com.h2database + h2 + runtime + + + + + jakarta.persistence + jakarta.persistence-api + 3.2.0 + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + org.mockito + mockito-core + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.monolithic.EcommerceApp + + + + jar-with-dependencies + + + + + + + + + + diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/EcommerceApp.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/EcommerceApp.java new file mode 100644 index 000000000000..148dfa1760a2 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/EcommerceApp.java @@ -0,0 +1,152 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic; + +import com.iluwatar.monolithic.controller.OrderController; +import com.iluwatar.monolithic.controller.ProductController; +import com.iluwatar.monolithic.controller.UserController; +import com.iluwatar.monolithic.model.Product; +import com.iluwatar.monolithic.model.User; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Main entry point for the Monolithic E-commerce application. + * ------------------------------------------------------------------------ Monolithic architecture + * is a software design pattern where all components of the application (presentation, business + * logic, and data access layers) are part of a single unified codebase and deployable unit. + * ------------------------------------------------------------------------ This example implements + * a monolithic architecture by integrating user management, product management, and order placement + * within the same application, sharing common resources and a single database. + */ +@SpringBootApplication +public class EcommerceApp implements CommandLineRunner { + + private static final Logger log = LogManager.getLogger(EcommerceApp.class); + private final UserController userService; + private final ProductController productService; + private final OrderController orderService; + + /** Initilizing controllers as services. */ + public EcommerceApp( + UserController userService, ProductController productService, OrderController orderService) { + this.userService = userService; + this.productService = productService; + this.orderService = orderService; + } + + /** + * The main entry point for the Monolithic E-commerce application. Initializes the Spring Boot + * application and starts the embedded server. + */ + public static void main(String... args) { + SpringApplication.run(EcommerceApp.class, args); + } + + @Override + public void run(String... args) { + Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8); + + log.info("Welcome to the Monolithic E-commerce CLI!"); + while (true) { + log.info("\nChoose an option:"); + log.info("1. Register User"); + log.info("2. Add Product"); + log.info("3. Place Order"); + log.info("4. Exit"); + log.info("Enter your choice: "); + + int userInput = scanner.nextInt(); + scanner.nextLine(); + + switch (userInput) { + case 1 -> registerUser(scanner); + case 2 -> addProduct(scanner); + case 3 -> placeOrder(scanner); + case 4 -> { + log.info("Exiting the application. Goodbye!"); + return; + } + default -> log.info("Invalid choice! Please try again."); + } + } + } + + /** Handles User Registration through user CLI inputs. */ + protected void registerUser(Scanner scanner) { + log.info("Enter user details:"); + log.info("Name: "); + String name = scanner.nextLine(); + log.info("Email: "); + String email = scanner.nextLine(); + log.info("Password: "); + String password = scanner.nextLine(); + + User user = new User(null, name, email, password); + userService.registerUser(user); + log.info("User registered successfully!"); + } + + /** Handles the addition of products. */ + protected void addProduct(Scanner scanner) { + log.info("Enter product details:"); + log.info("Name: "); + String name = scanner.nextLine(); + log.info("Description: "); + String description = scanner.nextLine(); + log.info("Price: "); + double price = scanner.nextDouble(); + log.info("Stock: "); + int stock = scanner.nextInt(); + Product product = new Product(null, name, description, price, stock); + scanner.nextLine(); + productService.addProduct(product); + log.info("Product added successfully!"); + } + + /** Handles Order Placement through user CLI inputs. */ + protected void placeOrder(Scanner scanner) { + log.info("Enter order details:"); + log.info("User ID: "); + long userId = scanner.nextLong(); + log.info("Product ID: "); + long productId = scanner.nextLong(); + log.info("Quantity: "); + int quantity = scanner.nextInt(); + scanner.nextLine(); + + try { + orderService.placeOrder(userId, productId, quantity); + log.info("Order placed successfully!"); + } catch (Exception e) { + log.info("Error placing order: {}", e.getMessage()); + } + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/OrderController.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/OrderController.java new file mode 100644 index 000000000000..94776a0d23b7 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/OrderController.java @@ -0,0 +1,80 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.controller; + +import com.iluwatar.monolithic.exceptions.InsufficientStockException; +import com.iluwatar.monolithic.exceptions.NonExistentProductException; +import com.iluwatar.monolithic.exceptions.NonExistentUserException; +import com.iluwatar.monolithic.model.Order; +import com.iluwatar.monolithic.model.Product; +import com.iluwatar.monolithic.model.User; +import com.iluwatar.monolithic.repository.OrderRepository; +import com.iluwatar.monolithic.repository.ProductRepository; +import com.iluwatar.monolithic.repository.UserRepository; +import org.springframework.stereotype.Service; + +/** OrderController is a controller class for managing Order operations. */ +@Service +public class OrderController { + private final OrderRepository orderRepository; + private final UserRepository userRepository; + private final ProductRepository productRepository; + + /** This function handles the initializing of the controller. */ + public OrderController( + OrderRepository orderRepository, + UserRepository userRepository, + ProductRepository productRepository) { + this.orderRepository = orderRepository; + this.userRepository = userRepository; + this.productRepository = productRepository; + } + + /** This function handles placing orders with all of its cases. */ + public Order placeOrder(Long userId, Long productId, Integer quantity) { + final User user = + userRepository + .findById(userId) + .orElseThrow( + () -> new NonExistentUserException("User with ID " + userId + " not found")); + + final Product product = + productRepository + .findById(productId) + .orElseThrow( + () -> + new NonExistentProductException("Product with ID " + productId + " not found")); + + if (product.getStock() < quantity) { + throw new InsufficientStockException("Not enough stock for product " + productId); + } + + product.setStock(product.getStock() - quantity); + productRepository.save(product); + + final Order order = new Order(null, user, product, quantity, product.getPrice() * quantity); + return orderRepository.save(order); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/ProductController.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/ProductController.java new file mode 100644 index 000000000000..b409fd8e8eb1 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/ProductController.java @@ -0,0 +1,51 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.controller; + +import com.iluwatar.monolithic.model.Product; +import com.iluwatar.monolithic.repository.ProductRepository; +import java.util.List; +import org.springframework.stereotype.Service; + +/** ProductCon is a controller class for managing Product operations. */ +@Service +public class ProductController { + private final ProductRepository productRepository; + + /** Linking Controller to DB. */ + public ProductController(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + /** Adds a product to the DB. */ + public Product addProduct(Product product) { + return productRepository.save(product); + } + + /** Returns all relevant Product. */ + public List getAllProducts() { + return productRepository.findAll(); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/UserController.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/UserController.java new file mode 100644 index 000000000000..a4fe6dbe363e --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/controller/UserController.java @@ -0,0 +1,45 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.controller; + +import com.iluwatar.monolithic.model.User; +import com.iluwatar.monolithic.repository.UserRepository; +import org.springframework.stereotype.Service; + +/** UserController is a controller class for managing user operations. */ +@Service +public class UserController { + private final UserRepository userRepository; + + /** Linking Controller to DB. */ + public UserController(UserRepository userRepository) { + this.userRepository = userRepository; + } + + /** Adds a user to the DB. */ + public User registerUser(User user) { + return userRepository.save(user); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/InsufficientStockException.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/InsufficientStockException.java new file mode 100644 index 000000000000..d5fd9edfcf75 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/InsufficientStockException.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.exceptions; + +import java.io.Serial; + +/** Custom Exception class for enhanced readability. */ +public class InsufficientStockException extends RuntimeException { + @Serial private static final long serialVersionUID = 1005208208127745099L; + + /** + * Exception Constructor that is readable through code and provides the message inputted into it. + */ + public InsufficientStockException(String message) { + super(message); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentProductException.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentProductException.java new file mode 100644 index 000000000000..63c4821d946b --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentProductException.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.exceptions; + +import java.io.Serial; + +/** Custom Exception class for enhanced readability. */ +public class NonExistentProductException extends RuntimeException { + @Serial private static final long serialVersionUID = -593425162052345565L; + + /** + * Exception Constructor that is readable through code and provides the message inputted into it. + */ + public NonExistentProductException(String msg) { + super(msg); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentUserException.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentUserException.java new file mode 100644 index 000000000000..99625ad7b324 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/exceptions/NonExistentUserException.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.exceptions; + +import java.io.Serial; + +/** Custom Exception class for enhanced readability. */ +public class NonExistentUserException extends RuntimeException { + @Serial private static final long serialVersionUID = -7660909426227843633L; + + /** + * Exception Constructor that is readable through code and provides the message inputted into it. + */ + public NonExistentUserException(String msg) { + super(msg); + } +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Order.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Order.java new file mode 100644 index 000000000000..91caaf9c7ede --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Order.java @@ -0,0 +1,53 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Represents a Database in which Order are stored. */ +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne private User user; + + @ManyToOne private Product product; + + private Integer quantity; + + private Double totalPrice; +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Product.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Product.java new file mode 100644 index 000000000000..bcfe52c44572 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/Product.java @@ -0,0 +1,52 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Represents a database of products. */ +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + private String description; + + private Double price; + + private Integer stock; +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/User.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/User.java new file mode 100644 index 000000000000..0b05f447c924 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/model/User.java @@ -0,0 +1,52 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** Represents a Product entity for the database. */ +@Entity +@Data +@NoArgsConstructor +@AllArgsConstructor +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + + @Column(unique = true) + private String email; + + private String password; +} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/OrderRepository.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/OrderRepository.java new file mode 100644 index 000000000000..af38c56f1315 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/OrderRepository.java @@ -0,0 +1,31 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.repository; + +import com.iluwatar.monolithic.model.Order; +import org.springframework.data.jpa.repository.JpaRepository; + +/** This interface allows JpaRepository to generate queries for the required tables. */ +public interface OrderRepository extends JpaRepository {} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/ProductRepository.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/ProductRepository.java new file mode 100644 index 000000000000..660ed33bb9fe --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/ProductRepository.java @@ -0,0 +1,31 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.repository; + +import com.iluwatar.monolithic.model.Product; +import org.springframework.data.jpa.repository.JpaRepository; + +/** This interface allows JpaRepository to generate queries for the required tables. */ +public interface ProductRepository extends JpaRepository {} diff --git a/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/UserRepository.java b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/UserRepository.java new file mode 100644 index 000000000000..e05c688169d0 --- /dev/null +++ b/monolithic-architecture/src/main/java/com/iluwatar/monolithic/repository/UserRepository.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic.repository; + +import com.iluwatar.monolithic.model.User; +import org.springframework.data.jpa.repository.JpaRepository; + +/** This interface allows JpaRepository to generate queries for the required tables. */ +public interface UserRepository extends JpaRepository { + /** + * Utilizes JpaRepository functionalities to generate a function which looks up in the User table + * using emails. + */ + User findByEmail(String email); +} diff --git a/monolithic-architecture/src/main/resources/application.properties b/monolithic-architecture/src/main/resources/application.properties new file mode 100644 index 000000000000..166f3dafd7ac --- /dev/null +++ b/monolithic-architecture/src/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.password=password +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.username=admin +spring.h2.console.enabled=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true diff --git a/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java b/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java new file mode 100644 index 000000000000..967b86e7080f --- /dev/null +++ b/monolithic-architecture/src/test/java/com/iluwatar/monolithic/MonolithicAppTest.java @@ -0,0 +1,267 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.monolithic; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import com.iluwatar.monolithic.controller.OrderController; +import com.iluwatar.monolithic.controller.ProductController; +import com.iluwatar.monolithic.controller.UserController; +import com.iluwatar.monolithic.exceptions.InsufficientStockException; +import com.iluwatar.monolithic.exceptions.NonExistentProductException; +import com.iluwatar.monolithic.exceptions.NonExistentUserException; +import com.iluwatar.monolithic.model.Order; +import com.iluwatar.monolithic.model.Product; +import com.iluwatar.monolithic.model.User; +import com.iluwatar.monolithic.repository.OrderRepository; +import com.iluwatar.monolithic.repository.ProductRepository; +import com.iluwatar.monolithic.repository.UserRepository; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.Optional; +import java.util.Scanner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +class MonolithicAppTest { + + @Mock private UserController userService; + + @Mock private ProductController productService; + + @Mock private OrderController orderService; + + private EcommerceApp ecommerceApp; + + private ByteArrayOutputStream outputStream; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + ecommerceApp = new EcommerceApp(userService, productService, orderService); + outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream, true, StandardCharsets.UTF_8)); + Locale.setDefault(Locale.US); + } + + @Test + void testRegisterUser() { + String simulatedInput = "John Doe\njohn@example.com\npassword123\n"; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + ecommerceApp.registerUser(new Scanner(System.in, StandardCharsets.UTF_8)); + + verify(userService, times(1)).registerUser(any(User.class)); + assertTrue(outputStream.toString().contains("User registered successfully!")); + } + + @Test + void testPlaceOrderUserNotFound() { + UserRepository mockUserRepository = mock(UserRepository.class); + ProductRepository mockProductRepository = mock(ProductRepository.class); + OrderRepository mockOrderRepo = mock(OrderRepository.class); + + when(mockUserRepository.findById(1L)).thenReturn(Optional.empty()); + + OrderController orderCon = + new OrderController(mockOrderRepo, mockUserRepository, mockProductRepository); + + Exception exception = + assertThrows(NonExistentUserException.class, () -> orderCon.placeOrder(1L, 1L, 5)); + + assertEquals("User with ID 1 not found", exception.getMessage()); + } + + @Test + void testPlaceOrderProductNotFound() { + UserRepository mockUserRepository = mock(UserRepository.class); + ProductRepository mockProductRepository = mock(ProductRepository.class); + OrderRepository mockOrderRepository = mock(OrderRepository.class); + + User mockUser = new User(1L, "John Doe", "john@example.com", "password123"); + when(mockUserRepository.findById(1L)).thenReturn(Optional.of(mockUser)); + + when(mockProductRepository.findById(1L)).thenReturn(Optional.empty()); + + OrderController orderCon = + new OrderController(mockOrderRepository, mockUserRepository, mockProductRepository); + + Exception exception = + assertThrows(NonExistentProductException.class, () -> orderCon.placeOrder(1L, 1L, 5)); + + assertEquals("Product with ID 1 not found", exception.getMessage()); + } + + @Test + void testOrderConstructor() { + OrderRepository mockOrderRepository = mock(OrderRepository.class); + UserRepository mockUserRepository = mock(UserRepository.class); + ProductRepository mockProductRepository = mock(ProductRepository.class); + + OrderController orderCon = + new OrderController(mockOrderRepository, mockUserRepository, mockProductRepository); + + assertNotNull(orderCon); + } + + @Test + void testAddProduct() { + String simulatedInput = "Laptop\nGaming Laptop\n1200.50\n10\n"; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + ecommerceApp.addProduct(new Scanner(System.in, StandardCharsets.UTF_8)); + + verify(productService, times(1)).addProduct(any(Product.class)); + assertTrue(outputStream.toString().contains("Product added successfully!")); + } + + @Test + void testPlaceOrderSuccess() { + String simulatedInput = "1\n2\n3\n"; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + Order mockOrder = new Order(); + doReturn(mockOrder).when(orderService).placeOrder(anyLong(), anyLong(), anyInt()); + + ecommerceApp.placeOrder(new Scanner(System.in, StandardCharsets.UTF_8)); + + verify(orderService, times(1)).placeOrder(anyLong(), anyLong(), anyInt()); + assertTrue(outputStream.toString().contains("Order placed successfully!")); + } + + @Test + void testPlaceOrderFailure() { + String simulatedInput = "1\n2\n3\n"; + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + doThrow(new RuntimeException("Product out of stock")) + .when(orderService) + .placeOrder(anyLong(), anyLong(), anyInt()); + + ecommerceApp.placeOrder(new Scanner(System.in, StandardCharsets.UTF_8)); + + verify(orderService, times(1)).placeOrder(anyLong(), anyLong(), anyInt()); + assertTrue(outputStream.toString().contains("Error placing order: Product out of stock")); + } + + @Test + void testPlaceOrderInsufficientStock() { + UserRepository mockUserRepository = mock(UserRepository.class); + ProductRepository mockProductRepository = mock(ProductRepository.class); + OrderRepository mockOrderRepository = mock(OrderRepository.class); + + User mockUser = new User(1L, "John Doe", "john@example.com", "password123"); + when(mockUserRepository.findById(1L)).thenReturn(Optional.of(mockUser)); + Product mockProduct = + new Product(1L, "Laptop", "High-end gaming laptop", 1500.00, 2); // Only 2 in stock + when(mockProductRepository.findById(1L)).thenReturn(Optional.of(mockProduct)); + + OrderController orderCon = + new OrderController(mockOrderRepository, mockUserRepository, mockProductRepository); + + Exception exception = + assertThrows(InsufficientStockException.class, () -> orderCon.placeOrder(1L, 1L, 5)); + assertEquals("Not enough stock for product 1", exception.getMessage()); + } + + @Test + void testProductConAddProduct() { + ProductRepository mockProductRepository = mock(ProductRepository.class); + + Product mockProduct = new Product(1L, "Smartphone", "High-end smartphone", 1000.00, 20); + + when(mockProductRepository.save(any(Product.class))).thenReturn(mockProduct); + + ProductController productController = new ProductController(mockProductRepository); + + Product savedProduct = productController.addProduct(mockProduct); + + verify(mockProductRepository, times(1)).save(any(Product.class)); + + assertNotNull(savedProduct); + assertEquals("Smartphone", savedProduct.getName()); + assertEquals("High-end smartphone", savedProduct.getDescription()); + assertEquals(1000.00, savedProduct.getPrice()); + assertEquals(20, savedProduct.getStock()); + } + + @Test + void testRun() { + String simulatedInput = + """ + 1 + John Doe + john@example.com + password123 + 2 + Laptop + Gaming Laptop + 1200.50 + 10 + 3 + 1 + 1 + 2 + 4 + """; // Exit + System.setIn(new ByteArrayInputStream(simulatedInput.getBytes(StandardCharsets.UTF_8))); + + ByteArrayOutputStream outputTest = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputTest, true, StandardCharsets.UTF_8)); + + when(userService.registerUser(any(User.class))) + .thenReturn(new User(1L, "John Doe", "john@example.com", "password123")); + when(productService.addProduct(any(Product.class))) + .thenReturn(new Product(1L, "Laptop", "Gaming Laptop", 1200.50, 10)); + when(orderService.placeOrder(anyLong(), anyLong(), anyInt())) + .thenReturn( + new Order( + 1L, + new User(1L, "John Doe", "john@example.com", "password123"), + new Product(1L, "Laptop", "Gaming Laptop", 1200.50, 10), + 5, + 6002.50)); + + ecommerceApp.run(); + + verify(userService, times(1)).registerUser(any(User.class)); + verify(productService, times(1)).addProduct(any(Product.class)); + verify(orderService, times(1)).placeOrder(anyLong(), anyLong(), anyInt()); + + String output = outputTest.toString(StandardCharsets.UTF_8); + assertTrue(output.contains("Welcome to the Monolithic E-commerce CLI!")); + assertTrue(output.contains("Choose an option:")); + assertTrue(output.contains("Register User")); + assertTrue(output.contains("Add Product")); + assertTrue(output.contains("Place Order")); + assertTrue(output.contains("Exiting the application. Goodbye!")); + } +} diff --git a/monostate/README.md b/monostate/README.md index 33a603502fe4..5a69c4a3dfc5 100644 --- a/monostate/README.md +++ b/monostate/README.md @@ -34,6 +34,10 @@ wiki.c2.com says > A monostate is a "conceptual singleton" - all data members of a monostate are static, so all instances of the monostate use the same (static) data. Applications using a monostate can create any number of instances that they desire, as each instance uses the same data. +Flowchart + +![Monostate flowchart](./etc/monostate-flowchart.png) + ## Programmatic Example of Monostate Pattern in Java The Monostate pattern in Java ensures that all instances of a class share the same state, making it a great Singleton alternative for maintaining consistent data. This is achieved by using static fields in the class. Any changes to these fields will be reflected across all instances of the class. This pattern is useful when you want to avoid global variables but still need a shared state across multiple instances. diff --git a/monostate/etc/monostate-flowchart.png b/monostate/etc/monostate-flowchart.png new file mode 100644 index 000000000000..44e93e200dc0 Binary files /dev/null and b/monostate/etc/monostate-flowchart.png differ diff --git a/monostate/pom.xml b/monostate/pom.xml index a9d3abfafd74..fad3226104be 100644 --- a/monostate/pom.xml +++ b/monostate/pom.xml @@ -34,6 +34,14 @@ monostate + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -45,4 +53,23 @@ test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.monostate.App + + + + + + + + diff --git a/monostate/src/main/java/com/iluwatar/monostate/App.java b/monostate/src/main/java/com/iluwatar/monostate/App.java index 289362f3023f..3c3dab2fcc4d 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/App.java +++ b/monostate/src/main/java/com/iluwatar/monostate/App.java @@ -24,7 +24,6 @@ */ package com.iluwatar.monostate; - /** * The MonoState pattern ensures that all instances of the class will have the same state. This can * be used a direct replacement of the Singleton pattern. @@ -49,5 +48,4 @@ public static void main(String[] args) { loadBalancer1.serverRequest(new Request("Hello")); loadBalancer2.serverRequest(new Request("Hello World")); } - } diff --git a/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java b/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java index 285dc573f38a..b93ae2789e32 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java +++ b/monostate/src/main/java/com/iluwatar/monostate/LoadBalancer.java @@ -33,26 +33,22 @@ * instances of the class share the same state, all instances will delegate to the same server on * receiving a new Request. */ - public class LoadBalancer { private static final List SERVERS = new ArrayList<>(); private static int lastServedId; static { var id = 0; - for (var port : new int[]{8080, 8081, 8082, 8083, 8084}) { + for (var port : new int[] {8080, 8081, 8082, 8083, 8084}) { SERVERS.add(new Server("localhost", port, ++id)); } } - /** - * Add new server. - */ + /** Add new server. */ public final void addServer(Server server) { synchronized (SERVERS) { SERVERS.add(server); } - } public final int getNoOfServers() { @@ -63,9 +59,7 @@ public int getLastServedId() { return lastServedId; } - /** - * Handle request. - */ + /** Handle request. */ public synchronized void serverRequest(Request request) { if (lastServedId >= SERVERS.size()) { lastServedId = 0; @@ -73,5 +67,4 @@ public synchronized void serverRequest(Request request) { var server = SERVERS.get(lastServedId++); server.serve(request); } - } diff --git a/monostate/src/main/java/com/iluwatar/monostate/Request.java b/monostate/src/main/java/com/iluwatar/monostate/Request.java index aa7dc8189a0e..e08f2dd472dd 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/Request.java +++ b/monostate/src/main/java/com/iluwatar/monostate/Request.java @@ -24,8 +24,5 @@ */ package com.iluwatar.monostate; -/** - * The Request record. A {@link Server} can handle an instance of a Request. - */ - +/** The Request record. A {@link Server} can handle an instance of a Request. */ public record Request(String value) {} diff --git a/monostate/src/main/java/com/iluwatar/monostate/Server.java b/monostate/src/main/java/com/iluwatar/monostate/Server.java index 13589c93f749..e0af58a82e63 100644 --- a/monostate/src/main/java/com/iluwatar/monostate/Server.java +++ b/monostate/src/main/java/com/iluwatar/monostate/Server.java @@ -39,9 +39,7 @@ public class Server { public final int port; public final int id; - /** - * Constructor. - */ + /** Constructor. */ public Server(String host, int port, int id) { this.host = host; this.port = port; @@ -49,7 +47,11 @@ public Server(String host, int port, int id) { } public void serve(Request request) { - LOGGER.info("Server ID {} associated to host : {} and port {}. Processed request with value {}", - id, host, port, request.value()); + LOGGER.info( + "Server ID {} associated to host : {} and port {}. Processed request with value {}", + id, + host, + port, + request.value()); } } diff --git a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java index a701787c53e3..82ea4e1952cc 100644 --- a/monostate/src/test/java/com/iluwatar/monostate/AppTest.java +++ b/monostate/src/test/java/com/iluwatar/monostate/AppTest.java @@ -28,15 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application Test Entry - */ - +/** Application Test Entry */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java index f3139b603cbc..10a46e016a67 100644 --- a/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java +++ b/monostate/src/test/java/com/iluwatar/monostate/LoadBalancerTest.java @@ -35,10 +35,7 @@ import org.junit.jupiter.api.Test; -/** - * LoadBalancerTest - * - */ +/** LoadBalancerTest */ class LoadBalancerTest { @Test @@ -71,7 +68,5 @@ void testServe() { verify(server, times(2)).serve(request); verifyNoMoreInteractions(server); - } - } diff --git a/multiton/README.md b/multiton/README.md index 34e37866a7a7..f1df11dc24a9 100644 --- a/multiton/README.md +++ b/multiton/README.md @@ -32,6 +32,10 @@ Wikipedia says > In software engineering, the multiton pattern is a design pattern which generalizes the singleton pattern. Whereas the singleton allows only one instance of a class to be created, the multiton pattern allows for the controlled creation of multiple instances, which it manages through the use of a map. +Flowchart + +![Multiton flowchart](./etc/multiton-flowchart.png) + ## Programmatic Example of Multiton Pattern in Java In this tutorial, we’ll explore how to implement the Multiton pattern in Java, covering its structure, benefits, and providing code examples. By following these implementation tips, you’ll be able to effectively utilize this Java design pattern. diff --git a/multiton/etc/multiton-flowchart.png b/multiton/etc/multiton-flowchart.png new file mode 100644 index 000000000000..bc3c119e62b6 Binary files /dev/null and b/multiton/etc/multiton-flowchart.png differ diff --git a/multiton/pom.xml b/multiton/pom.xml index 6d2430560a5c..f86151ca40f6 100644 --- a/multiton/pom.xml +++ b/multiton/pom.xml @@ -34,6 +34,14 @@ multiton + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java b/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java index 35e7ea337cbc..d1c46bfa9e88 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java +++ b/multiton/src/main/java/com/iluwatar/multiton/Nazgul.java @@ -35,8 +35,7 @@ public final class Nazgul { private static final Map nazguls; - @Getter - private final NazgulName name; + @Getter private final NazgulName name; static { nazguls = new ConcurrentHashMap<>(); diff --git a/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java b/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java index e9fef580337e..bde5c0d4a291 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java +++ b/multiton/src/main/java/com/iluwatar/multiton/NazgulEnum.java @@ -24,9 +24,7 @@ */ package com.iluwatar.multiton; -/** - * enum based multiton implementation. - */ +/** enum based multiton implementation. */ public enum NazgulEnum { KHAMUL, MURAZOR, diff --git a/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java b/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java index 44474efb8ce6..0d9c93cac265 100644 --- a/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java +++ b/multiton/src/main/java/com/iluwatar/multiton/NazgulName.java @@ -24,9 +24,7 @@ */ package com.iluwatar.multiton; -/** - * Each Nazgul has different {@link NazgulName}. - */ +/** Each Nazgul has different {@link NazgulName}. */ public enum NazgulName { KHAMUL, MURAZOR, diff --git a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java index 16fbf361338e..95478068b92a 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/AppTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/AppTest.java @@ -28,14 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Test if the application starts without throwing an exception. - */ - +/** Test if the application starts without throwing an exception. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java b/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java index c514f871be79..916409119ed0 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/NazgulEnumTest.java @@ -29,15 +29,12 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -/** - * NazgulEnumTest - * - */ +/** NazgulEnumTest */ class NazgulEnumTest { /** - * Check that multiple calls to any one of the instances in the multiton returns - * only that one particular instance, and do that for all instances in multiton + * Check that multiple calls to any one of the instances in the multiton returns only that one + * particular instance, and do that for all instances in multiton */ @ParameterizedTest @EnumSource diff --git a/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java index 4c998282f54d..8d7c3bfb844b 100644 --- a/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java +++ b/multiton/src/test/java/com/iluwatar/multiton/NazgulTest.java @@ -30,10 +30,7 @@ import org.junit.jupiter.api.Test; -/** - * NazgulTest - * - */ +/** NazgulTest */ class NazgulTest { /** diff --git a/mute-idiom/README.md b/mute-idiom/README.md index 3999d7ab9991..c75db9facfae 100644 --- a/mute-idiom/README.md +++ b/mute-idiom/README.md @@ -34,6 +34,10 @@ In plain words > The Mute Idiom design pattern suppresses the handling of trivial or non-critical exceptions to simplify code. +Flowchart + +![Mute Idiom flowchart](./etc/mute-idiom-flowchart.png) + ## Programmatic Example of Mute Idiom Pattern in Java In the following Java code example, we demonstrate the Mute Idiom by muting non-critical exceptions during the resource management process. This approach ensures error handling does not interrupt the main logic. diff --git a/mute-idiom/etc/mute-idiom-flowchart.png b/mute-idiom/etc/mute-idiom-flowchart.png new file mode 100644 index 000000000000..846c3b32d460 Binary files /dev/null and b/mute-idiom/etc/mute-idiom-flowchart.png differ diff --git a/mute-idiom/pom.xml b/mute-idiom/pom.xml index 4f521c638c09..40654d7de546 100644 --- a/mute-idiom/pom.xml +++ b/mute-idiom/pom.xml @@ -34,6 +34,14 @@ mute-idiom + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/App.java b/mute-idiom/src/main/java/com/iluwatar/mute/App.java index 341a9bb16d5f..75525731a8d8 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/App.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/App.java @@ -34,6 +34,7 @@ * when all we can do to handle the exception is to log it. This pattern should not be used * everywhere. It is very important to logically handle the exceptions in a system, but some * situations like the ones described above require this pattern, so that we don't need to repeat + * *

      * 
      *   try {
    @@ -42,7 +43,9 @@
      *     // ignore by logging or throw error if unexpected exception occurs
      *   }
      * 
    - * 
    every time we need to ignore an exception. + * + * + * every time we need to ignore an exception. */ @Slf4j public class App { diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java b/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java index 5c8d017b951f..9698f6d125a0 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/CheckedRunnable.java @@ -24,9 +24,7 @@ */ package com.iluwatar.mute; -/** - * A runnable which may throw exception on execution. - */ +/** A runnable which may throw exception on execution. */ @FunctionalInterface public interface CheckedRunnable { /** diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java b/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java index 092d940fa0c1..8eab13a6d5f8 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/Mute.java @@ -28,22 +28,19 @@ import java.io.IOException; import lombok.extern.slf4j.Slf4j; -/** - * A utility class that allows you to utilize mute idiom. - */ +/** A utility class that allows you to utilize mute idiom. */ @Slf4j public final class Mute { // The constructor is never meant to be called. - private Mute() { - } + private Mute() {} /** * Executes the runnable and throws the exception occurred within a {@link * AssertionError}. This method should be utilized to mute the operations that are guaranteed not - * to throw an exception. For instance {@link ByteArrayOutputStream#write(byte[])} declares in - * its signature that it can throw an {@link IOException}, but in reality it cannot. This is - * because the bulk write method is not overridden in {@link ByteArrayOutputStream}. + * to throw an exception. For instance {@link ByteArrayOutputStream#write(byte[])} declares in its + * signature that it can throw an {@link IOException}, but in reality it cannot. This is because + * the bulk write method is not overridden in {@link ByteArrayOutputStream}. * * @param runnable a runnable that should never throw an exception on execution. */ diff --git a/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java b/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java index cb32b27955e7..c4c4c65463eb 100644 --- a/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java +++ b/mute-idiom/src/main/java/com/iluwatar/mute/Resource.java @@ -30,6 +30,4 @@ * Represents any resource that the application might acquire and that must be closed after it is * utilized. Example of such resources can be a database connection, open files, sockets. */ -public interface Resource extends Closeable { - -} +public interface Resource extends Closeable {} diff --git a/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java b/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java index ecfb1dabba77..ef8dff8b0318 100644 --- a/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java +++ b/mute-idiom/src/test/java/com/iluwatar/mute/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.mute; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Mute idiom example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Mute idiom example runs without errors. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java b/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java index 07b17afaced7..06579d454fef 100644 --- a/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java +++ b/mute-idiom/src/test/java/com/iluwatar/mute/MuteTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.mute; +import static org.junit.jupiter.api.Assertions.*; + import java.io.ByteArrayOutputStream; import java.io.PrintStream; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Test for the mute-idiom pattern - */ +/** Test for the mute-idiom pattern */ class MuteTest { private static final Logger LOGGER = LoggerFactory.getLogger(MuteTest.class); @@ -42,7 +40,8 @@ class MuteTest { private static final String MESSAGE = "should not occur"; @Test - void muteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { + void + muteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { assertDoesNotThrow(() -> Mute.mute(this::methodNotThrowingAnyException)); } @@ -52,7 +51,8 @@ void muteShouldRethrowUnexpectedExceptionAsAssertionError() { } @Test - void loggedMuteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { + void + loggedMuteShouldRunTheCheckedRunnableAndNotThrowAnyExceptionIfCheckedRunnableDoesNotThrowAnyException() { assertDoesNotThrow(() -> Mute.mute(this::methodNotThrowingAnyException)); } diff --git a/naked-objects/README.md b/naked-objects/README.md index b96db41cd8ed..0b9b81fbd972 100644 --- a/naked-objects/README.md +++ b/naked-objects/README.md @@ -44,6 +44,11 @@ Wikipedia says > > The naked object pattern's innovative feature arises by combining the 1st and 2nd principles into a 3rd principle: 3. The user interface shall be entirely automatically created from the definitions of the domain objects. This may be done using reflection or source code generation. +Architecture diagram + +![Naked Objects Architecture Diagram](./etc/naked-objects-architecture-diagram.png) + + ## Programmatic Example of Naked Objects Pattern in Java Consider a simplified example with domain objects representing books and authors. In a Java-based application using the Naked Objects pattern, we define domain objects such as `Book` and `Author`. This example illustrates how Naked Objects can streamline user interface generation and domain object manipulation. diff --git a/naked-objects/etc/naked-objects-architecture-diagram.png b/naked-objects/etc/naked-objects-architecture-diagram.png new file mode 100644 index 000000000000..d11cd92ed784 Binary files /dev/null and b/naked-objects/etc/naked-objects-architecture-diagram.png differ diff --git a/notification/README.md b/notification/README.md index e94421ff504e..04feb01cb814 100644 --- a/notification/README.md +++ b/notification/README.md @@ -32,6 +32,10 @@ In plain words > The Notification design pattern enables an object to automatically notify a list of interested observers about changes or events without knowing the specifics of the subscribers. +Sequence diagram + +![Notification sequence diagram](./etc/notification-sequence-diagram.png) + ## Programmatic Example of Notification Pattern in Java The Java Notification pattern is used to capture information passed between layers, validate the information, and return any errors to the presentation layer if needed. It reduces coupling between the producer and consumer of events, enhances flexibility and reusability of components, and allows for dynamic event subscription and unsubscription. diff --git a/notification/etc/notification-sequence-diagram.png b/notification/etc/notification-sequence-diagram.png new file mode 100644 index 000000000000..ec9c25992fa2 Binary files /dev/null and b/notification/etc/notification-sequence-diagram.png differ diff --git a/notification/pom.xml b/notification/pom.xml index 760169ef8b72..829c9014919a 100644 --- a/notification/pom.xml +++ b/notification/pom.xml @@ -35,6 +35,14 @@ 1.26.0-SNAPSHOT + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-params diff --git a/notification/src/main/java/com/iluwatar/App.java b/notification/src/main/java/com/iluwatar/App.java index f62a62c32160..fdf041929230 100644 --- a/notification/src/main/java/com/iluwatar/App.java +++ b/notification/src/main/java/com/iluwatar/App.java @@ -27,14 +27,14 @@ import java.time.LocalDate; /** - * The notification pattern captures information passed between layers, validates the information, and returns - * any errors to the presentation layer if needed. + * The notification pattern captures information passed between layers, validates the information, + * and returns any errors to the presentation layer if needed. * - *

    In this code, this pattern is implemented through the example of a form being submitted to register - * a worker. The worker inputs their name, occupation, and date of birth to the RegisterWorkerForm (which acts - * as our presentation layer), and passes it to the RegisterWorker class (our domain layer) which validates it. - * Any errors caught by the domain layer are then passed back to the presentation layer through the - * RegisterWorkerDto.

    + *

    In this code, this pattern is implemented through the example of a form being submitted to + * register a worker. The worker inputs their name, occupation, and date of birth to the + * RegisterWorkerForm (which acts as our presentation layer), and passes it to the RegisterWorker + * class (our domain layer) which validates it. Any errors caught by the domain layer are then + * passed back to the presentation layer through the RegisterWorkerDto. */ public class App { @@ -46,5 +46,4 @@ public static void main(String[] args) { var form = new RegisterWorkerForm(NAME, OCCUPATION, DATE_OF_BIRTH); form.submit(); } - } diff --git a/notification/src/main/java/com/iluwatar/DataTransferObject.java b/notification/src/main/java/com/iluwatar/DataTransferObject.java index 11d648d16b42..e061a9976838 100644 --- a/notification/src/main/java/com/iluwatar/DataTransferObject.java +++ b/notification/src/main/java/com/iluwatar/DataTransferObject.java @@ -28,13 +28,12 @@ import lombok.NoArgsConstructor; /** - * Layer super type for all Data Transfer Objects. - * Also contains code for accessing our notification. + * Layer super type for all Data Transfer Objects. Also contains code for accessing our + * notification. */ @Getter @NoArgsConstructor public class DataTransferObject { private final Notification notification = new Notification(); - } diff --git a/notification/src/main/java/com/iluwatar/Notification.java b/notification/src/main/java/com/iluwatar/Notification.java index 47f36f5ee129..dc8a40ea7622 100644 --- a/notification/src/main/java/com/iluwatar/Notification.java +++ b/notification/src/main/java/com/iluwatar/Notification.java @@ -30,9 +30,8 @@ import lombok.NoArgsConstructor; /** - * The notification. Used for storing errors and any other methods - * that may be necessary for when we send information back to the - * presentation layer. + * The notification. Used for storing errors and any other methods that may be necessary for when we + * send information back to the presentation layer. */ @Getter @NoArgsConstructor diff --git a/notification/src/main/java/com/iluwatar/NotificationError.java b/notification/src/main/java/com/iluwatar/NotificationError.java index a243a383b9f0..29aa0fd13398 100644 --- a/notification/src/main/java/com/iluwatar/NotificationError.java +++ b/notification/src/main/java/com/iluwatar/NotificationError.java @@ -28,8 +28,8 @@ import lombok.Getter; /** - * Error class for storing information on the error. - * Error ID is not necessary, but may be useful for serialisation. + * Error class for storing information on the error. Error ID is not necessary, but may be useful + * for serialisation. */ @Getter @AllArgsConstructor diff --git a/notification/src/main/java/com/iluwatar/RegisterWorker.java b/notification/src/main/java/com/iluwatar/RegisterWorker.java index 9de39344b847..5952010e3c88 100644 --- a/notification/src/main/java/com/iluwatar/RegisterWorker.java +++ b/notification/src/main/java/com/iluwatar/RegisterWorker.java @@ -29,19 +29,18 @@ import lombok.extern.slf4j.Slf4j; /** - * Class which handles actual internal logic and validation for worker registration. - * Part of the domain layer which collects information and sends it back to the presentation. + * Class which handles actual internal logic and validation for worker registration. Part of the + * domain layer which collects information and sends it back to the presentation. */ @Slf4j public class RegisterWorker extends ServerCommand { static final int LEGAL_AGE = 18; + protected RegisterWorker(RegisterWorkerDto worker) { super(worker); } - /** - * Validates the data provided and adds it to the database in the backend. - */ + /** Validates the data provided and adds it to the database in the backend. */ public void run() { validate(); @@ -50,12 +49,10 @@ public void run() { } } - /** - * Validates our data. Checks for any errors and if found, adds to notification. - */ + /** Validates our data. Checks for any errors and if found, adds to notification. */ private void validate() { var ourData = ((RegisterWorkerDto) this.data); - //check if any of submitted data is not given + // check if any of submitted data is not given // passing for empty value validation fail(isNullOrBlank(ourData.getName()), RegisterWorkerDto.MISSING_NAME); fail(isNullOrBlank(ourData.getOccupation()), RegisterWorkerDto.MISSING_OCCUPATION); @@ -93,7 +90,7 @@ protected boolean isNullOrBlank(Object obj) { * If a condition is met, adds the error to our notification. * * @param condition condition to check for. - * @param error error to add if condition met. + * @param error error to add if condition met. */ protected void fail(boolean condition, NotificationError error) { if (condition) { diff --git a/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java b/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java index 7050c54dc6f4..c1fbadf9d107 100644 --- a/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java +++ b/notification/src/main/java/com/iluwatar/RegisterWorkerDto.java @@ -29,8 +29,8 @@ import lombok.Setter; /** - * Data transfer object which stores information about the worker. This is carried between - * objects and layers to reduce the number of method calls made. + * Data transfer object which stores information about the worker. This is carried between objects + * and layers to reduce the number of method calls made. */ @Getter @Setter @@ -39,30 +39,20 @@ public class RegisterWorkerDto extends DataTransferObject { private String occupation; private LocalDate dateOfBirth; - /** - * Error for when name field is blank or missing. - */ - public static final NotificationError MISSING_NAME = - new NotificationError(1, "Name is missing"); + /** Error for when name field is blank or missing. */ + public static final NotificationError MISSING_NAME = new NotificationError(1, "Name is missing"); - /** - * Error for when occupation field is blank or missing. - */ + /** Error for when occupation field is blank or missing. */ public static final NotificationError MISSING_OCCUPATION = - new NotificationError(2, "Occupation is missing"); + new NotificationError(2, "Occupation is missing"); - /** - * Error for when date of birth field is blank or missing. - */ + /** Error for when date of birth field is blank or missing. */ public static final NotificationError MISSING_DOB = - new NotificationError(3, "Date of birth is missing"); + new NotificationError(3, "Date of birth is missing"); - /** - * Error for when date of birth is less than 18 years ago. - */ + /** Error for when date of birth is less than 18 years ago. */ public static final NotificationError DOB_TOO_SOON = - new NotificationError(4, "Worker registered must be over 18"); - + new NotificationError(4, "Worker registered must be over 18"); protected RegisterWorkerDto() { super(); @@ -71,8 +61,8 @@ protected RegisterWorkerDto() { /** * Simple set up function for capturing our worker information. * - * @param name Name of the worker - * @param occupation occupation of the worker + * @param name Name of the worker + * @param occupation occupation of the worker * @param dateOfBirth Date of Birth of the worker */ public void setupWorkerDto(String name, String occupation, LocalDate dateOfBirth) { diff --git a/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java b/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java index 6641919936e5..c1a5092f928b 100644 --- a/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java +++ b/notification/src/main/java/com/iluwatar/RegisterWorkerForm.java @@ -28,9 +28,8 @@ import lombok.extern.slf4j.Slf4j; /** - * The form submitted by the user, part of the presentation layer, - * linked to the domain layer through a data transfer object and - * linked to the service layer directly. + * The form submitted by the user, part of the presentation layer, linked to the domain layer + * through a data transfer object and linked to the service layer directly. */ @Slf4j public class RegisterWorkerForm { @@ -41,10 +40,10 @@ public class RegisterWorkerForm { RegisterWorkerService service = new RegisterWorkerService(); /** - * Constructor. + * Constructor. * - * @param name Name of the worker - * @param occupation occupation of the worker + * @param name Name of the worker + * @param occupation occupation of the worker * @param dateOfBirth Date of Birth of the worker */ public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) { @@ -53,16 +52,14 @@ public RegisterWorkerForm(String name, String occupation, LocalDate dateOfBirth) this.dateOfBirth = dateOfBirth; } - /** - * Attempts to submit the form for registering a worker. - */ + /** Attempts to submit the form for registering a worker. */ public void submit() { - //Transmit information to our transfer object to communicate between layers + // Transmit information to our transfer object to communicate between layers saveToWorker(); - //call the service layer to register our worker + // call the service layer to register our worker service.registerWorker(worker); - //check for any errors + // check for any errors if (worker.getNotification().hasErrors()) { indicateErrors(); LOGGER.info("Not registered, see errors"); @@ -71,9 +68,7 @@ public void submit() { } } - /** - * Saves worker information to the data transfer object. - */ + /** Saves worker information to the data transfer object. */ private void saveToWorker() { worker = new RegisterWorkerDto(); worker.setName(name); @@ -81,9 +76,7 @@ private void saveToWorker() { worker.setDateOfBirth(dateOfBirth); } - /** - * Check for any errors with form submission and show them to the user. - */ + /** Check for any errors with form submission and show them to the user. */ public void indicateErrors() { worker.getNotification().getErrors().forEach(error -> LOGGER.error(error.toString())); } diff --git a/notification/src/main/java/com/iluwatar/RegisterWorkerService.java b/notification/src/main/java/com/iluwatar/RegisterWorkerService.java index aed1629716a6..a7742739510b 100644 --- a/notification/src/main/java/com/iluwatar/RegisterWorkerService.java +++ b/notification/src/main/java/com/iluwatar/RegisterWorkerService.java @@ -25,13 +25,13 @@ package com.iluwatar; /** - * Service used to register a worker. - * This represents the basic framework of a service layer which can be built upon. + * Service used to register a worker. This represents the basic framework of a service layer which + * can be built upon. */ public class RegisterWorkerService { /** - * Creates and runs a command object to do the work needed, - * in this case, register a worker in the system. + * Creates and runs a command object to do the work needed, in this case, register a worker in the + * system. * * @param registration worker to be registered if possible */ diff --git a/notification/src/main/java/com/iluwatar/ServerCommand.java b/notification/src/main/java/com/iluwatar/ServerCommand.java index 69af66cc0378..6dd1357cc888 100644 --- a/notification/src/main/java/com/iluwatar/ServerCommand.java +++ b/notification/src/main/java/com/iluwatar/ServerCommand.java @@ -27,8 +27,8 @@ import lombok.AllArgsConstructor; /** - * Stores the dto and access the notification within it. - * Acting as a layer supertype in this instance for the domain layer. + * Stores the dto and access the notification within it. Acting as a layer supertype in this + * instance for the domain layer. */ @AllArgsConstructor public class ServerCommand { diff --git a/notification/src/test/java/com/iluwatar/AppTest.java b/notification/src/test/java/com/iluwatar/AppTest.java index 8f5ff4a48d8d..0eaaf78632be 100644 --- a/notification/src/test/java/com/iluwatar/AppTest.java +++ b/notification/src/test/java/com/iluwatar/AppTest.java @@ -24,15 +24,14 @@ */ package com.iluwatar; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } - diff --git a/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java b/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java index f4a7358dab64..fdc5b77cffef 100644 --- a/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java +++ b/notification/src/test/java/com/iluwatar/RegisterWorkerFormTest.java @@ -33,42 +33,41 @@ class RegisterWorkerFormTest { - private RegisterWorkerForm registerWorkerForm; + private RegisterWorkerForm registerWorkerForm; - @Test - void submitSuccessfully() { - // Ensure the worker is null initially - registerWorkerForm = new RegisterWorkerForm("John Doe", "Engineer", LocalDate.of(1990, 1, 1)); + @Test + void submitSuccessfully() { + // Ensure the worker is null initially + registerWorkerForm = new RegisterWorkerForm("John Doe", "Engineer", LocalDate.of(1990, 1, 1)); - assertNull(registerWorkerForm.worker); + assertNull(registerWorkerForm.worker); - // Submit the form - registerWorkerForm.submit(); + // Submit the form + registerWorkerForm.submit(); - // Verify that the worker is not null after submission - assertNotNull(registerWorkerForm.worker); + // Verify that the worker is not null after submission + assertNotNull(registerWorkerForm.worker); - // Verify that the worker's properties are set correctly - assertEquals("John Doe", registerWorkerForm.worker.getName()); - assertEquals("Engineer", registerWorkerForm.worker.getOccupation()); - assertEquals(LocalDate.of(1990, 1, 1), registerWorkerForm.worker.getDateOfBirth()); - } + // Verify that the worker's properties are set correctly + assertEquals("John Doe", registerWorkerForm.worker.getName()); + assertEquals("Engineer", registerWorkerForm.worker.getOccupation()); + assertEquals(LocalDate.of(1990, 1, 1), registerWorkerForm.worker.getDateOfBirth()); + } - @Test - void submitWithErrors() { - // Set up the worker with a notification containing errors - registerWorkerForm = new RegisterWorkerForm(null, null, null); + @Test + void submitWithErrors() { + // Set up the worker with a notification containing errors + registerWorkerForm = new RegisterWorkerForm(null, null, null); - // Submit the form - registerWorkerForm.submit(); + // Submit the form + registerWorkerForm.submit(); - // Verify that the worker's properties remain unchanged - assertNull(registerWorkerForm.worker.getName()); - assertNull(registerWorkerForm.worker.getOccupation()); - assertNull(registerWorkerForm.worker.getDateOfBirth()); - - // Verify the presence of errors - assertEquals(registerWorkerForm.worker.getNotification().getErrors().size(), 4); - } + // Verify that the worker's properties remain unchanged + assertNull(registerWorkerForm.worker.getName()); + assertNull(registerWorkerForm.worker.getOccupation()); + assertNull(registerWorkerForm.worker.getDateOfBirth()); + // Verify the presence of errors + assertEquals(registerWorkerForm.worker.getNotification().getErrors().size(), 4); + } } diff --git a/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java b/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java index 994c6ab580b2..5f455b4cc6a5 100644 --- a/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java +++ b/notification/src/test/java/com/iluwatar/RegisterWorkerTest.java @@ -24,91 +24,97 @@ */ package com.iluwatar; -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import java.time.LocalDate; - -import static org.junit.jupiter.api.Assertions.*; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; @Slf4j class RegisterWorkerTest { - @Test - void runSuccessfully() { - RegisterWorkerDto validWorkerDto = createValidWorkerDto(); - validWorkerDto.setupWorkerDto("name", "occupation", LocalDate.of(2000, 12, 1)); - RegisterWorker registerWorker = new RegisterWorker(validWorkerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that there are no errors in the notification - assertFalse(registerWorker.getNotification().hasErrors()); - } - - @Test - void runWithMissingName() { - RegisterWorkerDto workerDto = createValidWorkerDto(); - workerDto.setupWorkerDto(null, "occupation", LocalDate.of(2000, 12, 1)); - RegisterWorker registerWorker = new RegisterWorker(workerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that the notification contains the missing name error - assertTrue(registerWorker.getNotification().hasErrors()); - assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_NAME)); - assertEquals(registerWorker.getNotification().getErrors().size(), 1); - } - - @Test - void runWithMissingOccupation() { - RegisterWorkerDto workerDto = createValidWorkerDto(); - workerDto.setupWorkerDto("name", null, LocalDate.of(2000, 12, 1)); - RegisterWorker registerWorker = new RegisterWorker(workerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that the notification contains the missing occupation error - assertTrue(registerWorker.getNotification().hasErrors()); - assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_OCCUPATION)); - assertEquals(registerWorker.getNotification().getErrors().size(), 1); - } - - @Test - void runWithMissingDOB() { - RegisterWorkerDto workerDto = createValidWorkerDto(); - workerDto.setupWorkerDto("name", "occupation", null); - RegisterWorker registerWorker = new RegisterWorker(workerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that the notification contains the missing DOB error - assertTrue(registerWorker.getNotification().hasErrors()); - assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)); - assertEquals(registerWorker.getNotification().getErrors().size(), 2); - } - - @Test - void runWithUnderageDOB() { - RegisterWorkerDto workerDto = createValidWorkerDto(); - workerDto.setDateOfBirth(LocalDate.now().minusYears(17)); // Under 18 - workerDto.setupWorkerDto("name", "occupation", LocalDate.now().minusYears(17)); - RegisterWorker registerWorker = new RegisterWorker(workerDto); - - // Run the registration process - registerWorker.run(); - - // Verify that the notification contains the underage DOB error - assertTrue(registerWorker.getNotification().hasErrors()); - assertTrue(registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.DOB_TOO_SOON)); - assertEquals(registerWorker.getNotification().getErrors().size(), 1); - } - - private RegisterWorkerDto createValidWorkerDto() { - return new RegisterWorkerDto(); - } + @Test + void runSuccessfully() { + RegisterWorkerDto validWorkerDto = createValidWorkerDto(); + validWorkerDto.setupWorkerDto("name", "occupation", LocalDate.of(2000, 12, 1)); + RegisterWorker registerWorker = new RegisterWorker(validWorkerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that there are no errors in the notification + assertFalse(registerWorker.getNotification().hasErrors()); + } + + @Test + void runWithMissingName() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setupWorkerDto(null, "occupation", LocalDate.of(2000, 12, 1)); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the missing name error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue( + registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_NAME)); + assertEquals(registerWorker.getNotification().getErrors().size(), 1); + } + + @Test + void runWithMissingOccupation() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setupWorkerDto("name", null, LocalDate.of(2000, 12, 1)); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the missing occupation error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue( + registerWorker + .getNotification() + .getErrors() + .contains(RegisterWorkerDto.MISSING_OCCUPATION)); + assertEquals(registerWorker.getNotification().getErrors().size(), 1); + } + + @Test + void runWithMissingDOB() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setupWorkerDto("name", "occupation", null); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the missing DOB error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue( + registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.MISSING_DOB)); + assertEquals(registerWorker.getNotification().getErrors().size(), 2); + } + + @Test + void runWithUnderageDOB() { + RegisterWorkerDto workerDto = createValidWorkerDto(); + workerDto.setDateOfBirth(LocalDate.now().minusYears(17)); // Under 18 + workerDto.setupWorkerDto("name", "occupation", LocalDate.now().minusYears(17)); + RegisterWorker registerWorker = new RegisterWorker(workerDto); + + // Run the registration process + registerWorker.run(); + + // Verify that the notification contains the underage DOB error + assertTrue(registerWorker.getNotification().hasErrors()); + assertTrue( + registerWorker.getNotification().getErrors().contains(RegisterWorkerDto.DOB_TOO_SOON)); + assertEquals(registerWorker.getNotification().getErrors().size(), 1); + } + + private RegisterWorkerDto createValidWorkerDto() { + return new RegisterWorkerDto(); + } } diff --git a/null-object/README.md b/null-object/README.md index 75da7452a01a..00799a1a31aa 100644 --- a/null-object/README.md +++ b/null-object/README.md @@ -35,6 +35,10 @@ Wikipedia says > In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral ("null") behavior. The null object design pattern describes the uses of such objects and their behavior (or lack thereof). +Sequence diagram + +![Null Object sequence diagram](./etc/null-object-sequence-diagram.png) + ## Programmatic Example of Null Object in Java By implementing the Null Object Pattern, Java developers can ensure that their applications handle 'empty' objects more gracefully, enhancing code stability and readability. diff --git a/null-object/etc/null-object-sequence-diagram.png b/null-object/etc/null-object-sequence-diagram.png new file mode 100644 index 000000000000..bfeda450aa0c Binary files /dev/null and b/null-object/etc/null-object-sequence-diagram.png differ diff --git a/null-object/pom.xml b/null-object/pom.xml index 1a68c1f10e2d..9bc13a79e2cd 100644 --- a/null-object/pom.xml +++ b/null-object/pom.xml @@ -34,6 +34,14 @@ null-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/null-object/src/main/java/com/iluwatar/nullobject/App.java b/null-object/src/main/java/com/iluwatar/nullobject/App.java index efeaac8581d9..d8908ef32772 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/App.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/App.java @@ -38,16 +38,17 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var root = new NodeImpl("1", - new NodeImpl("11", - new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()), - NullNode.getInstance() - ), - new NodeImpl("12", - NullNode.getInstance(), - new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance()) - ) - ); + var root = + new NodeImpl( + "1", + new NodeImpl( + "11", + new NodeImpl("111", NullNode.getInstance(), NullNode.getInstance()), + NullNode.getInstance()), + new NodeImpl( + "12", + NullNode.getInstance(), + new NodeImpl("122", NullNode.getInstance(), NullNode.getInstance()))); root.walk(); } diff --git a/null-object/src/main/java/com/iluwatar/nullobject/Node.java b/null-object/src/main/java/com/iluwatar/nullobject/Node.java index 26830dc0aa55..a306c91232c3 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/Node.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/Node.java @@ -1,41 +1,39 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.nullobject; - -/** - * Interface for binary tree node. - */ -public interface Node { - - String getName(); - - int getTreeSize(); - - Node getLeft(); - - Node getRight(); - - void walk(); -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.nullobject; + +/** Interface for binary tree node. */ +public interface Node { + + String getName(); + + int getTreeSize(); + + Node getLeft(); + + Node getRight(); + + void walk(); +} diff --git a/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java b/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java index 9dd4ccf2c9db..55fcf0eaff87 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/NodeImpl.java @@ -1,63 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.nullobject; - -import lombok.extern.slf4j.Slf4j; - -/** - * Implementation for binary tree's normal nodes. - */ - -@Slf4j -public record NodeImpl(String name, Node left, Node right) implements Node { - @Override - public Node getLeft() { - return left; - } - - @Override - public Node getRight() { - return right; - } - - @Override - public String getName() { - return name; - } - @Override - public int getTreeSize() { - return 1 + left.getTreeSize() + right.getTreeSize(); - } - @Override - public void walk() { - LOGGER.info(name); - if (left.getTreeSize() > 0) { - left.walk(); - } - if (right.getTreeSize() > 0) { - right.walk(); - } - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.nullobject; + +import lombok.extern.slf4j.Slf4j; + +/** Implementation for binary tree's normal nodes. */ +@Slf4j +public record NodeImpl(String name, Node left, Node right) implements Node { + @Override + public Node getLeft() { + return left; + } + + @Override + public Node getRight() { + return right; + } + + @Override + public String getName() { + return name; + } + + @Override + public int getTreeSize() { + return 1 + left.getTreeSize() + right.getTreeSize(); + } + + @Override + public void walk() { + LOGGER.info(name); + if (left.getTreeSize() > 0) { + left.walk(); + } + if (right.getTreeSize() > 0) { + right.walk(); + } + } +} diff --git a/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java b/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java index ce74ff0560ed..dfa1454fac5c 100644 --- a/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java +++ b/null-object/src/main/java/com/iluwatar/nullobject/NullNode.java @@ -33,8 +33,7 @@ public final class NullNode implements Node { private static final NullNode instance = new NullNode(); - private NullNode() { - } + private NullNode() {} public static NullNode getInstance() { return instance; diff --git a/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java b/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java index 01867936d019..df790d10851f 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.nullobject; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java index 8bcd8e7d9482..1cc0a9d041bb 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/NullNodeTest.java @@ -24,22 +24,17 @@ */ package com.iluwatar.nullobject; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; -/** - * NullNodeTest - * - */ +import org.junit.jupiter.api.Test; + +/** NullNodeTest */ class NullNodeTest { - /** - * Verify if {@link NullNode#getInstance()} actually returns the same object instance - */ + /** Verify if {@link NullNode#getInstance()} actually returns the same object instance */ @Test void testGetInstance() { final var instance = NullNode.getInstance(); @@ -57,7 +52,7 @@ void testFields() { } /** - * Removed unnecessary test method for {@link NullNode#walk()} as the method doesn't have an implementation. + * Removed unnecessary test method for {@link NullNode#walk()} as the method doesn't have an + * implementation. */ - } diff --git a/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java index ec2635c475aa..9c39e6f164bf 100644 --- a/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java +++ b/null-object/src/test/java/com/iluwatar/nullobject/TreeTest.java @@ -39,10 +39,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * TreeTest - * - */ +/** TreeTest */ class TreeTest { private InMemoryAppender appender; @@ -92,9 +89,7 @@ void testTreeSize() { assertEquals(7, TREE_ROOT.getTreeSize()); } - /** - * Walk through the tree and verify if every item is handled - */ + /** Walk through the tree and verify if every item is handled */ @Test void testWalk() { TREE_ROOT.walk(); @@ -160,5 +155,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/object-mother/README.md b/object-mother/README.md index c80ad2eaa8fc..67ad32f268a3 100644 --- a/object-mother/README.md +++ b/object-mother/README.md @@ -37,6 +37,14 @@ wiki.c2.com says > 2. providing methods to update the objects during the tests, and > 3. if necessary, deleting the object from the database at the completion of the test. +Mind map + +![Object Mother mind map](./etc/object-mother-mind-map.png) + +Flowchart + +![Object Mother flowchart](./etc/object-mother-flowchart.png) + ## Programmatic Example of Object Mother Pattern in Java The Object Mother is a design pattern that aims to provide an easy way to create objects for testing purposes. It encapsulates the logic for building instances of complex objects in one place, making it easier to maintain and reuse across multiple tests. diff --git a/object-mother/etc/object-mother-flowchart.png b/object-mother/etc/object-mother-flowchart.png new file mode 100644 index 000000000000..b475a0532282 Binary files /dev/null and b/object-mother/etc/object-mother-flowchart.png differ diff --git a/object-mother/etc/object-mother-mind-map.png b/object-mother/etc/object-mother-mind-map.png new file mode 100644 index 000000000000..2234eedde064 Binary files /dev/null and b/object-mother/etc/object-mother-mind-map.png differ diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/King.java b/object-mother/src/main/java/com/iluwatar/objectmother/King.java index 0cde71918a65..9aae081c5c47 100644 --- a/object-mother/src/main/java/com/iluwatar/objectmother/King.java +++ b/object-mother/src/main/java/com/iluwatar/objectmother/King.java @@ -24,9 +24,7 @@ */ package com.iluwatar.objectmother; -/** - * Defines all attributes and behaviour related to the King. - */ +/** Defines all attributes and behaviour related to the King. */ public class King implements Royalty { boolean isDrunk = false; boolean isHappy = false; @@ -67,6 +65,5 @@ public void flirt(Queen queen) { } else { this.makeHappy(); } - } } diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java b/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java index 22f944958492..e210161b307e 100644 --- a/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java +++ b/object-mother/src/main/java/com/iluwatar/objectmother/Queen.java @@ -24,9 +24,7 @@ */ package com.iluwatar.objectmother; -/** - * Defines all attributes and behaviour related to the Queen. - */ +/** Defines all attributes and behaviour related to the Queen. */ public class Queen implements Royalty { private boolean isDrunk = false; private boolean isHappy = false; diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java b/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java index 795de80e65c2..f97e4d8d7f43 100644 --- a/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java +++ b/object-mother/src/main/java/com/iluwatar/objectmother/Royalty.java @@ -24,9 +24,7 @@ */ package com.iluwatar.objectmother; -/** - * Interface contracting Royalty Behaviour. - */ +/** Interface contracting Royalty Behaviour. */ public interface Royalty { void makeDrunk(); diff --git a/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java b/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java index 1c30c3d944ef..509b32d70b6f 100644 --- a/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java +++ b/object-mother/src/main/java/com/iluwatar/objectmother/RoyaltyObjectMother.java @@ -24,9 +24,7 @@ */ package com.iluwatar.objectmother; -/** - * Object Mother Pattern generating Royalty Types. - */ +/** Object Mother Pattern generating Royalty Types. */ public final class RoyaltyObjectMother { /** diff --git a/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java b/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java index 3b565571432a..04f91372fece 100644 --- a/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java +++ b/object-mother/src/test/java/com/iluwatar/objectmother/test/RoyaltyObjectMotherTest.java @@ -33,9 +33,7 @@ import com.iluwatar.objectmother.RoyaltyObjectMother; import org.junit.jupiter.api.Test; -/** - * Test Generation of Royalty Types using the object-mother - */ +/** Test Generation of Royalty Types using the object-mother */ class RoyaltyObjectMotherTest { @Test diff --git a/object-pool/README.md b/object-pool/README.md index 56888b017a05..9746c27c574b 100644 --- a/object-pool/README.md +++ b/object-pool/README.md @@ -35,6 +35,10 @@ Wikipedia says > The object pool pattern is a software creational design pattern that uses a set of initialized objects kept ready to use – a "pool" – rather than allocating and destroying them on demand. +Sequence diagram + +![Object Pool sequence diagram](./etc/object-pool-sequence-diagram.png) + ## Programmatic Example of Object Pool Pattern in Java In our war game we need to use oliphaunts, massive and mythic beasts, but the problem is that they are extremely expensive to create. The solution is to create a pool of them, track which ones are in-use, and instead of disposing them re-use the instances. diff --git a/object-pool/etc/object-pool-sequence-diagram.png b/object-pool/etc/object-pool-sequence-diagram.png new file mode 100644 index 000000000000..2aa438024455 Binary files /dev/null and b/object-pool/etc/object-pool-sequence-diagram.png differ diff --git a/object-pool/pom.xml b/object-pool/pom.xml index 52233e375512..72e46e4b5035 100644 --- a/object-pool/pom.xml +++ b/object-pool/pom.xml @@ -34,6 +34,14 @@ object-pool + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java b/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java index 5d97e4201274..dfd1b118dfb9 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/ObjectPool.java @@ -39,9 +39,7 @@ public abstract class ObjectPool { protected abstract T create(); - /** - * Checkout object from pool. - */ + /** Checkout object from pool. */ public synchronized T checkOut() { if (available.isEmpty()) { available.add(create()); diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java b/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java index 6d7f5ce6db4e..97e8fea49ce0 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/Oliphaunt.java @@ -28,20 +28,15 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * Oliphaunts are expensive to create. - */ +/** Oliphaunts are expensive to create. */ @Slf4j public class Oliphaunt { private static final AtomicInteger counter = new AtomicInteger(0); - @Getter - private final int id; + @Getter private final int id; - /** - * Constructor. - */ + /** Constructor. */ public Oliphaunt() { id = counter.incrementAndGet(); try { diff --git a/object-pool/src/main/java/com/iluwatar/object/pool/OliphauntPool.java b/object-pool/src/main/java/com/iluwatar/object/pool/OliphauntPool.java index 0b1a2af18e1f..c13fde73ba74 100644 --- a/object-pool/src/main/java/com/iluwatar/object/pool/OliphauntPool.java +++ b/object-pool/src/main/java/com/iluwatar/object/pool/OliphauntPool.java @@ -24,9 +24,7 @@ */ package com.iluwatar.object.pool; -/** - * Oliphaunt object pool. - */ +/** Oliphaunt object pool. */ public class OliphauntPool extends ObjectPool { @Override diff --git a/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java b/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java index c96c21bc5066..8318e50bf1d0 100644 --- a/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java +++ b/object-pool/src/test/java/com/iluwatar/object/pool/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test. - */ +/** Application test. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java b/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java index 1bd19c44bc81..b929951241a0 100644 --- a/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java +++ b/object-pool/src/test/java/com/iluwatar/object/pool/OliphauntPoolTest.java @@ -34,9 +34,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * OliphauntPoolTest. - */ +/** OliphauntPoolTest. */ class OliphauntPoolTest { /** @@ -45,27 +43,29 @@ class OliphauntPoolTest { */ @Test void testSubsequentCheckinCheckout() { - assertTimeout(ofMillis(5000), () -> { - final var pool = new OliphauntPool(); - assertEquals("Pool available=0 inUse=0", pool.toString()); - - final var expectedOliphaunt = pool.checkOut(); - assertEquals("Pool available=0 inUse=1", pool.toString()); - - pool.checkIn(expectedOliphaunt); - assertEquals("Pool available=1 inUse=0", pool.toString()); - - for (int i = 0; i < 100; i++) { - final var oliphaunt = pool.checkOut(); - assertEquals("Pool available=0 inUse=1", pool.toString()); - assertSame(expectedOliphaunt, oliphaunt); - assertEquals(expectedOliphaunt.getId(), oliphaunt.getId()); - assertEquals(expectedOliphaunt.toString(), oliphaunt.toString()); - - pool.checkIn(oliphaunt); - assertEquals("Pool available=1 inUse=0", pool.toString()); - } - }); + assertTimeout( + ofMillis(5000), + () -> { + final var pool = new OliphauntPool(); + assertEquals("Pool available=0 inUse=0", pool.toString()); + + final var expectedOliphaunt = pool.checkOut(); + assertEquals("Pool available=0 inUse=1", pool.toString()); + + pool.checkIn(expectedOliphaunt); + assertEquals("Pool available=1 inUse=0", pool.toString()); + + for (int i = 0; i < 100; i++) { + final var oliphaunt = pool.checkOut(); + assertEquals("Pool available=0 inUse=1", pool.toString()); + assertSame(expectedOliphaunt, oliphaunt); + assertEquals(expectedOliphaunt.getId(), oliphaunt.getId()); + assertEquals(expectedOliphaunt.toString(), oliphaunt.toString()); + + pool.checkIn(oliphaunt); + assertEquals("Pool available=1 inUse=0", pool.toString()); + } + }); } /** @@ -74,50 +74,51 @@ void testSubsequentCheckinCheckout() { */ @Test void testConcurrentCheckinCheckout() { - assertTimeout(ofMillis(5000), () -> { - final var pool = new OliphauntPool(); - assertEquals(pool.toString(), "Pool available=0 inUse=0"); - - final var firstOliphaunt = pool.checkOut(); - assertEquals(pool.toString(), "Pool available=0 inUse=1"); - - final var secondOliphaunt = pool.checkOut(); - assertEquals(pool.toString(), "Pool available=0 inUse=2"); - - assertNotSame(firstOliphaunt, secondOliphaunt); - assertEquals(firstOliphaunt.getId() + 1, secondOliphaunt.getId()); - - // After checking in the second, we should get the same when checking out a new oliphaunt ... - pool.checkIn(secondOliphaunt); - assertEquals(pool.toString(), "Pool available=1 inUse=1"); - - final var oliphaunt3 = pool.checkOut(); - assertEquals(pool.toString(), "Pool available=0 inUse=2"); - assertSame(secondOliphaunt, oliphaunt3); - - // ... and the same applies for the first one - pool.checkIn(firstOliphaunt); - assertEquals(pool.toString(), "Pool available=1 inUse=1"); - - final var oliphaunt4 = pool.checkOut(); - assertEquals(pool.toString(), "Pool available=0 inUse=2"); - assertSame(firstOliphaunt, oliphaunt4); - - // When both oliphaunt return to the pool, we should still get the same instances - pool.checkIn(firstOliphaunt); - assertEquals(pool.toString(), "Pool available=1 inUse=1"); - - pool.checkIn(secondOliphaunt); - assertEquals(pool.toString(), "Pool available=2 inUse=0"); - - // The order of the returned instances is not determined, so just put them in a list - // and verify if both expected instances are in there. - final var oliphaunts = List.of(pool.checkOut(), pool.checkOut()); - assertEquals(pool.toString(), "Pool available=0 inUse=2"); - assertTrue(oliphaunts.contains(firstOliphaunt)); - assertTrue(oliphaunts.contains(secondOliphaunt)); - }); + assertTimeout( + ofMillis(5000), + () -> { + final var pool = new OliphauntPool(); + assertEquals(pool.toString(), "Pool available=0 inUse=0"); + + final var firstOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=1"); + + final var secondOliphaunt = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + + assertNotSame(firstOliphaunt, secondOliphaunt); + assertEquals(firstOliphaunt.getId() + 1, secondOliphaunt.getId()); + + // After checking in the second, we should get the same when checking out a new oliphaunt + // ... + pool.checkIn(secondOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + final var oliphaunt3 = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertSame(secondOliphaunt, oliphaunt3); + + // ... and the same applies for the first one + pool.checkIn(firstOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + final var oliphaunt4 = pool.checkOut(); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertSame(firstOliphaunt, oliphaunt4); + + // When both oliphaunt return to the pool, we should still get the same instances + pool.checkIn(firstOliphaunt); + assertEquals(pool.toString(), "Pool available=1 inUse=1"); + + pool.checkIn(secondOliphaunt); + assertEquals(pool.toString(), "Pool available=2 inUse=0"); + + // The order of the returned instances is not determined, so just put them in a list + // and verify if both expected instances are in there. + final var oliphaunts = List.of(pool.checkOut(), pool.checkOut()); + assertEquals(pool.toString(), "Pool available=0 inUse=2"); + assertTrue(oliphaunts.contains(firstOliphaunt)); + assertTrue(oliphaunts.contains(secondOliphaunt)); + }); } - - -} \ No newline at end of file +} diff --git a/observer/README.md b/observer/README.md index 1bf11fbe331b..6b8a700771f3 100644 --- a/observer/README.md +++ b/observer/README.md @@ -7,7 +7,7 @@ language: en tag: - Decoupling - Event-driven - - Gang Of Four + - Gang of Four - Publish/subscribe --- @@ -33,6 +33,10 @@ Wikipedia says > The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. +Sequence diagram + +![Observer sequence diagram](./etc/observer-sequence-diagram.png) + ## Programmatic Example of Observer Pattern in Java In a land far away live the races of hobbits and orcs. Both of them are mostly outdoors, so they closely follow the weather changes. One could say that they are constantly observing the weather. diff --git a/observer/etc/observer-sequence-diagram.png b/observer/etc/observer-sequence-diagram.png new file mode 100644 index 000000000000..94c7c3ee179c Binary files /dev/null and b/observer/etc/observer-sequence-diagram.png differ diff --git a/observer/pom.xml b/observer/pom.xml index af7f26e7f7e1..41fe814e06fc 100644 --- a/observer/pom.xml +++ b/observer/pom.xml @@ -34,6 +34,14 @@ observer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/observer/src/main/java/com/iluwatar/observer/Hobbits.java b/observer/src/main/java/com/iluwatar/observer/Hobbits.java index 43ce675f4227..0698a8aaf7d4 100644 --- a/observer/src/main/java/com/iluwatar/observer/Hobbits.java +++ b/observer/src/main/java/com/iluwatar/observer/Hobbits.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Hobbits. - */ +/** Hobbits. */ @Slf4j public class Hobbits implements WeatherObserver { diff --git a/observer/src/main/java/com/iluwatar/observer/Orcs.java b/observer/src/main/java/com/iluwatar/observer/Orcs.java index 542101840690..ff09ea8ad955 100644 --- a/observer/src/main/java/com/iluwatar/observer/Orcs.java +++ b/observer/src/main/java/com/iluwatar/observer/Orcs.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Orcs. - */ +/** Orcs. */ @Slf4j public class Orcs implements WeatherObserver { diff --git a/observer/src/main/java/com/iluwatar/observer/Weather.java b/observer/src/main/java/com/iluwatar/observer/Weather.java index 8769292d4c02..eb19f0acc5bc 100644 --- a/observer/src/main/java/com/iluwatar/observer/Weather.java +++ b/observer/src/main/java/com/iluwatar/observer/Weather.java @@ -51,9 +51,7 @@ public void removeObserver(WeatherObserver obs) { observers.remove(obs); } - /** - * Makes time pass for weather. - */ + /** Makes time pass for weather. */ public void timePasses() { var enumValues = WeatherType.values(); currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; diff --git a/observer/src/main/java/com/iluwatar/observer/WeatherObserver.java b/observer/src/main/java/com/iluwatar/observer/WeatherObserver.java index 17cac1fff0e7..106d016ba3fa 100644 --- a/observer/src/main/java/com/iluwatar/observer/WeatherObserver.java +++ b/observer/src/main/java/com/iluwatar/observer/WeatherObserver.java @@ -1,34 +1,31 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.observer; - -/** - * Observer interface. - */ -public interface WeatherObserver { - - void update(WeatherType currentWeather); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.observer; + +/** Observer interface. */ +public interface WeatherObserver { + + void update(WeatherType currentWeather); +} diff --git a/observer/src/main/java/com/iluwatar/observer/WeatherType.java b/observer/src/main/java/com/iluwatar/observer/WeatherType.java index 600f586488f9..7f52d64328a5 100644 --- a/observer/src/main/java/com/iluwatar/observer/WeatherType.java +++ b/observer/src/main/java/com/iluwatar/observer/WeatherType.java @@ -24,18 +24,16 @@ */ package com.iluwatar.observer; -import lombok.Getter; /** - * WeatherType enumeration. - */ -public enum WeatherType { +import lombok.Getter; +/** WeatherType enumeration. */ +public enum WeatherType { SUNNY("Sunny"), RAINY("Rainy"), WINDY("Windy"), COLD("Cold"); - @Getter - private final String description; + @Getter private final String description; WeatherType(String description) { this.description = description; diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GenHobbits.java b/observer/src/main/java/com/iluwatar/observer/generic/GenHobbits.java index 1d4e9baa6078..22c1404c7b9e 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GenHobbits.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GenHobbits.java @@ -27,9 +27,7 @@ import com.iluwatar.observer.WeatherType; import lombok.extern.slf4j.Slf4j; -/** - * GHobbits. - */ +/** GHobbits. */ @Slf4j public class GenHobbits implements Race { diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GenOrcs.java b/observer/src/main/java/com/iluwatar/observer/generic/GenOrcs.java index bbf1c197f751..163c38582e83 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GenOrcs.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GenOrcs.java @@ -27,9 +27,7 @@ import com.iluwatar.observer.WeatherType; import lombok.extern.slf4j.Slf4j; -/** - * GOrcs. - */ +/** GOrcs. */ @Slf4j public class GenOrcs implements Race { diff --git a/observer/src/main/java/com/iluwatar/observer/generic/GenWeather.java b/observer/src/main/java/com/iluwatar/observer/generic/GenWeather.java index 485f2ab36048..c05277028bb1 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/GenWeather.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/GenWeather.java @@ -27,9 +27,7 @@ import com.iluwatar.observer.WeatherType; import lombok.extern.slf4j.Slf4j; -/** - * GWeather. - */ +/** GWeather. */ @Slf4j public class GenWeather extends Observable { @@ -39,9 +37,7 @@ public GenWeather() { currentWeather = WeatherType.SUNNY; } - /** - * Makes time pass for weather. - */ + /** Makes time pass for weather. */ public void timePasses() { var enumValues = WeatherType.values(); currentWeather = enumValues[(currentWeather.ordinal() + 1) % enumValues.length]; diff --git a/observer/src/main/java/com/iluwatar/observer/generic/Observable.java b/observer/src/main/java/com/iluwatar/observer/generic/Observable.java index ff7e917d4099..2e10d7de266c 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/Observable.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/Observable.java @@ -50,9 +50,7 @@ public void removeObserver(O observer) { this.observers.remove(observer); } - /** - * Notify observers. - */ + /** Notify observers. */ @SuppressWarnings("unchecked") public void notifyObservers(A argument) { for (var observer : observers) { diff --git a/observer/src/main/java/com/iluwatar/observer/generic/Race.java b/observer/src/main/java/com/iluwatar/observer/generic/Race.java index 6b602e2f761e..2f30d5881fc8 100644 --- a/observer/src/main/java/com/iluwatar/observer/generic/Race.java +++ b/observer/src/main/java/com/iluwatar/observer/generic/Race.java @@ -26,8 +26,5 @@ import com.iluwatar.observer.WeatherType; -/** - * Race. - */ -public interface Race extends Observer { -} +/** Race. */ +public interface Race extends Observer {} diff --git a/observer/src/test/java/com/iluwatar/observer/AppTest.java b/observer/src/test/java/com/iluwatar/observer/AppTest.java index 261b51fe43e3..2322ff31b33d 100644 --- a/observer/src/test/java/com/iluwatar/observer/AppTest.java +++ b/observer/src/test/java/com/iluwatar/observer/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test. - */ +/** Application test. */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java index 823061c727ee..abce5cfa42db 100644 --- a/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/HobbitsTest.java @@ -27,26 +27,20 @@ import java.util.Collection; import java.util.List; -/** - * HobbitsTest - * - */ +/** HobbitsTest */ class HobbitsTest extends WeatherObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, - new Object[]{WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, - new Object[]{WeatherType.WINDY, "The hobbits are facing Windy weather now"}, - new Object[]{WeatherType.COLD, "The hobbits are facing Cold weather now"}); + new Object[] {WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, + new Object[] {WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, + new Object[] {WeatherType.WINDY, "The hobbits are facing Windy weather now"}, + new Object[] {WeatherType.COLD, "The hobbits are facing Cold weather now"}); } - /** - * Create a new test with the given weather and expected response - */ + /** Create a new test with the given weather and expected response */ public HobbitsTest() { super(Hobbits::new); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java index 71dacc948c8f..05e34758c5c7 100644 --- a/observer/src/test/java/com/iluwatar/observer/OrcsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/OrcsTest.java @@ -27,26 +27,20 @@ import java.util.Collection; import java.util.List; -/** - * OrcsTest - * - */ +/** OrcsTest */ class OrcsTest extends WeatherObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, - new Object[]{WeatherType.RAINY, "The orcs are facing Rainy weather now"}, - new Object[]{WeatherType.WINDY, "The orcs are facing Windy weather now"}, - new Object[]{WeatherType.COLD, "The orcs are facing Cold weather now"}); + new Object[] {WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, + new Object[] {WeatherType.RAINY, "The orcs are facing Rainy weather now"}, + new Object[] {WeatherType.WINDY, "The orcs are facing Windy weather now"}, + new Object[] {WeatherType.COLD, "The orcs are facing Cold weather now"}); } - /** - * Create a new test with the given weather and expected response - */ + /** Create a new test with the given weather and expected response */ public OrcsTest() { super(Orcs::new); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java b/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java index 6702438b693d..95958139ec86 100644 --- a/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java +++ b/observer/src/test/java/com/iluwatar/observer/WeatherObserverTest.java @@ -37,6 +37,7 @@ /** * Weather Observer Tests + * * @param Type of WeatherObserver */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -54,15 +55,13 @@ void tearDown() { appender.stop(); } - /** - * The observer instance factory - */ + /** The observer instance factory */ private final Supplier factory; /** * Create a new test instance using the given parameters * - * @param factory The factory, used to create an instance of the tested observer + * @param factory The factory, used to create an instance of the tested observer */ WeatherObserverTest(final Supplier factory) { this.factory = factory; @@ -70,9 +69,7 @@ void tearDown() { public abstract Collection dataProvider(); - /** - * Verify if the weather has the expected influence on the observer - */ + /** Verify if the weather has the expected influence on the observer */ @ParameterizedTest @MethodSource("dataProvider") void testObserver(WeatherType weather, String response) { @@ -83,5 +80,4 @@ void testObserver(WeatherType weather, String response) { assertEquals(response, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/WeatherTest.java b/observer/src/test/java/com/iluwatar/observer/WeatherTest.java index 15b3f5db83a7..9d33eaaa0a30 100644 --- a/observer/src/test/java/com/iluwatar/observer/WeatherTest.java +++ b/observer/src/test/java/com/iluwatar/observer/WeatherTest.java @@ -35,10 +35,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * WeatherTest - * - */ +/** WeatherTest */ class WeatherTest { private InMemoryAppender appender; @@ -77,9 +74,7 @@ void testAddRemoveObserver() { assertEquals(2, appender.getLogSize()); } - /** - * Verify if the weather passes in the order of the {@link WeatherType}s - */ + /** Verify if the weather passes in the order of the {@link WeatherType}s */ @Test void testTimePasses() { final var observer = mock(WeatherObserver.class); @@ -95,5 +90,4 @@ void testTimePasses() { verifyNoMoreInteractions(observer); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java index 54ec19440088..d1c9af42cf3a 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/GHobbitsTest.java @@ -28,26 +28,20 @@ import java.util.Collection; import java.util.List; -/** - * GHobbitsTest. - */ +/** GHobbitsTest. */ class GHobbitsTest extends ObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, - new Object[]{WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, - new Object[]{WeatherType.WINDY, "The hobbits are facing Windy weather now"}, - new Object[]{WeatherType.COLD, "The hobbits are facing Cold weather now"} - ); + new Object[] {WeatherType.SUNNY, "The hobbits are facing Sunny weather now"}, + new Object[] {WeatherType.RAINY, "The hobbits are facing Rainy weather now"}, + new Object[] {WeatherType.WINDY, "The hobbits are facing Windy weather now"}, + new Object[] {WeatherType.COLD, "The hobbits are facing Cold weather now"}); } - /** - * Create a new test with the given weather and expected response. - */ + /** Create a new test with the given weather and expected response. */ public GHobbitsTest() { super(GenHobbits::new); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java b/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java index 1fd42a1cd80a..a052cf2c16ab 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/GWeatherTest.java @@ -37,10 +37,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * GWeatherTest - * - */ +/** GWeatherTest */ class GWeatherTest { private InMemoryAppender appender; @@ -79,9 +76,7 @@ void testAddRemoveObserver() { assertEquals(2, appender.getLogSize()); } - /** - * Verify if the weather passes in the order of the {@link WeatherType}s - */ + /** Verify if the weather passes in the order of the {@link WeatherType}s */ @Test void testTimePasses() { final var observer = mock(Race.class); @@ -97,5 +92,4 @@ void testTimePasses() { verifyNoMoreInteractions(observer); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java b/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java index 9a76e9bdb78b..2a06651b2f6a 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/ObserverTest.java @@ -24,21 +24,21 @@ */ package com.iluwatar.observer.generic; +import static org.junit.jupiter.api.Assertions.assertEquals; + import com.iluwatar.observer.WeatherType; import com.iluwatar.observer.utils.InMemoryAppender; +import java.util.Collection; +import java.util.function.Supplier; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.Collection; -import java.util.function.Supplier; - -import static org.junit.jupiter.api.Assertions.assertEquals; - /** * Test for Observers + * * @param Type of Observer */ @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -56,15 +56,13 @@ void tearDown() { appender.stop(); } - /** - * The observer instance factory - */ + /** The observer instance factory */ private final Supplier factory; /** * Create a new test instance using the given parameters * - * @param factory The factory, used to create an instance of the tested observer + * @param factory The factory, used to create an instance of the tested observer */ ObserverTest(final Supplier factory) { this.factory = factory; @@ -72,9 +70,7 @@ void tearDown() { public abstract Collection dataProvider(); - /** - * Verify if the weather has the expected influence on the observer - */ + /** Verify if the weather has the expected influence on the observer */ @ParameterizedTest @MethodSource("dataProvider") void testObserver(WeatherType weather, String response) { @@ -85,5 +81,4 @@ void testObserver(WeatherType weather, String response) { assertEquals(response, appender.getLastMessage()); assertEquals(1, appender.getLogSize()); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java index 0439bf56b15e..47d604fd7ec3 100644 --- a/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java +++ b/observer/src/test/java/com/iluwatar/observer/generic/OrcsTest.java @@ -28,27 +28,20 @@ import java.util.Collection; import java.util.List; -/** - * OrcsTest - * - */ +/** OrcsTest */ class OrcsTest extends ObserverTest { @Override public Collection dataProvider() { return List.of( - new Object[]{WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, - new Object[]{WeatherType.RAINY, "The orcs are facing Rainy weather now"}, - new Object[]{WeatherType.WINDY, "The orcs are facing Windy weather now"}, - new Object[]{WeatherType.COLD, "The orcs are facing Cold weather now"} - ); + new Object[] {WeatherType.SUNNY, "The orcs are facing Sunny weather now"}, + new Object[] {WeatherType.RAINY, "The orcs are facing Rainy weather now"}, + new Object[] {WeatherType.WINDY, "The orcs are facing Windy weather now"}, + new Object[] {WeatherType.COLD, "The orcs are facing Cold weather now"}); } - /** - * Create a new test with the given weather and expected response - */ + /** Create a new test with the given weather and expected response */ public OrcsTest() { super(GenOrcs::new); } - } diff --git a/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java b/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java index 1fc48688131d..36e92d66ef4c 100644 --- a/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java +++ b/observer/src/test/java/com/iluwatar/observer/utils/InMemoryAppender.java @@ -31,9 +31,7 @@ import java.util.List; import org.slf4j.LoggerFactory; -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/optimistic-offline-lock/README.md b/optimistic-offline-lock/README.md index 98bf4140b6e7..c14e102ba250 100644 --- a/optimistic-offline-lock/README.md +++ b/optimistic-offline-lock/README.md @@ -35,6 +35,10 @@ Wikipedia says > Optimistic concurrency control (OCC), also known as optimistic locking, is a concurrency control method applied to transactional systems such as relational database management systems and software transactional memory. +Sequence diagram + +![Optimistic Offline Lock sequence diagram](./etc/optimistic-offline-lock-sequence-diagram.png) + ## Programmatic Example of Optimistic Offline Lock Pattern in Java In this section, we delve into the practical implementation of the Optimistic Offline Lock in Java. By following these steps, you can ensure that your application handles data conflicts and concurrency with minimal overhead. diff --git a/optimistic-offline-lock/etc/optimistic-offline-lock-sequence-diagram.png b/optimistic-offline-lock/etc/optimistic-offline-lock-sequence-diagram.png new file mode 100644 index 000000000000..7870596ca211 Binary files /dev/null and b/optimistic-offline-lock/etc/optimistic-offline-lock-sequence-diagram.png differ diff --git a/optimistic-offline-lock/src/main/java/com/iluwatar/api/UpdateService.java b/optimistic-offline-lock/src/main/java/com/iluwatar/api/UpdateService.java index e41af6d50bcf..5b7489c4a9a2 100644 --- a/optimistic-offline-lock/src/main/java/com/iluwatar/api/UpdateService.java +++ b/optimistic-offline-lock/src/main/java/com/iluwatar/api/UpdateService.java @@ -35,7 +35,7 @@ public interface UpdateService { * Update entity. * * @param obj entity to update - * @param id primary key + * @param id primary key * @return modified entity */ T doUpdate(T obj, long id); diff --git a/optimistic-offline-lock/src/main/java/com/iluwatar/exception/ApplicationException.java b/optimistic-offline-lock/src/main/java/com/iluwatar/exception/ApplicationException.java index 4dbd1918bc7b..ed065c597c1e 100644 --- a/optimistic-offline-lock/src/main/java/com/iluwatar/exception/ApplicationException.java +++ b/optimistic-offline-lock/src/main/java/com/iluwatar/exception/ApplicationException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.exception; -/** - * Exception happens in application during business-logic execution. - */ +/** Exception happens in application during business-logic execution. */ public class ApplicationException extends RuntimeException { /** diff --git a/optimistic-offline-lock/src/main/java/com/iluwatar/model/Card.java b/optimistic-offline-lock/src/main/java/com/iluwatar/model/Card.java index 73e27b2ec077..9728d7c7f3e2 100644 --- a/optimistic-offline-lock/src/main/java/com/iluwatar/model/Card.java +++ b/optimistic-offline-lock/src/main/java/com/iluwatar/model/Card.java @@ -24,38 +24,27 @@ */ package com.iluwatar.model; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -/** - * Bank card entity. - */ +/** Bank card entity. */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class Card { - /** - * Primary key. - */ + /** Primary key. */ private long id; - /** - * Foreign key points to card's owner. - */ + /** Foreign key points to card's owner. */ private long personId; - /** - * Sum of money. - */ + /** Sum of money. */ private float sum; - /** - * Current version of object. - */ + /** Current version of object. */ private int version; } diff --git a/optimistic-offline-lock/src/main/java/com/iluwatar/service/CardUpdateService.java b/optimistic-offline-lock/src/main/java/com/iluwatar/service/CardUpdateService.java index d36e092c9e06..9e70ee24ed5f 100644 --- a/optimistic-offline-lock/src/main/java/com/iluwatar/service/CardUpdateService.java +++ b/optimistic-offline-lock/src/main/java/com/iluwatar/service/CardUpdateService.java @@ -30,9 +30,7 @@ import com.iluwatar.repository.JpaRepository; import lombok.RequiredArgsConstructor; -/** - * Service to update {@link Card} entity. - */ +/** Service to update {@link Card} entity. */ @RequiredArgsConstructor public class CardUpdateService implements UpdateService { @@ -45,11 +43,10 @@ public Card doUpdate(Card obj, long id) { int initialVersion = cardToUpdate.getVersion(); float resultSum = cardToUpdate.getSum() + additionalSum; cardToUpdate.setSum(resultSum); - //Maybe more complex business-logic e.g. HTTP-requests and so on + // Maybe more complex business-logic e.g. HTTP-requests and so on if (initialVersion != cardJpaRepository.getEntityVersionById(id)) { - String exMessage = - String.format("Entity with id %s were updated in another transaction", id); + String exMessage = String.format("Entity with id %s were updated in another transaction", id); throw new ApplicationException(exMessage); } diff --git a/optimistic-offline-lock/src/test/java/com/iluwatar/OptimisticLockTest.java b/optimistic-offline-lock/src/test/java/com/iluwatar/OptimisticLockTest.java index c50ed8780107..7731d12d7e6d 100644 --- a/optimistic-offline-lock/src/test/java/com/iluwatar/OptimisticLockTest.java +++ b/optimistic-offline-lock/src/test/java/com/iluwatar/OptimisticLockTest.java @@ -24,6 +24,9 @@ */ package com.iluwatar; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.when; + import com.iluwatar.exception.ApplicationException; import com.iluwatar.model.Card; import com.iluwatar.repository.JpaRepository; @@ -33,9 +36,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.eq; - @SuppressWarnings({"rawtypes", "unchecked"}) public class OptimisticLockTest { @@ -53,27 +53,19 @@ public void setUp() { public void shouldNotUpdateEntityOnDifferentVersion() { int initialVersion = 1; long cardId = 123L; - Card card = Card.builder() - .id(cardId) - .version(initialVersion) - .sum(123f) - .build(); + Card card = Card.builder().id(cardId).version(initialVersion).sum(123f).build(); when(cardRepository.findById(eq(cardId))).thenReturn(card); when(cardRepository.getEntityVersionById(Mockito.eq(cardId))).thenReturn(initialVersion + 1); - Assertions.assertThrows(ApplicationException.class, - () -> cardUpdateService.doUpdate(card, cardId)); + Assertions.assertThrows( + ApplicationException.class, () -> cardUpdateService.doUpdate(card, cardId)); } @Test public void shouldUpdateOnSameVersion() { int initialVersion = 1; long cardId = 123L; - Card card = Card.builder() - .id(cardId) - .version(initialVersion) - .sum(123f) - .build(); + Card card = Card.builder().id(cardId).version(initialVersion).sum(123f).build(); when(cardRepository.findById(eq(cardId))).thenReturn(card); when(cardRepository.getEntityVersionById(Mockito.eq(cardId))).thenReturn(initialVersion); diff --git a/page-controller/README.md b/page-controller/README.md index 99b3b7be746c..b91215d9b443 100644 --- a/page-controller/README.md +++ b/page-controller/README.md @@ -31,6 +31,10 @@ In plain words > The Page Controller pattern handles requests for specific pages or actions within a Java web application, processing input, executing business logic, and determining the appropriate view for rendering the response, enhancing response handling and system architecture. +Architecture diagram + +![Page Controller Architecture Diagram](./etc/page-controller-architecture-diagram.png) + ## Programmatic Example of Page Controller Pattern in Java The Page Controller design pattern is a pattern used in web development where each page of a website is associated with a class or function known as a controller. The controller handles the HTTP requests for that page and determines which model and view to use. Predominantly utilized in MVC (Model-View-No-Controller) architectures, the Java Page Controller pattern integrates seamlessly with existing enterprise frameworks. diff --git a/page-controller/etc/page-controller-architecture-diagram.png b/page-controller/etc/page-controller-architecture-diagram.png new file mode 100644 index 000000000000..e6ef45cca6d3 Binary files /dev/null and b/page-controller/etc/page-controller-architecture-diagram.png differ diff --git a/page-controller/pom.xml b/page-controller/pom.xml index 64e3c8a3a46e..c314459e9d14 100644 --- a/page-controller/pom.xml +++ b/page-controller/pom.xml @@ -37,22 +37,12 @@ page-controller page-controller page-controller - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.2.4 - import - - - org.springframework spring-webmvc + 6.2.5 org.springframework.boot @@ -61,10 +51,12 @@ org.springframework spring-context + 6.2.5 org.springframework.boot spring-boot-starter-thymeleaf + 3.4.4 org.junit.jupiter @@ -81,25 +73,9 @@ spring-boot-starter-test test - - org.springframework - spring-test - test - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - org.apache.maven.plugins maven-assembly-plugin diff --git a/page-controller/src/main/java/com/iluwatar/page/controller/App.java b/page-controller/src/main/java/com/iluwatar/page/controller/App.java index 278c01396221..076ebf081c24 100644 --- a/page-controller/src/main/java/com/iluwatar/page/controller/App.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/App.java @@ -30,10 +30,11 @@ /** * Page Controller pattern is utilized when we want to simplify relationship in a dynamic website. - * It is an approach of one front page leading to one logical file that handles HTTP requests and actions. - * In this example, we build a website with signup page handling an input form with Signup Controller, Signup View, and Signup Model - * and after signup, it is redirected to a user page handling with User Controller, User View, and User Model. -*/ + * It is an approach of one front page leading to one logical file that handles HTTP requests and + * actions. In this example, we build a website with signup page handling an input form with Signup + * Controller, Signup View, and Signup Model and after signup, it is redirected to a user page + * handling with User Controller, User View, and User Model. + */ @Slf4j @SpringBootApplication public class App { diff --git a/page-controller/src/main/java/com/iluwatar/page/controller/SignupController.java b/page-controller/src/main/java/com/iluwatar/page/controller/SignupController.java index 2efdc9ef19a5..08d64cebf4a7 100644 --- a/page-controller/src/main/java/com/iluwatar/page/controller/SignupController.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/SignupController.java @@ -31,31 +31,23 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes; -/** - * Signup Controller. - */ +/** Signup Controller. */ @Slf4j @Controller @Component public class SignupController { SignupView view = new SignupView(); - /** - * Signup Controller can handle http request and decide which model and view use. - */ - SignupController() { - } - /** - * Handle http GET request. - */ + /** Signup Controller can handle http request and decide which model and view use. */ + SignupController() {} + + /** Handle http GET request. */ @GetMapping("/signup") public String getSignup() { return view.display(); } - /** - * Handle http POST request and access model and view. - */ + /** Handle http POST request and access model and view. */ @PostMapping("/signup") public String create(SignupModel form, RedirectAttributes redirectAttributes) { LOGGER.info(form.getName()); diff --git a/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java b/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java index ee9c2e1ce1f0..88134b923fab 100644 --- a/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/SignupModel.java @@ -28,9 +28,7 @@ import lombok.NoArgsConstructor; import org.springframework.stereotype.Component; -/** - * ignup model. - */ +/** ignup model. */ @Component @Data @NoArgsConstructor @@ -38,5 +36,4 @@ public class SignupModel { private String name; private String email; private String password; - -} \ No newline at end of file +} diff --git a/page-controller/src/main/java/com/iluwatar/page/controller/SignupView.java b/page-controller/src/main/java/com/iluwatar/page/controller/SignupView.java index c295fa471a5d..ef0cad94dc14 100644 --- a/page-controller/src/main/java/com/iluwatar/page/controller/SignupView.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/SignupView.java @@ -27,9 +27,7 @@ import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * Signup View. - */ +/** Signup View. */ @Slf4j @NoArgsConstructor public class SignupView { @@ -39,11 +37,10 @@ public String display() { return "/signup"; } - /** - * redirect to user page. - */ + /** redirect to user page. */ public String redirect(SignupModel form) { - LOGGER.info("Redirect to user page with " + "name " + form.getName() + " email " + form.getEmail()); + LOGGER.info( + "Redirect to user page with " + "name " + form.getName() + " email " + form.getEmail()); return "redirect:/user"; } -} \ No newline at end of file +} diff --git a/page-controller/src/main/java/com/iluwatar/page/controller/UserController.java b/page-controller/src/main/java/com/iluwatar/page/controller/UserController.java index 0f4743778a0a..2e0ee66824a4 100644 --- a/page-controller/src/main/java/com/iluwatar/page/controller/UserController.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/UserController.java @@ -30,22 +30,18 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -/** - * User Controller. - */ +/** User Controller. */ @Slf4j @Controller @NoArgsConstructor public class UserController { private final UserView view = new UserView(); - /** - * Handle http GET request and access view and model. - */ + /** Handle http GET request and access view and model. */ @GetMapping("/user") public String getUserPath(SignupModel form, Model model) { model.addAttribute("name", form.getName()); model.addAttribute("email", form.getEmail()); return view.display(form); } -} \ No newline at end of file +} diff --git a/page-controller/src/main/java/com/iluwatar/page/controller/UserModel.java b/page-controller/src/main/java/com/iluwatar/page/controller/UserModel.java index 0f099b2e6041..3dcb0e43e39d 100644 --- a/page-controller/src/main/java/com/iluwatar/page/controller/UserModel.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/UserModel.java @@ -27,13 +27,10 @@ import lombok.Data; import lombok.NoArgsConstructor; -/** - * User model. - */ +/** User model. */ @Data @NoArgsConstructor public class UserModel { private String name; private String email; - -} \ No newline at end of file +} diff --git a/page-controller/src/main/java/com/iluwatar/page/controller/UserView.java b/page-controller/src/main/java/com/iluwatar/page/controller/UserView.java index 3f4469d7dbc8..e759cc54f177 100644 --- a/page-controller/src/main/java/com/iluwatar/page/controller/UserView.java +++ b/page-controller/src/main/java/com/iluwatar/page/controller/UserView.java @@ -26,13 +26,12 @@ import lombok.extern.slf4j.Slf4j; -/** - * User view class generating html file. - */ +/** User view class generating html file. */ @Slf4j public class UserView { /** * displaying command to generate html. + * * @param user model content. */ public String display(SignupModel user) { diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/AppTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/AppTest.java index 9dcbfe3f8ef8..b3d374210dfe 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/AppTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/AppTest.java @@ -24,15 +24,14 @@ */ package com.iluwatar.page.controller; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test - */ +import org.junit.jupiter.api.Test; + +/** Application test */ public class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/SignupControllerTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/SignupControllerTest.java index 7260c24d4291..08440d88d3d9 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/SignupControllerTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/SignupControllerTest.java @@ -24,19 +24,16 @@ */ package com.iluwatar.page.controller; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap; -import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Signup Controller - */ +/** Test for Signup Controller */ public class SignupControllerTest { - /** - * Verify if user can sign up and redirect to user page - */ + /** Verify if user can sign up and redirect to user page */ @Test void testSignup() { var controller = new SignupController(); diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/SignupModelTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/SignupModelTest.java index 46e60eb94745..237ea138c395 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/SignupModelTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/SignupModelTest.java @@ -24,16 +24,13 @@ */ package com.iluwatar.page.controller; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * Test for Signup Model - */ +import org.junit.jupiter.api.Test; + +/** Test for Signup Model */ public class SignupModelTest { - /** - * Verify if a user can set a name properly - */ + /** Verify if a user can set a name properly */ @Test void testSetName() { SignupModel model = new SignupModel(); @@ -41,9 +38,7 @@ void testSetName() { assertEquals("Lily", model.getName()); } - /** - * Verify if a user can set an email properly - */ + /** Verify if a user can set an email properly */ @Test void testSetEmail() { SignupModel model = new SignupModel(); @@ -51,9 +46,7 @@ void testSetEmail() { assertEquals("Lily@email", model.getEmail()); } - /** - * Verify if a user can set a password properly - */ + /** Verify if a user can set a password properly */ @Test void testSetPassword() { SignupModel model = new SignupModel(); diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/UserControllerTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/UserControllerTest.java index 7fd1c0cd1c18..bf5eef59536d 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/UserControllerTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/UserControllerTest.java @@ -24,6 +24,10 @@ */ package com.iluwatar.page.controller; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -31,9 +35,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @ExtendWith(SpringExtension.class) @SpringBootTest @@ -41,17 +42,13 @@ public class UserControllerTest { private UserController userController; - @Autowired - MockMvc mockMvc; + @Autowired MockMvc mockMvc; - /** - * Verify if view and model are directed properly - */ + /** Verify if view and model are directed properly */ @Test - void testGetUserPath () throws Exception { - this.mockMvc.perform(get("/user") - .param("name", "Lily") - .param("email", "Lily@email.com")) + void testGetUserPath() throws Exception { + this.mockMvc + .perform(get("/user").param("name", "Lily").param("email", "Lily@email.com")) .andExpect(status().isOk()) .andExpect(model().attribute("name", "Lily")) .andExpect(model().attribute("email", "Lily@email.com")) diff --git a/page-controller/src/test/java/com/iluwatar/page/controller/UserModelTest.java b/page-controller/src/test/java/com/iluwatar/page/controller/UserModelTest.java index 49e1102c8005..8ecafae2dbfc 100644 --- a/page-controller/src/test/java/com/iluwatar/page/controller/UserModelTest.java +++ b/page-controller/src/test/java/com/iluwatar/page/controller/UserModelTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; public class UserModelTest { - /** - * Verify if a user can set a name properly - */ + /** Verify if a user can set a name properly */ @Test void testSetName() { UserModel model = new UserModel(); @@ -39,9 +37,7 @@ void testSetName() { assertEquals("Lily", model.getName()); } - /** - * Verify if a user can set an email properly - */ + /** Verify if a user can set an email properly */ @Test void testSetEmail() { UserModel model = new UserModel(); diff --git a/page-object/README.md b/page-object/README.md index 6f21070a3582..8209a0fc52ab 100644 --- a/page-object/README.md +++ b/page-object/README.md @@ -39,6 +39,14 @@ selenium.dev says > > Page Object is a Design Pattern that has become popular in test automation for enhancing test maintenance and reducing code duplication. A page object is an object-oriented class that serves as an interface to a page of your AUT. The tests then use the methods of this page object class whenever they need to interact with the UI of that page. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently, all changes to support that new UI are located in one place. +Mind map + +![Page Object mind map](./etc/page-object-mind-map.png) + +Flowchart + +![Page Object flowchart](./etc/page-object-flowchart.png) + ## Programmatic Example of Page Object Pattern in Java The Page Object design pattern is a popular design pattern in test automation. It helps in enhancing test maintenance and reducing code duplication. A page object is an object-oriented class that serves as an interface to a page of your Application Under Test (AUT). The tests then use the methods of this page object class whenever they need to interact with the UI of that page. The benefit is that if the UI changes for the page, the tests themselves don’t need to change, only the code within the page object needs to change. Subsequently, all changes to support that new UI are located in one place. diff --git a/page-object/etc/page-object-flowchart.png b/page-object/etc/page-object-flowchart.png new file mode 100644 index 000000000000..b32c413b78af Binary files /dev/null and b/page-object/etc/page-object-flowchart.png differ diff --git a/page-object/etc/page-object-mind-map.png b/page-object/etc/page-object-mind-map.png new file mode 100644 index 000000000000..c8af6acce9e7 Binary files /dev/null and b/page-object/etc/page-object-mind-map.png differ diff --git a/page-object/pom.xml b/page-object/pom.xml index 4f70425c9874..380288a53cc1 100644 --- a/page-object/pom.xml +++ b/page-object/pom.xml @@ -27,17 +27,6 @@ --> 4.0.0 - - 11 - 11 - - - - org.htmlunit - htmlunit - test - - com.iluwatar java-design-patterns @@ -49,6 +38,13 @@ sample-application test-automation + + + org.htmlunit + htmlunit + test + + diff --git a/page-object/sample-application/pom.xml b/page-object/sample-application/pom.xml index e74f4f539795..cf1b96e0901c 100644 --- a/page-object/sample-application/pom.xml +++ b/page-object/sample-application/pom.xml @@ -32,5 +32,15 @@ com.iluwatar 1.26.0-SNAPSHOT + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + sample-application diff --git a/page-object/sample-application/src/main/java/com/iluwatar/pageobject/App.java b/page-object/sample-application/src/main/java/com/iluwatar/pageobject/App.java index 0913af68e538..aaf19fb0cee2 100644 --- a/page-object/sample-application/src/main/java/com/iluwatar/pageobject/App.java +++ b/page-object/sample-application/src/main/java/com/iluwatar/pageobject/App.java @@ -52,8 +52,7 @@ @Slf4j public final class App { - private App() { - } + private App() {} /** * Application entry point @@ -85,6 +84,5 @@ public static void main(String[] args) { } catch (IOException ex) { LOGGER.error("An error occurred.", ex); } - } } diff --git a/page-object/src/main/java/com/iluwatar/pageobject/App.java b/page-object/src/main/java/com/iluwatar/pageobject/App.java index c779ff9e3e56..a0eda082f726 100644 --- a/page-object/src/main/java/com/iluwatar/pageobject/App.java +++ b/page-object/src/main/java/com/iluwatar/pageobject/App.java @@ -50,8 +50,7 @@ */ public final class App { - private App() { - } + private App() {} /** * Application entry point @@ -84,6 +83,5 @@ public static void main(String[] args) { } catch (IOException ex) { ex.printStackTrace(); } - } } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java index 4b565cc8b26b..bbaf6bfea859 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java @@ -26,14 +26,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import org.htmlunit.WebClient; import com.iluwatar.pageobject.pages.AlbumListPage; +import org.htmlunit.WebClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Album Selection and Album Listing - */ +/** Test Album Selection and Album Listing */ class AlbumListPageTest { private final AlbumListPage albumListPage = new AlbumListPage(new WebClient()); @@ -49,5 +47,4 @@ void testSelectAlbum() { albumPage.navigateToPage(); assertTrue(albumPage.isAt()); } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java index 7dbd1b91d580..08dbd6d59335 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java @@ -26,14 +26,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import org.htmlunit.WebClient; import com.iluwatar.pageobject.pages.AlbumPage; +import org.htmlunit.WebClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Album Page Operations - */ +/** Test Album Page Operations */ class AlbumPageTest { private final AlbumPage albumPage = new AlbumPage(new WebClient()); @@ -46,16 +44,16 @@ void setUp() { @Test void testSaveAlbum() { - var albumPageAfterChanges = albumPage - .changeAlbumTitle("25") - .changeArtist("Adele Laurie Blue Adkins") - .changeAlbumYear(2015) - .changeAlbumRating("B") - .changeNumberOfSongs(20) - .saveChanges(); + var albumPageAfterChanges = + albumPage + .changeAlbumTitle("25") + .changeArtist("Adele Laurie Blue Adkins") + .changeAlbumYear(2015) + .changeAlbumRating("B") + .changeNumberOfSongs(20) + .saveChanges(); assertTrue(albumPageAfterChanges.isAt()); - } @Test @@ -64,5 +62,4 @@ void testCancelChanges() { albumListPage.navigateToPage(); assertTrue(albumListPage.isAt()); } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java b/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java index 4e525b13e176..8cf22176904d 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/LoginPageTest.java @@ -26,14 +26,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import org.htmlunit.WebClient; import com.iluwatar.pageobject.pages.LoginPage; +import org.htmlunit.WebClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Login Page Object - */ +/** Test Login Page Object */ class LoginPageTest { private final LoginPage loginPage = new LoginPage(new WebClient()); @@ -45,12 +43,8 @@ void setUp() { @Test void testLogin() { - var albumListPage = loginPage - .enterUsername("admin") - .enterPassword("password") - .login(); + var albumListPage = loginPage.enterUsername("admin").enterPassword("password").login(); albumListPage.navigateToPage(); assertTrue(albumListPage.isAt()); } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java index f4d769187dad..bd59d9292897 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumListPage.java @@ -24,15 +24,13 @@ */ package com.iluwatar.pageobject.pages; +import java.io.IOException; +import java.util.List; import org.htmlunit.WebClient; import org.htmlunit.html.HtmlAnchor; import org.htmlunit.html.HtmlPage; -import java.io.IOException; -import java.util.List; -/** - * Page Object encapsulating the Album List page (album-list.html) - */ +/** Page Object encapsulating the Album List page (album-list.html) */ public class AlbumListPage extends Page { private static final String ALBUM_LIST_HTML_FILE = "album-list.html"; @@ -40,15 +38,11 @@ public class AlbumListPage extends Page { private HtmlPage page; - - /** - * Constructor - */ + /** Constructor */ public AlbumListPage(WebClient webClient) { super(webClient); } - /** * Navigates to the Album List Page * @@ -63,9 +57,7 @@ public AlbumListPage navigateToPage() { return this; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Album List".equals(page.getTitleText()); @@ -92,6 +84,4 @@ public AlbumPage selectAlbum(String albumTitle) { } throw new IllegalArgumentException("No links with the album title: " + albumTitle); } - - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java index 385bcf86c916..e21898b8c7ad 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/AlbumPage.java @@ -24,18 +24,15 @@ */ package com.iluwatar.pageobject.pages; +import java.io.IOException; import org.htmlunit.WebClient; import org.htmlunit.html.HtmlNumberInput; -import org.htmlunit.html.HtmlOption; import org.htmlunit.html.HtmlPage; import org.htmlunit.html.HtmlSelect; import org.htmlunit.html.HtmlSubmitInput; import org.htmlunit.html.HtmlTextInput; -import java.io.IOException; -/** - * Page Object encapsulating the Album Page (album-page.html) - */ +/** Page Object encapsulating the Album Page (album-page.html) */ public class AlbumPage extends Page { private static final String ALBUM_PAGE_HTML_FILE = "album-page.html"; @@ -43,15 +40,11 @@ public class AlbumPage extends Page { private HtmlPage page; - - /** - * Constructor - */ + /** Constructor */ public AlbumPage(WebClient webClient) { super(webClient); } - /** * Navigates to the album page * @@ -66,16 +59,12 @@ public AlbumPage navigateToPage() { return this; } - - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Album Page".equals(page.getTitleText()); } - /** * Sets the album title input text field * @@ -88,7 +77,6 @@ public AlbumPage changeAlbumTitle(String albumTitle) { return this; } - /** * Sets the artist input text field * @@ -101,7 +89,6 @@ public AlbumPage changeArtist(String artist) { return this; } - /** * Selects the select's option value based on the year value given * @@ -115,7 +102,6 @@ public AlbumPage changeAlbumYear(int year) { return this; } - /** * Sets the album rating input text field * @@ -140,7 +126,6 @@ public AlbumPage changeNumberOfSongs(int numberOfSongs) { return this; } - /** * Cancel changes made by clicking the cancel button * @@ -156,7 +141,6 @@ public AlbumListPage cancelChanges() { return new AlbumListPage(webClient); } - /** * Saves changes made by clicking the save button * @@ -171,5 +155,4 @@ public AlbumPage saveChanges() { } return this; } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java index 5d2ef992e0f3..b113696c026d 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/LoginPage.java @@ -24,16 +24,14 @@ */ package com.iluwatar.pageobject.pages; +import java.io.IOException; import org.htmlunit.WebClient; import org.htmlunit.html.HtmlPage; import org.htmlunit.html.HtmlPasswordInput; import org.htmlunit.html.HtmlSubmitInput; import org.htmlunit.html.HtmlTextInput; -import java.io.IOException; -/** - * Page Object encapsulating the Login Page (login.html) - */ +/** Page Object encapsulating the Login Page (login.html) */ public class LoginPage extends Page { private static final String LOGIN_PAGE_HTML_FILE = "login.html"; @@ -64,15 +62,12 @@ public LoginPage navigateToPage() { return this; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Login".equals(page.getTitleText()); } - /** * Enters the username into the username input text field * @@ -85,7 +80,6 @@ public LoginPage enterUsername(String username) { return this; } - /** * Enters the password into the password input password field * @@ -98,7 +92,6 @@ public LoginPage enterPassword(String password) { return this; } - /** * Clicking on the login button to 'login' * @@ -114,5 +107,4 @@ public AlbumListPage login() { } return new AlbumListPage(webClient); } - } diff --git a/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java b/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java index 5331e03fe9c4..499a15774f9e 100644 --- a/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java +++ b/page-object/src/test/java/com/iluwatar/pageobject/pages/Page.java @@ -26,14 +26,10 @@ import org.htmlunit.WebClient; -/** - * Encapsulation for a generic 'Page' - */ +/** Encapsulation for a generic 'Page' */ public abstract class Page { - /** - * Application Under Test path This directory location is where html web pages are located - */ + /** Application Under Test path This directory location is where html web pages are located */ public static final String AUT_PATH = "src/main/resources/sample-ui/"; protected final WebClient webClient; @@ -53,6 +49,4 @@ public Page(WebClient webClient) { * @return true if so, otherwise false */ public abstract boolean isAt(); - - } diff --git a/page-object/test-automation/pom.xml b/page-object/test-automation/pom.xml index 81bdf93fbb98..ea5ac819f726 100644 --- a/page-object/test-automation/pom.xml +++ b/page-object/test-automation/pom.xml @@ -34,9 +34,17 @@ test-automation + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter - junit-jupiter-api + junit-jupiter-engine test diff --git a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumListPage.java b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumListPage.java index fa58b9cafede..9b78b0a8b43e 100644 --- a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumListPage.java +++ b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumListPage.java @@ -31,9 +31,7 @@ import org.htmlunit.html.HtmlAnchor; import org.htmlunit.html.HtmlPage; -/** - * Page Object encapsulating the Album List page (album-list.html) - */ +/** Page Object encapsulating the Album List page (album-list.html) */ @Slf4j public class AlbumListPage extends Page { private static final String ALBUM_LIST_HTML_FILE = "album-list.html"; @@ -41,15 +39,11 @@ public class AlbumListPage extends Page { private HtmlPage page; - - /** - * Constructor. - */ + /** Constructor. */ public AlbumListPage(WebClient webClient) { super(webClient); } - /** * Navigates to the Album List Page. * @@ -64,9 +58,7 @@ public AlbumListPage navigateToPage() { return this; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Album List".equals(page.getTitleText()); @@ -93,6 +85,4 @@ public AlbumPage selectAlbum(String albumTitle) { } throw new IllegalArgumentException("No links with the album title: " + albumTitle); } - - } diff --git a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumPage.java b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumPage.java index 64d833551cdd..6da0b05f37af 100644 --- a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumPage.java +++ b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/AlbumPage.java @@ -33,9 +33,7 @@ import org.htmlunit.html.HtmlSubmitInput; import org.htmlunit.html.HtmlTextInput; -/** - * Page Object encapsulating the Album Page (album-page.html) - */ +/** Page Object encapsulating the Album Page (album-page.html) */ @Slf4j public class AlbumPage extends Page { private static final String ALBUM_PAGE_HTML_FILE = "album-page.html"; @@ -43,15 +41,11 @@ public class AlbumPage extends Page { private HtmlPage page; - - /** - * Constructor. - */ + /** Constructor. */ public AlbumPage(WebClient webClient) { super(webClient); } - /** * Navigates to the album page. * @@ -66,16 +60,12 @@ public AlbumPage navigateToPage() { return this; } - - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Album Page".equals(page.getTitleText()); } - /** * Sets the album title input text field. * @@ -88,7 +78,6 @@ public AlbumPage changeAlbumTitle(String albumTitle) { return this; } - /** * Sets the artist input text field. * @@ -101,7 +90,6 @@ public AlbumPage changeArtist(String artist) { return this; } - /** * Selects the select's option value based on the year value given. * @@ -115,7 +103,6 @@ public AlbumPage changeAlbumYear(int year) { return this; } - /** * Sets the album rating input text field. * @@ -140,7 +127,6 @@ public AlbumPage changeNumberOfSongs(int numberOfSongs) { return this; } - /** * Cancel changes made by clicking the cancel button. * @@ -156,7 +142,6 @@ public AlbumListPage cancelChanges() { return new AlbumListPage(webClient); } - /** * Saves changes made by clicking the save button. * @@ -171,5 +156,4 @@ public AlbumPage saveChanges() { } return this; } - } diff --git a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/LoginPage.java b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/LoginPage.java index 2f48e08938dc..3d4f7c8f2d96 100644 --- a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/LoginPage.java +++ b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/LoginPage.java @@ -32,9 +32,7 @@ import org.htmlunit.html.HtmlSubmitInput; import org.htmlunit.html.HtmlTextInput; -/** - * Page Object encapsulating the Login Page (login.html) - */ +/** Page Object encapsulating the Login Page (login.html) */ @Slf4j public class LoginPage extends Page { private static final String LOGIN_PAGE_HTML_FILE = "login.html"; @@ -65,15 +63,12 @@ public LoginPage navigateToPage() { return this; } - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public boolean isAt() { return "Login".equals(page.getTitleText()); } - /** * Enters the username into the username input text field. * @@ -86,7 +81,6 @@ public LoginPage enterUsername(String username) { return this; } - /** * Enters the password into the password input password field. * @@ -99,7 +93,6 @@ public LoginPage enterPassword(String password) { return this; } - /** * Clicking on the login button to 'login'. * @@ -115,5 +108,4 @@ public AlbumListPage login() { } return new AlbumListPage(webClient); } - } diff --git a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/Page.java b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/Page.java index 0a2186f43b84..cbc24bd6eaf9 100644 --- a/page-object/test-automation/src/main/java/com/iluwatar/pageobject/Page.java +++ b/page-object/test-automation/src/main/java/com/iluwatar/pageobject/Page.java @@ -26,14 +26,10 @@ import org.htmlunit.WebClient; -/** - * Encapsulation for a generic 'Page'. - */ +/** Encapsulation for a generic 'Page'. */ public abstract class Page { - /** - * Application Under Test path This directory location is where html web pages are located. - */ + /** Application Under Test path This directory location is where html web pages are located. */ public static final String AUT_PATH = "../sample-application/src/main/resources/sample-ui/"; protected final WebClient webClient; @@ -53,6 +49,4 @@ public Page(WebClient webClient) { * @return true if so, otherwise false */ public abstract boolean isAt(); - - } diff --git a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java index ff6dd877d50c..1b710a37bc35 100644 --- a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java +++ b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumListPageTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Album Selection and Album Listing - */ +/** Test Album Selection and Album Listing */ class AlbumListPageTest { private final AlbumListPage albumListPage = new AlbumListPage(new WebClient()); @@ -48,5 +46,4 @@ void testSelectAlbum() { albumPage.navigateToPage(); assertTrue(albumPage.isAt()); } - } diff --git a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java index 830201c1cbcd..85a9afe807cc 100644 --- a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java +++ b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/AlbumPageTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Album Page Operations - */ +/** Test Album Page Operations */ class AlbumPageTest { private final AlbumPage albumPage = new AlbumPage(new WebClient()); @@ -45,16 +43,16 @@ void setUp() { @Test void testSaveAlbum() { - var albumPageAfterChanges = albumPage - .changeAlbumTitle("25") - .changeArtist("Adele Laurie Blue Adkins") - .changeAlbumYear(2015) - .changeAlbumRating("B") - .changeNumberOfSongs(20) - .saveChanges(); + var albumPageAfterChanges = + albumPage + .changeAlbumTitle("25") + .changeArtist("Adele Laurie Blue Adkins") + .changeAlbumYear(2015) + .changeAlbumRating("B") + .changeNumberOfSongs(20) + .saveChanges(); assertTrue(albumPageAfterChanges.isAt()); - } @Test @@ -63,5 +61,4 @@ void testCancelChanges() { albumListPage.navigateToPage(); assertTrue(albumListPage.isAt()); } - } diff --git a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java index ed6232030610..660487002b68 100644 --- a/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java +++ b/page-object/test-automation/src/test/java/com/iluwatar/pageobject/LoginPageTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Test Login Page Object - */ +/** Test Login Page Object */ class LoginPageTest { private final LoginPage loginPage = new LoginPage(new WebClient()); @@ -44,12 +42,8 @@ void setUp() { @Test void testLogin() { - var albumListPage = loginPage - .enterUsername("admin") - .enterPassword("password") - .login(); + var albumListPage = loginPage.enterUsername("admin").enterPassword("password").login(); albumListPage.navigateToPage(); assertTrue(albumListPage.isAt()); } - } diff --git a/parameter-object/README.md b/parameter-object/README.md index bbb1c73d95cc..226d4073a352 100644 --- a/parameter-object/README.md +++ b/parameter-object/README.md @@ -40,6 +40,14 @@ wiki.c2.com says > Replace the LongParameterList with a ParameterObject; an object or structure with data members representing the arguments to be passed in. +Mind map + +![Parameter Object mind map](./etc/parameter-object-mind-map.png) + +Flowchart + +![Parameter Object flowchart](./etc/parameter-object-flowchart.png) + ## Programmatic Example of Parameter Object Pattern in Java The Parameter Object design pattern is a way to group multiple parameters into a single object. This simplifies method signatures and enhances code maintainability enabling Java developers to streamline complex method calls, focusing on cleaner and more maintainable Java code. diff --git a/parameter-object/etc/parameter-object-flowchart.png b/parameter-object/etc/parameter-object-flowchart.png new file mode 100644 index 000000000000..c63b41f75d9e Binary files /dev/null and b/parameter-object/etc/parameter-object-flowchart.png differ diff --git a/parameter-object/etc/parameter-object-mind-map.png b/parameter-object/etc/parameter-object-mind-map.png new file mode 100644 index 000000000000..3f920502f28b Binary files /dev/null and b/parameter-object/etc/parameter-object-mind-map.png differ diff --git a/parameter-object/pom.xml b/parameter-object/pom.xml index a9cfec4732f3..9906e880adfb 100644 --- a/parameter-object/pom.xml +++ b/parameter-object/pom.xml @@ -34,6 +34,14 @@ parameter-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/parameter-object/src/main/java/com/iluwatar/parameter/object/App.java b/parameter-object/src/main/java/com/iluwatar/parameter/object/App.java index 8e02434a764f..d4d1e45abd92 100644 --- a/parameter-object/src/main/java/com/iluwatar/parameter/object/App.java +++ b/parameter-object/src/main/java/com/iluwatar/parameter/object/App.java @@ -28,19 +28,18 @@ import org.slf4j.LoggerFactory; /** - * The syntax of Java language doesn’t allow you to declare a method with a predefined value - * for a parameter. Probably the best option to achieve default method parameters in Java is - * by using the method overloading. Method overloading allows you to declare several methods - * with the same name but with a different number of parameters. But the main problem with - * method overloading as a solution for default parameter values reveals itself when a method - * accepts multiple parameters. Creating an overloaded method for each possible combination of - * parameters might be cumbersome. To deal with this issue, the Parameter Object pattern is used. - * The Parameter Object is simply a wrapper object for all parameters of a method. - * It is nothing more than just a regular POJO. The advantage of the Parameter Object over a - * regular method parameter list is the fact that class fields can have default values. - * Once the wrapper class is created for the method parameter list, a corresponding builder class - * is also created. Usually it's an inner static class. The final step is to use the builder - * to construct a new parameter object. For those parameters that are skipped, + * The syntax of Java language doesn’t allow you to declare a method with a predefined value for a + * parameter. Probably the best option to achieve default method parameters in Java is by using the + * method overloading. Method overloading allows you to declare several methods with the same name + * but with a different number of parameters. But the main problem with method overloading as a + * solution for default parameter values reveals itself when a method accepts multiple parameters. + * Creating an overloaded method for each possible combination of parameters might be cumbersome. To + * deal with this issue, the Parameter Object pattern is used. The Parameter Object is simply a + * wrapper object for all parameters of a method. It is nothing more than just a regular POJO. The + * advantage of the Parameter Object over a regular method parameter list is the fact that class + * fields can have default values. Once the wrapper class is created for the method parameter list, + * a corresponding builder class is also created. Usually it's an inner static class. The final step + * is to use the builder to construct a new parameter object. For those parameters that are skipped, * their default values are going to be used. */ public class App { @@ -53,10 +52,8 @@ public class App { * @param args command line args */ public static void main(String[] args) { - ParameterObject params = ParameterObject.newBuilder() - .withType("sneakers") - .sortBy("brand") - .build(); + ParameterObject params = + ParameterObject.newBuilder().withType("sneakers").sortBy("brand").build(); LOGGER.info(params.toString()); LOGGER.info(new SearchService().search(params)); } diff --git a/parameter-object/src/main/java/com/iluwatar/parameter/object/ParameterObject.java b/parameter-object/src/main/java/com/iluwatar/parameter/object/ParameterObject.java index 154733202819..d211d50892cb 100644 --- a/parameter-object/src/main/java/com/iluwatar/parameter/object/ParameterObject.java +++ b/parameter-object/src/main/java/com/iluwatar/parameter/object/ParameterObject.java @@ -27,30 +27,24 @@ import lombok.Getter; import lombok.Setter; -/** - * ParameterObject. - */ +/** ParameterObject. */ @Getter @Setter public class ParameterObject { - /** - * Default values are defined here. - */ + /** Default values are defined here. */ public static final String DEFAULT_SORT_BY = "price"; + public static final SortOrder DEFAULT_SORT_ORDER = SortOrder.ASC; private String type; - /** - * Default values are assigned here. - */ + /** Default values are assigned here. */ private String sortBy = DEFAULT_SORT_BY; + private SortOrder sortOrder = DEFAULT_SORT_ORDER; - /** - * Overriding default values on object creation only when builder object has a valid value. - */ + /** Overriding default values on object creation only when builder object has a valid value. */ private ParameterObject(Builder builder) { setType(builder.type); setSortBy(builder.sortBy != null && !builder.sortBy.isBlank() ? builder.sortBy : sortBy); @@ -63,21 +57,18 @@ public static Builder newBuilder() { @Override public String toString() { - return String.format("ParameterObject[type='%s', sortBy='%s', sortOrder='%s']", - type, sortBy, sortOrder); + return String.format( + "ParameterObject[type='%s', sortBy='%s', sortOrder='%s']", type, sortBy, sortOrder); } - /** - * Builder for ParameterObject. - */ + /** Builder for ParameterObject. */ public static final class Builder { private String type; private String sortBy; private SortOrder sortOrder; - private Builder() { - } + private Builder() {} public Builder withType(String type) { this.type = type; diff --git a/parameter-object/src/main/java/com/iluwatar/parameter/object/SearchService.java b/parameter-object/src/main/java/com/iluwatar/parameter/object/SearchService.java index e356c2f5bcbd..9adf9fbfd40d 100644 --- a/parameter-object/src/main/java/com/iluwatar/parameter/object/SearchService.java +++ b/parameter-object/src/main/java/com/iluwatar/parameter/object/SearchService.java @@ -24,17 +24,15 @@ */ package com.iluwatar.parameter.object; -/** - * SearchService to demonstrate parameter object pattern. - */ +/** SearchService to demonstrate parameter object pattern. */ public class SearchService { /** - * Below two methods of name `search` is overloaded so that we can send a default value for - * one of the criteria and call the final api. A default SortOrder is sent in the first method - * and a default SortBy is sent in the second method. So two separate method definitions are - * needed for having default values for one argument in each case. Hence, multiple overloaded - * methods are needed as the number of argument increases. + * Below two methods of name `search` is overloaded so that we can send a default value for one of + * the criteria and call the final api. A default SortOrder is sent in the first method and a + * default SortBy is sent in the second method. So two separate method definitions are needed for + * having default values for one argument in each case. Hence, multiple overloaded methods are + * needed as the number of argument increases. */ public String search(String type, String sortBy) { return getQuerySummary(type, sortBy, SortOrder.ASC); @@ -44,21 +42,19 @@ public String search(String type, SortOrder sortOrder) { return getQuerySummary(type, "price", sortOrder); } - /** - * The need for multiple method definitions can be avoided by the Parameter Object pattern. - * Below is the example where only one method is required and all the logic for having default - * values are abstracted into the Parameter Object at the time of object creation. + * The need for multiple method definitions can be avoided by the Parameter Object pattern. Below + * is the example where only one method is required and all the logic for having default values + * are abstracted into the Parameter Object at the time of object creation. */ public String search(ParameterObject parameterObject) { - return getQuerySummary(parameterObject.getType(), parameterObject.getSortBy(), - parameterObject.getSortOrder()); + return getQuerySummary( + parameterObject.getType(), parameterObject.getSortBy(), parameterObject.getSortOrder()); } private String getQuerySummary(String type, String sortBy, SortOrder sortOrder) { - return String.format("Requesting shoes of type \"%s\" sorted by \"%s\" in \"%sending\" order..", - type, - sortBy, - sortOrder.getValue()); + return String.format( + "Requesting shoes of type \"%s\" sorted by \"%s\" in \"%sending\" order..", + type, sortBy, sortOrder.getValue()); } } diff --git a/parameter-object/src/main/java/com/iluwatar/parameter/object/SortOrder.java b/parameter-object/src/main/java/com/iluwatar/parameter/object/SortOrder.java index faae9e581fcd..68a7b701ffd5 100644 --- a/parameter-object/src/main/java/com/iluwatar/parameter/object/SortOrder.java +++ b/parameter-object/src/main/java/com/iluwatar/parameter/object/SortOrder.java @@ -26,15 +26,12 @@ import lombok.Getter; -/** - * enum for sort order types. - */ +/** enum for sort order types. */ public enum SortOrder { ASC("asc"), DESC("desc"); - @Getter - private String value; + @Getter private String value; SortOrder(String value) { this.value = value; diff --git a/parameter-object/src/test/java/com/iluwatar/parameter/object/AppTest.java b/parameter-object/src/test/java/com/iluwatar/parameter/object/AppTest.java index d51b71cd86e0..c28e77fed956 100644 --- a/parameter-object/src/test/java/com/iluwatar/parameter/object/AppTest.java +++ b/parameter-object/src/test/java/com/iluwatar/parameter/object/AppTest.java @@ -28,12 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/parameter-object/src/test/java/com/iluwatar/parameter/object/ParameterObjectTest.java b/parameter-object/src/test/java/com/iluwatar/parameter/object/ParameterObjectTest.java index 93b1aff5e4e0..0db0c0d36f72 100644 --- a/parameter-object/src/test/java/com/iluwatar/parameter/object/ParameterObjectTest.java +++ b/parameter-object/src/test/java/com/iluwatar/parameter/object/ParameterObjectTest.java @@ -24,41 +24,38 @@ */ package com.iluwatar.parameter.object; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.junit.jupiter.api.Assertions.assertEquals; - class ParameterObjectTest { private static final Logger LOGGER = LoggerFactory.getLogger(ParameterObjectTest.class); @Test void testForDefaultSortBy() { - //Creating parameter object with default value for SortBy set - ParameterObject params = ParameterObject.newBuilder() - .withType("sneakers") - .sortOrder(SortOrder.DESC) - .build(); - - assertEquals(ParameterObject.DEFAULT_SORT_BY, params.getSortBy(), - "Default SortBy is not set."); - LOGGER.info("{} Default parameter value is set during object creation as no value is passed." - , "SortBy"); + // Creating parameter object with default value for SortBy set + ParameterObject params = + ParameterObject.newBuilder().withType("sneakers").sortOrder(SortOrder.DESC).build(); + + assertEquals(ParameterObject.DEFAULT_SORT_BY, params.getSortBy(), "Default SortBy is not set."); + LOGGER.info( + "{} Default parameter value is set during object creation as no value is passed.", + "SortBy"); } @Test void testForDefaultSortOrder() { - //Creating parameter object with default value for SortOrder set - ParameterObject params = ParameterObject.newBuilder() - .withType("sneakers") - .sortBy("brand") - .build(); - - assertEquals(ParameterObject.DEFAULT_SORT_ORDER, params.getSortOrder(), - "Default SortOrder is not set."); - LOGGER.info("{} Default parameter value is set during object creation as no value is passed." - , "SortOrder"); + // Creating parameter object with default value for SortOrder set + ParameterObject params = + ParameterObject.newBuilder().withType("sneakers").sortBy("brand").build(); + + assertEquals( + ParameterObject.DEFAULT_SORT_ORDER, params.getSortOrder(), "Default SortOrder is not set."); + LOGGER.info( + "{} Default parameter value is set during object creation as no value is passed.", + "SortOrder"); } } diff --git a/parameter-object/src/test/java/com/iluwatar/parameter/object/SearchServiceTest.java b/parameter-object/src/test/java/com/iluwatar/parameter/object/SearchServiceTest.java index 32e6698320af..ce7a0afccce8 100644 --- a/parameter-object/src/test/java/com/iluwatar/parameter/object/SearchServiceTest.java +++ b/parameter-object/src/test/java/com/iluwatar/parameter/object/SearchServiceTest.java @@ -24,13 +24,13 @@ */ package com.iluwatar.parameter.object; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.junit.jupiter.api.Assertions.assertEquals; - class SearchServiceTest { private static final Logger LOGGER = LoggerFactory.getLogger(SearchServiceTest.class); private ParameterObject parameterObject; @@ -38,25 +38,25 @@ class SearchServiceTest { @BeforeEach void setUp() { - //Creating parameter object with default values set - parameterObject = ParameterObject.newBuilder() - .withType("sneakers") - .build(); + // Creating parameter object with default values set + parameterObject = ParameterObject.newBuilder().withType("sneakers").build(); searchService = new SearchService(); } - /** - * Testing parameter object against the overloaded method to verify if the behaviour is same. - */ + /** Testing parameter object against the overloaded method to verify if the behaviour is same. */ @Test void testDefaultParametersMatch() { - assertEquals(searchService.search(parameterObject), searchService.search("sneakers", - SortOrder.ASC), "Default Parameter values do not not match."); + assertEquals( + searchService.search(parameterObject), + searchService.search("sneakers", SortOrder.ASC), + "Default Parameter values do not not match."); LOGGER.info("SortBy Default parameter value matches."); - assertEquals(searchService.search(parameterObject), searchService.search("sneakers", - "price"), "Default Parameter values do not not match."); + assertEquals( + searchService.search(parameterObject), + searchService.search("sneakers", "price"), + "Default Parameter values do not not match."); LOGGER.info("SortOrder Default parameter value matches."); LOGGER.info("testDefaultParametersMatch executed successfully without errors."); diff --git a/partial-response/README.md b/partial-response/README.md index a3704a4c3453..f4161443f687 100644 --- a/partial-response/README.md +++ b/partial-response/README.md @@ -33,6 +33,10 @@ In plain words > The Partial Response design pattern allows a system to send portions of data to the client as they become available, enabling the client to start processing the data before the complete response is received. +Sequence diagram + +![Partial Response sequence diagram](./etc/partial-response-sequence-diagram.png) + ## Programmatic Example of Partial Response Pattern in Java The Partial Response design pattern allows clients to specify which fields of a resource they need. This pattern is useful for reducing the amount of data transferred over the network and allowing clients to start processing data sooner. diff --git a/partial-response/etc/partial-response-sequence-diagram.png b/partial-response/etc/partial-response-sequence-diagram.png new file mode 100644 index 000000000000..ac2f550154ef Binary files /dev/null and b/partial-response/etc/partial-response-sequence-diagram.png differ diff --git a/partial-response/pom.xml b/partial-response/pom.xml index 20c136598ccb..7b10001cabae 100644 --- a/partial-response/pom.xml +++ b/partial-response/pom.xml @@ -34,9 +34,18 @@ 4.0.0 partial-response + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.mockito mockito-junit-jupiter + 5.16.1 test diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/App.java b/partial-response/src/main/java/com/iluwatar/partialresponse/App.java index 2a729f889c44..a9f1be453676 100644 --- a/partial-response/src/main/java/com/iluwatar/partialresponse/App.java +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/App.java @@ -34,7 +34,6 @@ * *

    {@link VideoResource} act as server to serve video information. */ - @Slf4j public class App { @@ -44,17 +43,22 @@ public class App { * @param args program argument. */ public static void main(String[] args) throws Exception { - var videos = Map.of( - 1, new Video(1, "Avatar", 178, "epic science fiction film", - "James Cameron", "English"), - 2, new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", - "Hideaki Anno", "Japanese"), - 3, new Video(3, "Interstellar", 169, "Adventure & Sci-Fi", - "Christopher Nolan", "English") - ); + var videos = + Map.of( + 1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English"), + 2, + new Video( + 2, + "Godzilla Resurgence", + 120, + "Action & drama movie|", + "Hideaki Anno", + "Japanese"), + 3, + new Video( + 3, "Interstellar", 169, "Adventure & Sci-Fi", "Christopher Nolan", "English")); var videoResource = new VideoResource(new FieldJsonMapper(), videos); - LOGGER.info("Retrieving full response from server:-"); LOGGER.info("Get all video information:"); var videoDetails = videoResource.getDetails(1); diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java b/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java index 62ce17320b21..2e4109fdfec1 100644 --- a/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/FieldJsonMapper.java @@ -27,15 +27,13 @@ import java.lang.reflect.Field; import java.util.StringJoiner; -/** - * Map a video to json. - */ +/** Map a video to json. */ public class FieldJsonMapper { /** * Gets json of required fields from video. * - * @param video object containing video information + * @param video object containing video information * @param fields fields information to get * @return json of required fields from video */ diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java b/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java index c0bcac7b2c96..36ce69d265ab 100644 --- a/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/Video.java @@ -25,11 +25,16 @@ package com.iluwatar.partialresponse; /** - * {@link Video} is an entity to serve from server.It contains all video related information. - * Video is a record class. + * {@link Video} is an entity to serve from server.It contains all video related information. Video + * is a record class. */ - -public record Video(Integer id, String title, Integer length, String description, String director, String language) { +public record Video( + Integer id, + String title, + Integer length, + String description, + String director, + String language) { /** * ToString. * @@ -38,12 +43,24 @@ public record Video(Integer id, String title, Integer length, String description @Override public String toString() { return "{" - + "\"id\": " + id + "," - + "\"title\": \"" + title + "\"," - + "\"length\": " + length + "," - + "\"description\": \"" + description + "\"," - + "\"director\": \"" + director + "\"," - + "\"language\": \"" + language + "\"" - + "}"; + + "\"id\": " + + id + + "," + + "\"title\": \"" + + title + + "\"," + + "\"length\": " + + length + + "," + + "\"description\": \"" + + description + + "\"," + + "\"director\": \"" + + director + + "\"," + + "\"language\": \"" + + language + + "\"" + + "}"; } } diff --git a/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java b/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java index 84dd35a3e0d8..6522f143d795 100644 --- a/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java +++ b/partial-response/src/main/java/com/iluwatar/partialresponse/VideoResource.java @@ -27,18 +27,17 @@ import java.util.Map; /** - * The resource record class which serves video information. This class act as server in the demo. Which - * has all video details. + * The resource record class which serves video information. This class act as server in the demo. + * Which has all video details. * * @param fieldJsonMapper map object to json. - * @param videos initialize resource with existing videos. Act as database. + * @param videos initialize resource with existing videos. Act as database. */ - public record VideoResource(FieldJsonMapper fieldJsonMapper, Map videos) { /** * Get Details. * - * @param id video id + * @param id video id * @param fields fields to get information about * @return full response if no fields specified else partial response for given field. */ diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java index 16f5fff29c94..6d712b820dc6 100644 --- a/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/AppTest.java @@ -24,17 +24,14 @@ */ package com.iluwatar.partialresponse; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - Assertions.assertDoesNotThrow(() -> App.main(new String[]{})); + Assertions.assertDoesNotThrow(() -> App.main(new String[] {})); } - -} \ No newline at end of file +} diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java index eb8677130fcc..59e075e6b2b4 100644 --- a/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/FieldJsonMapperTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.partialresponse; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -/** - * tests {@link FieldJsonMapper}. - */ +/** tests {@link FieldJsonMapper}. */ class FieldJsonMapperTest { private static FieldJsonMapper mapper; @@ -41,15 +39,14 @@ static void setUp() { @Test void shouldReturnJsonForSpecifiedFieldsInVideo() throws Exception { - var fields = new String[]{"id", "title", "length"}; - var video = new Video( - 2, "Godzilla Resurgence", 120, - "Action & drama movie|", "Hideaki Anno", "Japanese" - ); + var fields = new String[] {"id", "title", "length"}; + var video = + new Video( + 2, "Godzilla Resurgence", 120, "Action & drama movie|", "Hideaki Anno", "Japanese"); var jsonFieldResponse = mapper.toJson(video, fields); var expectedDetails = "{\"id\": 2,\"title\": \"Godzilla Resurgence\",\"length\": 120}"; Assertions.assertEquals(expectedDetails, jsonFieldResponse); } -} \ No newline at end of file +} diff --git a/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java b/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java index 1b6593d1a3fe..630f68c02cbc 100644 --- a/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java +++ b/partial-response/src/test/java/com/iluwatar/partialresponse/VideoResourceTest.java @@ -36,25 +36,29 @@ import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; -/** - * tests {@link VideoResource}. - */ +/** tests {@link VideoResource}. */ @ExtendWith(MockitoExtension.class) class VideoResourceTest { - @Mock - private static FieldJsonMapper fieldJsonMapper; + @Mock private static FieldJsonMapper fieldJsonMapper; private static VideoResource resource; @BeforeEach void setUp() { - var videos = Map.of( - 1, new Video(1, "Avatar", 178, "epic science fiction film", - "James Cameron", "English"), - 2, new Video(2, "Godzilla Resurgence", 120, "Action & drama movie|", - "Hideaki Anno", "Japanese"), - 3, new Video(3, "Interstellar", 169, "Adventure & Sci-Fi", - "Christopher Nolan", "English")); + var videos = + Map.of( + 1, new Video(1, "Avatar", 178, "epic science fiction film", "James Cameron", "English"), + 2, + new Video( + 2, + "Godzilla Resurgence", + 120, + "Action & drama movie|", + "Hideaki Anno", + "Japanese"), + 3, + new Video( + 3, "Interstellar", 169, "Adventure & Sci-Fi", "Christopher Nolan", "English")); resource = new VideoResource(fieldJsonMapper, videos); } @@ -62,14 +66,15 @@ void setUp() { void shouldGiveVideoDetailsById() throws Exception { var actualDetails = resource.getDetails(1); - var expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": " - + "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\"}"; + var expectedDetails = + "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": " + + "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\"}"; Assertions.assertEquals(expectedDetails, actualDetails); } @Test void shouldGiveSpecifiedFieldsInformationOfVideo() throws Exception { - var fields = new String[]{"id", "title", "length"}; + var fields = new String[] {"id", "title", "length"}; var expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178}"; Mockito.when(fieldJsonMapper.toJson(any(Video.class), eq(fields))).thenReturn(expectedDetails); @@ -81,10 +86,11 @@ void shouldGiveSpecifiedFieldsInformationOfVideo() throws Exception { @Test void shouldAllSpecifiedFieldsInformationOfVideo() throws Exception { - var fields = new String[]{"id", "title", "length", "description", "director", "language"}; + var fields = new String[] {"id", "title", "length", "description", "director", "language"}; - var expectedDetails = "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": " - + "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\"}"; + var expectedDetails = + "{\"id\": 1,\"title\": \"Avatar\",\"length\": 178,\"description\": " + + "\"epic science fiction film\",\"director\": \"James Cameron\",\"language\": \"English\"}"; Mockito.when(fieldJsonMapper.toJson(any(Video.class), eq(fields))).thenReturn(expectedDetails); var actualFieldsDetails = resource.getDetails(1, fields); diff --git a/pipeline/README.md b/pipeline/README.md index e05f151e25ac..0b44fe143a72 100644 --- a/pipeline/README.md +++ b/pipeline/README.md @@ -46,6 +46,10 @@ Wikipedia says > In software engineering, a pipeline consists of a chain of processing elements (processes, threads, coroutines, functions, etc.), arranged so that the output of each element is the input of the next; the name is by analogy to a physical pipeline. +Flowchart + +![Pipeline flowchart](./etc/pipeline-flowchart.png) + ## Programmatic Example of Pipeline Pattern in Java Let's create a string processing pipeline example. The stages of our pipeline are called `Handler`s. diff --git a/pipeline/etc/pipeline-flowchart.png b/pipeline/etc/pipeline-flowchart.png new file mode 100644 index 000000000000..48a5cf513b21 Binary files /dev/null and b/pipeline/etc/pipeline-flowchart.png differ diff --git a/pipeline/pom.xml b/pipeline/pom.xml index a45118dd8f62..f28889c47aa0 100644 --- a/pipeline/pom.xml +++ b/pipeline/pom.xml @@ -34,6 +34,14 @@ pipeline + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/App.java b/pipeline/src/main/java/com/iluwatar/pipeline/App.java index 2f0892e7031d..9f82a6e9eb3a 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/App.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/App.java @@ -45,28 +45,29 @@ public class App { */ public static void main(String[] args) { /* - Suppose we wanted to pass through a String to a series of filtering stages and convert it - as a char array on the last stage. + Suppose we wanted to pass through a String to a series of filtering stages and convert it + as a char array on the last stage. - - Stage handler 1 (pipe): Removing the alphabets, accepts a String input and returns the - processed String output. This will be used by the next handler as its input. + - Stage handler 1 (pipe): Removing the alphabets, accepts a String input and returns the + processed String output. This will be used by the next handler as its input. - - Stage handler 2 (pipe): Removing the digits, accepts a String input and returns the - processed String output. This shall also be used by the last handler we have. + - Stage handler 2 (pipe): Removing the digits, accepts a String input and returns the + processed String output. This shall also be used by the last handler we have. - - Stage handler 3 (pipe): Converting the String input to a char array handler. We would - be returning a different type in here since that is what's specified by the requirement. - This means that at any stages along the pipeline, the handler can return any type of data - as long as it fulfills the requirements for the next handler's input. + - Stage handler 3 (pipe): Converting the String input to a char array handler. We would + be returning a different type in here since that is what's specified by the requirement. + This means that at any stages along the pipeline, the handler can return any type of data + as long as it fulfills the requirements for the next handler's input. - Suppose we wanted to add another handler after ConvertToCharArrayHandler. That handler - then is expected to receive an input of char[] array since that is the type being returned - by the previous handler, ConvertToCharArrayHandler. - */ + Suppose we wanted to add another handler after ConvertToCharArrayHandler. That handler + then is expected to receive an input of char[] array since that is the type being returned + by the previous handler, ConvertToCharArrayHandler. + */ LOGGER.info("Creating pipeline"); - var filters = new Pipeline<>(new RemoveAlphabetsHandler()) - .addHandler(new RemoveDigitsHandler()) - .addHandler(new ConvertToCharArrayHandler()); + var filters = + new Pipeline<>(new RemoveAlphabetsHandler()) + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); var input = "GoYankees123!"; LOGGER.info("Executing pipeline with input: {}", input); var output = filters.execute(input); diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java b/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java index 24393333f1cd..f7edde565c54 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/ConvertToCharArrayHandler.java @@ -28,9 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Stage handler that converts an input String to its char[] array counterpart. - */ +/** Stage handler that converts an input String to its char[] array counterpart. */ class ConvertToCharArrayHandler implements Handler { private static final Logger LOGGER = LoggerFactory.getLogger(ConvertToCharArrayHandler.class); @@ -40,9 +38,9 @@ public char[] process(String input) { var characters = input.toCharArray(); var string = Arrays.toString(characters); LOGGER.info( - String.format("Current handler: %s, input is %s of type %s, output is %s, of type %s", - ConvertToCharArrayHandler.class, input, String.class, string, Character[].class) - ); + String.format( + "Current handler: %s, input is %s of type %s, output is %s, of type %s", + ConvertToCharArrayHandler.class, input, String.class, string, Character[].class)); return characters; } diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java b/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java index 1e72f863c186..a27e0592c27c 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/Handler.java @@ -33,4 +33,4 @@ */ interface Handler { O process(I input); -} \ No newline at end of file +} diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java b/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java index 6c030e513d05..b7a619692a74 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/Pipeline.java @@ -46,4 +46,4 @@ Pipeline addHandler(Handler newHandler) { O execute(I input) { return currentHandler.process(input); } -} \ No newline at end of file +} diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java index 85a23e69065c..3bf01bf8a9a4 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveAlphabetsHandler.java @@ -40,7 +40,8 @@ class RemoveAlphabetsHandler implements Handler { public String process(String input) { var inputWithoutAlphabets = new StringBuilder(); var isAlphabetic = (IntPredicate) Character::isAlphabetic; - input.chars() + input + .chars() .filter(isAlphabetic.negate()) .mapToObj(x -> (char) x) .forEachOrdered(inputWithoutAlphabets::append); @@ -49,11 +50,12 @@ public String process(String input) { LOGGER.info( String.format( "Current handler: %s, input is %s of type %s, output is %s, of type %s", - RemoveAlphabetsHandler.class, input, - String.class, inputWithoutAlphabetsStr, String.class - ) - ); + RemoveAlphabetsHandler.class, + input, + String.class, + inputWithoutAlphabetsStr, + String.class)); return inputWithoutAlphabetsStr; } -} \ No newline at end of file +} diff --git a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java index 75e7a460ef87..e84b1693ad64 100644 --- a/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java +++ b/pipeline/src/main/java/com/iluwatar/pipeline/RemoveDigitsHandler.java @@ -40,7 +40,8 @@ class RemoveDigitsHandler implements Handler { public String process(String input) { var inputWithoutDigits = new StringBuilder(); var isDigit = (IntPredicate) Character::isDigit; - input.chars() + input + .chars() .filter(isDigit.negate()) .mapToObj(x -> (char) x) .forEachOrdered(inputWithoutDigits::append); @@ -49,10 +50,8 @@ public String process(String input) { LOGGER.info( String.format( "Current handler: %s, input is %s of type %s, output is %s, of type %s", - RemoveDigitsHandler.class, input, String.class, inputWithoutDigitsStr, String.class - ) - ); + RemoveDigitsHandler.class, input, String.class, inputWithoutDigitsStr, String.class)); return inputWithoutDigitsStr; } -} \ No newline at end of file +} diff --git a/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java b/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java index 2a49dbe0b5ba..5f1c0d2311b5 100644 --- a/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java +++ b/pipeline/src/test/java/com/iluwatar/pipeline/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.pipeline; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java b/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java index 677c8eb50495..d26069dc7fb8 100644 --- a/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java +++ b/pipeline/src/test/java/com/iluwatar/pipeline/PipelineTest.java @@ -28,20 +28,17 @@ import org.junit.jupiter.api.Test; -/** - * Test for {@link Pipeline} - */ +/** Test for {@link Pipeline} */ class PipelineTest { @Test void testAddHandlersToPipeline() { - var filters = new Pipeline<>(new RemoveAlphabetsHandler()) - .addHandler(new RemoveDigitsHandler()) - .addHandler(new ConvertToCharArrayHandler()); + var filters = + new Pipeline<>(new RemoveAlphabetsHandler()) + .addHandler(new RemoveDigitsHandler()) + .addHandler(new ConvertToCharArrayHandler()); assertArrayEquals( - new char[]{'#', '!', '(', '&', '%', '#', '!'}, - filters.execute("#H!E(L&L0O%THE3R#34E!") - ); + new char[] {'#', '!', '(', '&', '%', '#', '!'}, filters.execute("#H!E(L&L0O%THE3R#34E!")); } } diff --git a/poison-pill/README.md b/poison-pill/README.md index 31921326e347..5e9e690cb962 100644 --- a/poison-pill/README.md +++ b/poison-pill/README.md @@ -29,6 +29,10 @@ In plain words > Poison Pill is a known message structure that ends the message exchange. +Sequence diagram + +![Poison Pill sequence diagram](./etc/poison-pill-sequence-diagram.png) + ## Programmatic Example of Poison Pill Pattern in Java In this Java example, the Poison Pill serves as a shutdown signal within message queues, demonstrating effective thread management and consumer communication. @@ -210,10 +214,6 @@ Program output: 07:43:01.520 [Thread-0] INFO com.iluwatar.poison.pill.Consumer -- Consumer CONSUMER_1 receive request to terminate. ``` -## Detailed Explanation of Poison Pill Pattern with Real-World Examples - -![Poison Pill](./etc/poison-pill.png "Poison Pill") - ## When to Use the Poison Pill Pattern in Java Use the Poison Pill idiom when: diff --git a/poison-pill/etc/poison-pill-sequence-diagram.png b/poison-pill/etc/poison-pill-sequence-diagram.png new file mode 100644 index 000000000000..9976f2cc69de Binary files /dev/null and b/poison-pill/etc/poison-pill-sequence-diagram.png differ diff --git a/poison-pill/pom.xml b/poison-pill/pom.xml index 803c9fd755dc..74f4ce8a9a3d 100644 --- a/poison-pill/pom.xml +++ b/poison-pill/pom.xml @@ -34,6 +34,14 @@ poison-pill + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/App.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/App.java index 0dd2b2e45e66..222bfdbb59b4 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/App.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/App.java @@ -50,11 +50,13 @@ public static void main(String[] args) { new Thread(consumer::consume).start(); - new Thread(() -> { - producer.send("hand shake"); - producer.send("some very important information"); - producer.send("bye!"); - producer.stop(); - }).start(); + new Thread( + () -> { + producer.send("hand shake"); + producer.send("some very important information"); + producer.send("bye!"); + producer.stop(); + }) + .start(); } } diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java index 8fc05f8ab5b5..c6c24af95d42 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Consumer.java @@ -27,9 +27,7 @@ import com.iluwatar.poison.pill.Message.Headers; import lombok.extern.slf4j.Slf4j; -/** - * Class responsible for receiving and handling submitted to the queue messages. - */ +/** Class responsible for receiving and handling submitted to the queue messages. */ @Slf4j public class Consumer { @@ -41,9 +39,7 @@ public Consumer(String name, MqSubscribePoint queue) { this.queue = queue; } - /** - * Consume message. - */ + /** Consume message. */ public void consume() { while (true) { try { diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java index 09cb20a6c9d3..ab7dbdf31ed7 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Message.java @@ -32,44 +32,43 @@ */ public interface Message { - Message POISON_PILL = new Message() { - - @Override - public void addHeader(Headers header, String value) { - throw poison(); - } - - @Override - public String getHeader(Headers header) { - throw poison(); - } - - @Override - public Map getHeaders() { - throw poison(); - } - - @Override - public void setBody(String body) { - throw poison(); - } - - @Override - public String getBody() { - throw poison(); - } - - private RuntimeException poison() { - return new UnsupportedOperationException("Poison"); - } - - }; - - /** - * Enumeration of Type of Headers. - */ + Message POISON_PILL = + new Message() { + + @Override + public void addHeader(Headers header, String value) { + throw poison(); + } + + @Override + public String getHeader(Headers header) { + throw poison(); + } + + @Override + public Map getHeaders() { + throw poison(); + } + + @Override + public void setBody(String body) { + throw poison(); + } + + @Override + public String getBody() { + throw poison(); + } + + private RuntimeException poison() { + return new UnsupportedOperationException("Poison"); + } + }; + + /** Enumeration of Type of Headers. */ enum Headers { - DATE, SENDER + DATE, + SENDER } void addHeader(Headers header, String value); diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java index 9477a80b2df8..91584b5171a8 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MessageQueue.java @@ -27,6 +27,4 @@ /** * Represents abstraction of channel (or pipe) that bounds {@link Producer} and {@link Consumer}. */ -public interface MessageQueue extends MqPublishPoint, MqSubscribePoint { - -} +public interface MessageQueue extends MqPublishPoint, MqSubscribePoint {} diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java index f4fdac9441ca..a4c986d76f7b 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqPublishPoint.java @@ -24,9 +24,7 @@ */ package com.iluwatar.poison.pill; -/** - * Endpoint to publish {@link Message} to queue. - */ +/** Endpoint to publish {@link Message} to queue. */ public interface MqPublishPoint { void put(Message msg) throws InterruptedException; diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java index a89c6fb82113..d0521cc7e8fd 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/MqSubscribePoint.java @@ -24,9 +24,7 @@ */ package com.iluwatar.poison.pill; -/** - * Endpoint to retrieve {@link Message} from queue. - */ +/** Endpoint to retrieve {@link Message} from queue. */ public interface MqSubscribePoint { Message take() throws InterruptedException; diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java index 23e703db6210..1d9966139772 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/Producer.java @@ -39,22 +39,19 @@ public class Producer { private final String name; private boolean isStopped; - /** - * Constructor. - */ + /** Constructor. */ public Producer(String name, MqPublishPoint queue) { this.name = name; this.queue = queue; this.isStopped = false; } - /** - * Send message to queue. - */ + /** Send message to queue. */ public void send(String body) { if (isStopped) { - throw new IllegalStateException(String.format( - "Producer %s was stopped and fail to deliver requested message [%s].", body, name)); + throw new IllegalStateException( + String.format( + "Producer %s was stopped and fail to deliver requested message [%s].", body, name)); } var msg = new SimpleMessage(); msg.addHeader(Headers.DATE, new Date().toString()); @@ -69,9 +66,7 @@ public void send(String body) { } } - /** - * Stop system by sending poison pill. - */ + /** Stop system by sending poison pill. */ public void stop() { isStopped = true; try { diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java index 013b0e5d4d0d..c8efb7ec2fcb 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessage.java @@ -28,9 +28,7 @@ import java.util.HashMap; import java.util.Map; -/** - * {@link Message} basic implementation. - */ +/** {@link Message} basic implementation. */ public class SimpleMessage implements Message { private final Map headers = new HashMap<>(); diff --git a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessageQueue.java b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessageQueue.java index 288664c7fa23..32fc48a2a1c7 100644 --- a/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessageQueue.java +++ b/poison-pill/src/main/java/com/iluwatar/poison/pill/SimpleMessageQueue.java @@ -27,9 +27,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -/** - * Bounded blocking queue wrapper. - */ +/** Bounded blocking queue wrapper. */ public class SimpleMessageQueue implements MessageQueue { private final BlockingQueue queue; diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java index b121663829e2..a4c2844fe42b 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.poison.pill; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java index 8ed5f2cb1668..3d03a5043520 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/ConsumerTest.java @@ -37,10 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * ConsumerTest - * - */ +/** ConsumerTest */ class ConsumerTest { private InMemoryAppender appender; @@ -57,12 +54,12 @@ void tearDown() { @Test void testConsume() throws Exception { - final var messages = List.of( - createMessage("you", "Hello!"), - createMessage("me", "Hi!"), - Message.POISON_PILL, - createMessage("late_for_the_party", "Hello? Anyone here?") - ); + final var messages = + List.of( + createMessage("you", "Hello!"), + createMessage("me", "Hi!"), + Message.POISON_PILL, + createMessage("late_for_the_party", "Hello? Anyone here?")); final var queue = new SimpleMessageQueue(messages.size()); for (final var message : messages) { @@ -79,7 +76,7 @@ void testConsume() throws Exception { /** * Create a new message from the given sender with the given message body * - * @param sender The sender's name + * @param sender The sender's name * @param message The message body * @return The message instance */ @@ -108,5 +105,4 @@ public boolean logContains(String message) { return log.stream().map(ILoggingEvent::getFormattedMessage).anyMatch(message::equals); } } - } diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java index aeed814b7a4c..40688f8c386d 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/PoisonMessageTest.java @@ -30,15 +30,13 @@ import org.junit.jupiter.api.Test; -/** - * PoisonMessageTest - * - */ +/** PoisonMessageTest */ class PoisonMessageTest { @Test void testAddHeader() { - assertThrows(UnsupportedOperationException.class, () -> POISON_PILL.addHeader(Headers.SENDER, "sender")); + assertThrows( + UnsupportedOperationException.class, () -> POISON_PILL.addHeader(Headers.SENDER, "sender")); } @Test @@ -60,5 +58,4 @@ void testSetBody() { void testGetBody() { assertThrows(UnsupportedOperationException.class, POISON_PILL::getBody); } - } diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java index 3eb0a2dcc346..3d5d50f70dca 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/ProducerTest.java @@ -35,10 +35,7 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -/** - * ProducerTest - * - */ +/** ProducerTest */ class ProducerTest { @Test @@ -76,11 +73,11 @@ void testStop() throws Exception { } catch (IllegalStateException e) { assertNotNull(e); assertNotNull(e.getMessage()); - assertEquals("Producer Hello! was stopped and fail to deliver requested message [producer].", + assertEquals( + "Producer Hello! was stopped and fail to deliver requested message [producer].", e.getMessage()); } verifyNoMoreInteractions(publishPoint); } - } diff --git a/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java b/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java index f066d1425c1e..77c547d617da 100644 --- a/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java +++ b/poison-pill/src/test/java/com/iluwatar/poison/pill/SimpleMessageTest.java @@ -32,10 +32,7 @@ import org.junit.jupiter.api.Test; -/** - * SimpleMessageTest - * - */ +/** SimpleMessageTest */ class SimpleMessageTest { @Test @@ -55,6 +52,7 @@ void testGetHeaders() { void testUnModifiableHeaders() { final var message = new SimpleMessage(); final var headers = message.getHeaders(); - assertThrows(UnsupportedOperationException.class, () -> headers.put(Message.Headers.SENDER, "test")); + assertThrows( + UnsupportedOperationException.class, () -> headers.put(Message.Headers.SENDER, "test")); } -} \ No newline at end of file +} diff --git a/pom.xml b/pom.xml index 88381e34010e..8337c97966da 100644 --- a/pom.xml +++ b/pom.xml @@ -35,22 +35,35 @@ Java Design Patterns Java Design Patterns + UTF-8 - 4.0.0.4121 - 2.7.5 - 0.8.12 + + + 3.4.4 + 5.11.4 + 5.14.2 + 1.5.18 + 2.0.17 + + + 0.8.13 1.4 - 4.5.0 + 4.7.0 2.11.0 6.0.0 1.1.0 - 3.5.1 - 3.6.0 - 4.6 - 2.1.1 - 2.0.16 - 1.5.6 + 1.18.38 + 5.4.0 + 5.4.0 + 2.3.232 + + + 3.5.2 + 5.0.0 + 3.14.0 + + 5.1.0.4751 https://sonarcloud.io iluwatar iluwatar_java-design-patterns @@ -58,166 +71,182 @@ Java Design Patterns + + abstract-document abstract-factory - collecting-parameter - monitor - builder - factory-method - prototype - singleton + active-object + acyclic-visitor adapter + ambassador + anti-corruption-layer + arrange-act-assert + async-method-invocation + balking + bloc bridge + builder + business-delegate + bytecode + caching + callback + chain-of-responsibility + circuit-breaker + clean-architecture + client-session + collecting-parameter + collection-pipeline + combinator + command + command-query-responsibility-segregation + commander + component composite + composite-entity + composite-view + context-object + converter + curiously-recurring-template-pattern + currying data-access-object + data-bus + data-locality data-mapper + data-transfer-object decorator - facade - flyweight - proxy - chain-of-responsibility - command - interpreter - iterator - mediator - memento - model-view-presenter - observer - state - strategy - template-method - version-number - visitor + delegation + dependency-injection + dirty-flag + domain-model + double-buffer double-checked-locking - servant - service-locator - null-object + double-dispatch + dynamic-proxy event-aggregator - callback + event-based-asynchronous + event-driven-architecture + event-queue + event-sourcing execute-around - property - intercepting-filter - producer-consumer - pipeline - poison-pill - lazy-loading - service-layer - specification - tolerant-reader - model-view-controller + extension-objects + facade + factory + factory-kit + factory-method + fanout-fanin + feature-toggle + filterer + fluent-interface flux - double-dispatch - multiton - resource-acquisition-is-initialization - twin - private-class-data - object-pool - dependency-injection + flyweight front-controller - repository - async-method-invocation - monostate - step-builder - business-delegate + function-composition + game-loop + gateway + guarded-suspension half-sync-half-async + health-check + hexagonal-architecture + identity-map + intercepting-filter + interpreter + iterator layered-architecture - fluent-interface - reactor - caching - delegation - event-driven-architecture + lazy-loading + leader-election + leader-followers + lockable-object + map-reduce + marker-interface + master-worker + mediator + memento + metadata-mapping + microservices-aggregrator microservices-api-gateway - factory-kit - feature-toggle - value-object + microservices-client-side-ui-composition + microservices-distributed-tracing + microservices-idempotent-consumer + microservices-log-aggregation + model-view-controller + model-view-intent + model-view-presenter + model-view-viewmodel monad + money + monitor + monolithic-architecture + monostate + multiton mute-idiom - hexagonal-architecture - abstract-document - microservices-aggregrator - promise + notification + null-object + object-mother + object-pool + observer + optimistic-offline-lock page-controller page-object - event-based-asynchronous - event-queue - queue-based-load-leveling - object-mother - data-bus - converter - guarded-suspension - balking - extension-objects - marker-interface - command-query-responsibility-segregation - event-sourcing - data-transfer-object - throttling - unit-of-work + parameter-object partial-response + pipeline + poison-pill + presentation-model + private-class-data + producer-consumer + promise + property + prototype + proxy + publish-subscribe + queue-based-load-leveling + reactor + registry + repository + resource-acquisition-is-initialization retry - dirty-flag - trampoline - ambassador - acyclic-visitor - collection-pipeline - master-worker - spatial-partition - commander - type-object - bytecode - leader-election - data-locality - subclass-sandbox - circuit-breaker role-object saga - double-buffer - sharding - game-loop - combinator - update-method - leader-followers - strangler - arrange-act-assert - transaction-script - registry - filterer - factory separated-interface - special-case - parameter-object - active-object - model-view-viewmodel - composite-entity - table-module - presentation-model - lockable-object - fanout-fanin - domain-model - composite-view - metadata-mapping - service-to-worker - client-session - model-view-intent - currying serialized-entity - identity-map - component - context-object - optimistic-offline-lock - curiously-recurring-template-pattern - microservices-log-aggregation - anti-corruption-layer - health-check - notification - single-table-inheritance - dynamic-proxy - gateway serialized-lob + servant server-session + service-layer + service-locator + service-stub + service-to-worker + session-facade + sharding + single-table-inheritance + singleton + spatial-partition + special-case + specification + state + step-builder + strangler + strategy + subclass-sandbox + table-inheritance + table-module + template-method + templateview + thread-pool-executor + throttling + tolerant-reader + trampoline + transaction-script + twin + type-object + unit-of-work + update-method + value-object + version-number virtual-proxy - function-composition - microservices-distributed-tracing - microservices-idempotent-consumer + visitor + backpressure + actor-model + @@ -229,10 +258,29 @@ org.springframework.boot - spring-boot-dependencies + spring-boot-starter + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-actuator + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring-boot.version} + + + org.springframework.boot + spring-boot-starter-test + test ${spring-boot.version} - pom - import commons-dbcp @@ -260,27 +308,61 @@ ${system-lambda.version} test + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-migrationsupport + ${junit.version} + test + + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + org.mockito + mockito-core + ${mockito.version} + + + org.mongodb + bson + ${bson.version} + + + org.mongodb + mongodb-driver-legacy + ${mongo.version} + + + com.h2database + h2 + ${h2.version} + - - org.slf4j - slf4j-api - ${slf4j.version} - - - ch.qos.logback - logback-classic - ${logback.version} - - - ch.qos.logback - logback-core - ${logback.version} - org.projectlombok lombok + ${lombok.version} provided @@ -290,9 +372,17 @@ org.apache.maven.plugins maven-compiler-plugin + ${maven-compiler-plugin.version} - 17 - 17 + 21 + 21 + + + org.projectlombok + lombok + ${lombok.version} + + @@ -318,9 +408,8 @@ jar-with-dependencies - - ${project.artifactId} + + ${project.artifactId}-${project.version} false @@ -334,28 +423,6 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - ${maven-checkstyle-plugin.version} - - - validate - - check - - validate - - google_checks.xml - checkstyle-suppressions.xml - - true - warning - false - - - - com.mycila license-maven-plugin @@ -410,27 +477,24 @@ - com.iluwatar.urm - urm-maven-plugin - ${urm-maven-plugin.version} - - - ${project.basedir}/etc - - com.iluwatar - - true - false - plantuml - + com.diffplug.spotless + spotless-maven-plugin + 2.44.4 - process-classes - map + check + apply + + + + 1.17.0 + + + diff --git a/presentation-model/README.md b/presentation-model/README.md index 597c4580309f..df191ddb6591 100644 --- a/presentation-model/README.md +++ b/presentation-model/README.md @@ -29,6 +29,10 @@ In plain words > The Presentation Model design pattern separates the UI logic from the business logic by creating an intermediate model that represents the data and behavior of the UI independently, enhancing testability, maintainability, and flexibility. +Architecture diagram + +![Presentation Model Architecture Diagram](./etc/presentation-model-architecture-diagram.png) + ## Programmatic Example of Presentation Model Pattern in Java The Presentation Model design pattern is a pattern that separates the responsibility of managing the state and behavior of the GUI in a separate model class. This model class is not tied to the view and can be used to test the GUI behavior independently of the GUI itself. diff --git a/presentation-model/etc/presentation-model-architecture-diagram.png b/presentation-model/etc/presentation-model-architecture-diagram.png new file mode 100644 index 000000000000..8c6718dfb89b Binary files /dev/null and b/presentation-model/etc/presentation-model-architecture-diagram.png differ diff --git a/presentation-model/pom.xml b/presentation-model/pom.xml index 3edea0713677..e2701838cb60 100644 --- a/presentation-model/pom.xml +++ b/presentation-model/pom.xml @@ -34,6 +34,14 @@ presentation-model + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/Album.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/Album.java index aa32312defbf..af2dc0662395 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/Album.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/Album.java @@ -28,28 +28,20 @@ import lombok.Getter; import lombok.Setter; -/** - *A class used to store the information of album. - */ +/** A class used to store the information of album. */ @Setter @Getter @AllArgsConstructor public class Album { - /** - * the title of the album. - */ + /** the title of the album. */ private String title; - /** - * the artist name of the album. - */ + + /** the artist name of the album. */ private String artist; - /** - * is the album classical, true or false. - */ + + /** is the album classical, true or false. */ private boolean isClassical; - /** - * only when the album is classical, - * composer can have content. - */ + + /** only when the album is classical, composer can have content. */ private String composer; } diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/App.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/App.java index d799480b86e5..0393c48265fa 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/App.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/App.java @@ -27,16 +27,13 @@ import lombok.extern.slf4j.Slf4j; /** - * The Presentation model pattern is used to divide the presentation and controlling. - * This demo is a used to information of some albums with GUI. + * The Presentation model pattern is used to divide the presentation and controlling. This demo is a + * used to information of some albums with GUI. */ @Slf4j public final class App { - /** - * the constructor. - */ - private App() { - } + /** the constructor. */ + private App() {} /** * main method. @@ -48,4 +45,3 @@ public static void main(final String[] args) { view.createView(); } } - diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/DisplayedAlbums.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/DisplayedAlbums.java index 11dae6df65ce..0e74e4c6db61 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/DisplayedAlbums.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/DisplayedAlbums.java @@ -29,21 +29,14 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * a class used to deal with albums. - * - */ +/** a class used to deal with albums. */ @Slf4j @Getter public class DisplayedAlbums { - /** - * albums a list of albums. - */ + /** albums a list of albums. */ private final List albums; - /** - * a constructor method. - */ + /** a constructor method. */ public DisplayedAlbums() { this.albums = new ArrayList<>(); } @@ -51,15 +44,13 @@ public DisplayedAlbums() { /** * a method used to add a new album to album list. * - * @param title the title of the album. - * @param artist the artist name of the album. + * @param title the title of the album. + * @param artist the artist name of the album. * @param isClassical is the album classical, true or false. - * @param composer only when the album is classical, - * composer can have content. + * @param composer only when the album is classical, composer can have content. */ - public void addAlbums(final String title, - final String artist, final boolean isClassical, - final String composer) { + public void addAlbums( + final String title, final String artist, final boolean isClassical, final String composer) { if (isClassical) { this.albums.add(new Album(title, artist, true, composer)); } else { diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/PresentationModel.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/PresentationModel.java index 67414d6aeb74..8f6f7597b1ef 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/PresentationModel.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/PresentationModel.java @@ -26,22 +26,16 @@ import lombok.extern.slf4j.Slf4j; -/** - * The class between view and albums, it is used to control the data. - */ +/** The class between view and albums, it is used to control the data. */ @Slf4j public class PresentationModel { - /** - * the data of all albums that will be shown. - */ + /** the data of all albums that will be shown. */ private final DisplayedAlbums data; - /** - * the no of selected album. - */ + + /** the no of selected album. */ private int selectedAlbumNumber; - /** - * the selected album. - */ + + /** the selected album. */ private Album selectedAlbum; /** @@ -50,17 +44,23 @@ public class PresentationModel { * @return a instance of DsAlbum which store the data. */ public static DisplayedAlbums albumDataSet() { - var titleList = new String[]{"HQ", "The Rough Dancer and Cyclical Night", - "The Black Light", "Symphony No.5"}; - var artistList = new String[]{"Roy Harper", "Astor Piazzola", - "The Black Light", "CBSO"}; - var isClassicalList = new boolean[]{false, false, false, true}; - var composerList = new String[]{null, null, null, "Sibelius"}; + var titleList = + new String[] { + "HQ", "The Rough Dancer and Cyclical Night", + "The Black Light", "Symphony No.5" + }; + var artistList = + new String[] { + "Roy Harper", "Astor Piazzola", + "The Black Light", "CBSO" + }; + var isClassicalList = new boolean[] {false, false, false, true}; + var composerList = new String[] {null, null, null, "Sibelius"}; var result = new DisplayedAlbums(); for (var i = 1; i <= titleList.length; i++) { - result.addAlbums(titleList[i - 1], artistList[i - 1], - isClassicalList[i - 1], composerList[i - 1]); + result.addAlbums( + titleList[i - 1], artistList[i - 1], isClassicalList[i - 1], composerList[i - 1]); } return result; } @@ -82,8 +82,7 @@ public PresentationModel(final DisplayedAlbums dataOfAlbums) { * @param albumNumber the number of album which is shown on the view. */ public void setSelectedAlbumNumber(final int albumNumber) { - LOGGER.info("Change select number from {} to {}", - this.selectedAlbumNumber, albumNumber); + LOGGER.info("Change select number from {} to {}", this.selectedAlbumNumber, albumNumber); this.selectedAlbumNumber = albumNumber; this.selectedAlbum = data.getAlbums().get(this.selectedAlbumNumber - 1); } @@ -103,8 +102,7 @@ public String getTitle() { * @param value the title which user want to user. */ public void setTitle(final String value) { - LOGGER.info("Change album title from {} to {}", - selectedAlbum.getTitle(), value); + LOGGER.info("Change album title from {} to {}", selectedAlbum.getTitle(), value); selectedAlbum.setTitle(value); } @@ -123,8 +121,7 @@ public String getArtist() { * @param value the name want artist to be. */ public void setArtist(final String value) { - LOGGER.info("Change album artist from {} to {}", - selectedAlbum.getArtist(), value); + LOGGER.info("Change album artist from {} to {}", selectedAlbum.getArtist(), value); selectedAlbum.setArtist(value); } @@ -143,8 +140,7 @@ public boolean getIsClassical() { * @param value is the album classical. */ public void setIsClassical(final boolean value) { - LOGGER.info("Change album isClassical from {} to {}", - selectedAlbum.isClassical(), value); + LOGGER.info("Change album isClassical from {} to {}", selectedAlbum.isClassical(), value); selectedAlbum.setClassical(value); } @@ -164,8 +160,7 @@ public String getComposer() { */ public void setComposer(final String value) { if (selectedAlbum.isClassical()) { - LOGGER.info("Change album composer from {} to {}", - selectedAlbum.getComposer(), value); + LOGGER.info("Change album composer from {} to {}", selectedAlbum.getComposer(), value); selectedAlbum.setComposer(value); } else { LOGGER.info("Composer can not be changed"); diff --git a/presentation-model/src/main/java/com/iluwatar/presentationmodel/View.java b/presentation-model/src/main/java/com/iluwatar/presentationmodel/View.java index 2d694644f986..f62335293f0a 100644 --- a/presentation-model/src/main/java/com/iluwatar/presentationmodel/View.java +++ b/presentation-model/src/main/java/com/iluwatar/presentationmodel/View.java @@ -35,70 +35,52 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * Generates the GUI of albums. - */ +/** Generates the GUI of albums. */ @Getter @Slf4j public class View { - /** - * the model that controls this view. - */ + /** the model that controls this view. */ private final PresentationModel model; - /** - * the filed to show and modify title. - */ + /** the filed to show and modify title. */ private TextField txtTitle; - /** - * the filed to show and modify the name of artist. - */ + + /** the filed to show and modify the name of artist. */ private TextField txtArtist; - /** - * the checkbox for is classical. - */ + + /** the checkbox for is classical. */ private JCheckBox chkClassical; - /** - * the filed to show and modify composer. - */ + + /** the filed to show and modify composer. */ private TextField txtComposer; - /** - * a list to show all the name of album. - */ + + /** a list to show all the name of album. */ private JList albumList; - /** - * a button to apply of all the change. - */ + + /** a button to apply of all the change. */ private JButton apply; - /** - * roll back the change. - */ + + /** roll back the change. */ private JButton cancel; - /** - * the value of the text field size. - */ + /** the value of the text field size. */ static final int WIDTH_TXT = 200; + static final int HEIGHT_TXT = 50; - /** - * the value of the GUI size and location. - */ + /** the value of the GUI size and location. */ static final int LOCATION_X = 200; + static final int LOCATION_Y = 200; static final int WIDTH = 500; static final int HEIGHT = 300; - /** - * constructor method. - */ + /** constructor method. */ public View() { model = new PresentationModel(PresentationModel.albumDataSet()); } - /** - * save the data to PresentationModel. - */ + /** save the data to PresentationModel. */ public void saveToMod() { LOGGER.info("Save data to PresentationModel"); model.setArtist(txtArtist.getText()); @@ -107,9 +89,7 @@ public void saveToMod() { model.setComposer(txtComposer.getText()); } - /** - * load the data from PresentationModel. - */ + /** load the data from PresentationModel. */ public void loadFromMod() { LOGGER.info("Load data from PresentationModel"); txtArtist.setText(model.getArtist()); @@ -119,22 +99,21 @@ public void loadFromMod() { txtComposer.setText(model.getComposer()); } - /** - * initialize the GUI. - */ + /** initialize the GUI. */ public void createView() { var frame = new JFrame("Album"); var b1 = Box.createHorizontalBox(); frame.add(b1); albumList = new JList<>(model.getAlbumList()); - albumList.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - model.setSelectedAlbumNumber(albumList.getSelectedIndex() + 1); - loadFromMod(); - } - }); + albumList.addMouseListener( + new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + model.setSelectedAlbumNumber(albumList.getSelectedIndex() + 1); + loadFromMod(); + } + }); b1.add(albumList); var b2 = Box.createVerticalBox(); @@ -148,30 +127,33 @@ public void mouseClicked(final MouseEvent e) { chkClassical = new JCheckBox(); txtComposer = new TextField(); - chkClassical.addActionListener(itemEvent -> { - txtComposer.setEditable(chkClassical.isSelected()); - if (!chkClassical.isSelected()) { - txtComposer.setText(""); - } - }); + chkClassical.addActionListener( + itemEvent -> { + txtComposer.setEditable(chkClassical.isSelected()); + if (!chkClassical.isSelected()) { + txtComposer.setText(""); + } + }); txtComposer.setSize(WIDTH_TXT, HEIGHT_TXT); txtComposer.setEditable(model.getIsClassical()); apply = new JButton("Apply"); - apply.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - saveToMod(); - loadFromMod(); - } - }); + apply.addMouseListener( + new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + saveToMod(); + loadFromMod(); + } + }); cancel = new JButton("Cancel"); - cancel.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(final MouseEvent e) { - loadFromMod(); - } - }); + cancel.addMouseListener( + new MouseAdapter() { + @Override + public void mouseClicked(final MouseEvent e) { + loadFromMod(); + } + }); b2.add(txtArtist); b2.add(txtTitle); @@ -186,5 +168,4 @@ public void mouseClicked(final MouseEvent e) { frame.setBounds(LOCATION_X, LOCATION_Y, WIDTH, HEIGHT); frame.setVisible(true); } - } diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/AlbumTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/AlbumTest.java index dd4d84884093..1f87055b4e59 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/AlbumTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/AlbumTest.java @@ -24,35 +24,35 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + class AlbumTest { @Test - void testSetTitle(){ + void testSetTitle() { Album album = new Album("a", "b", false, ""); album.setTitle("b"); assertEquals("b", album.getTitle()); } @Test - void testSetArtist(){ + void testSetArtist() { Album album = new Album("a", "b", false, ""); album.setArtist("c"); assertEquals("c", album.getArtist()); } @Test - void testSetClassical(){ + void testSetClassical() { Album album = new Album("a", "b", false, ""); album.setClassical(true); assertTrue(album.isClassical()); } @Test - void testSetComposer(){ + void testSetComposer() { Album album = new Album("a", "b", false, ""); album.setClassical(true); album.setComposer("w"); diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/AppTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/AppTest.java index c3715ed5a766..fb8780a1fe67 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/AppTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/AppTest.java @@ -24,20 +24,20 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.Test; + /** * 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} + *

    Solution: Inserted assertion to check whether the execution of the main method in {@link App} * throws an exception. */ class AppTest { - @Test - void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/DisplayedAlbumsTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/DisplayedAlbumsTest.java index b9d5716427bd..8c5599fe1537 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/DisplayedAlbumsTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/DisplayedAlbumsTest.java @@ -24,21 +24,20 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Test; + class DisplayedAlbumsTest { @Test - void testAdd_true(){ + void testAdd_true() { DisplayedAlbums displayedAlbums = new DisplayedAlbums(); displayedAlbums.addAlbums("title", "artist", true, "composer"); assertEquals("composer", displayedAlbums.getAlbums().get(0).getComposer()); - } @Test - void testAdd_false(){ + void testAdd_false() { DisplayedAlbums displayedAlbums = new DisplayedAlbums(); displayedAlbums.addAlbums("title", "artist", false, "composer"); assertEquals("", displayedAlbums.getAlbums().get(0).getComposer()); diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/PresentationTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/PresentationTest.java index 2a7d26caf006..0326824c0882 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/PresentationTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/PresentationTest.java @@ -24,15 +24,16 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + class PresentationTest { - String[] albumList = {"HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5"}; + String[] albumList = { + "HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5" + }; @Test void testCreateAlbumList() { diff --git a/presentation-model/src/test/java/com/iluwatar/presentationmodel/ViewTest.java b/presentation-model/src/test/java/com/iluwatar/presentationmodel/ViewTest.java index 7a0e6edd04c2..6428c6ac0036 100644 --- a/presentation-model/src/test/java/com/iluwatar/presentationmodel/ViewTest.java +++ b/presentation-model/src/test/java/com/iluwatar/presentationmodel/ViewTest.java @@ -24,15 +24,18 @@ */ package com.iluwatar.presentationmodel; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + class ViewTest { - String[] albumList = {"HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5"}; + String[] albumList = { + "HQ", "The Rough Dancer and Cyclical Night", "The Black Light", "Symphony No.5" + }; @Test - void testSave_setArtistAndTitle(){ + void testSave_setArtistAndTitle() { View view = new View(); view.createView(); String testTitle = "testTitle"; @@ -46,7 +49,7 @@ void testSave_setArtistAndTitle(){ } @Test - void testSave_setClassicalAndComposer(){ + void testSave_setClassicalAndComposer() { View view = new View(); view.createView(); boolean isClassical = true; @@ -60,7 +63,7 @@ void testSave_setClassicalAndComposer(){ } @Test - void testLoad_1(){ + void testLoad_1() { View view = new View(); view.createView(); view.getModel().setSelectedAlbumNumber(2); @@ -69,7 +72,7 @@ void testLoad_1(){ } @Test - void testLoad_2(){ + void testLoad_2() { View view = new View(); view.createView(); view.getModel().setSelectedAlbumNumber(4); diff --git a/private-class-data/README.md b/private-class-data/README.md index 18dd48053849..fe9bc08e0b87 100644 --- a/private-class-data/README.md +++ b/private-class-data/README.md @@ -33,6 +33,14 @@ Wikipedia says > Private class data is a design pattern in computer programming used to encapsulate class attributes and their manipulation. +Mind map + +![Private Class Data mind map](./etc/private-class-data-mind-map.png) + +Flowchart + +![Private Class Data flowchart](./etc/private-class-data-flowchart.png) + ## Programmatic Example of Private Class Data Pattern in Java Imagine you are cooking a stew for your family dinner. You want to stop your family members from tasting the stew while you're still preparing it. If they do, there might not be enough stew left for dinner. diff --git a/private-class-data/etc/private-class-data-flowchart.png b/private-class-data/etc/private-class-data-flowchart.png new file mode 100644 index 000000000000..2af3e1a6cb3a Binary files /dev/null and b/private-class-data/etc/private-class-data-flowchart.png differ diff --git a/private-class-data/etc/private-class-data-mind-map.png b/private-class-data/etc/private-class-data-mind-map.png new file mode 100644 index 000000000000..adfaa6abbc68 Binary files /dev/null and b/private-class-data/etc/private-class-data-mind-map.png differ diff --git a/private-class-data/pom.xml b/private-class-data/pom.xml index 57fc5eba913e..393832527dd6 100644 --- a/private-class-data/pom.xml +++ b/private-class-data/pom.xml @@ -34,6 +34,14 @@ private-class-data + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java index e13577911eb5..3a7f8c809a76 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/ImmutableStew.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Immutable stew class, protected with Private Class Data pattern. - */ +/** Immutable stew class, protected with Private Class Data pattern. */ @Slf4j public class ImmutableStew { @@ -38,12 +36,13 @@ public ImmutableStew(int numPotatoes, int numCarrots, int numMeat, int numPepper data = new StewData(numPotatoes, numCarrots, numMeat, numPeppers); } - /** - * Mix the stew. - */ + /** Mix the stew. */ public void mix() { - LOGGER - .info("Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers", - data.numPotatoes(), data.numCarrots(), data.numMeat(), data.numPeppers()); + LOGGER.info( + "Mixing the immutable stew we find: {} potatoes, {} carrots, {} meat and {} peppers", + data.numPotatoes(), + data.numCarrots(), + data.numMeat(), + data.numPeppers()); } } diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java index fe6122f9d547..7d58698d3427 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/Stew.java @@ -1,76 +1,72 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.privateclassdata; - -import lombok.extern.slf4j.Slf4j; - -/** - * Mutable stew class. - */ -@Slf4j -public class Stew { - - private int numPotatoes; - private int numCarrots; - private int numMeat; - private int numPeppers; - - /** - * Constructor. - */ - public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { - this.numPotatoes = numPotatoes; - this.numCarrots = numCarrots; - this.numMeat = numMeat; - this.numPeppers = numPeppers; - } - - /** - * Mix the stew. - */ - public void mix() { - LOGGER.info("Mixing the stew we find: {} potatoes, {} carrots, {} meat and {} peppers", - numPotatoes, numCarrots, numMeat, numPeppers); - } - - /** - * Taste the stew. - */ - public void taste() { - LOGGER.info("Tasting the stew"); - if (numPotatoes > 0) { - numPotatoes--; - } - if (numCarrots > 0) { - numCarrots--; - } - if (numMeat > 0) { - numMeat--; - } - if (numPeppers > 0) { - numPeppers--; - } - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.privateclassdata; + +import lombok.extern.slf4j.Slf4j; + +/** Mutable stew class. */ +@Slf4j +public class Stew { + + private int numPotatoes; + private int numCarrots; + private int numMeat; + private int numPeppers; + + /** Constructor. */ + public Stew(int numPotatoes, int numCarrots, int numMeat, int numPeppers) { + this.numPotatoes = numPotatoes; + this.numCarrots = numCarrots; + this.numMeat = numMeat; + this.numPeppers = numPeppers; + } + + /** Mix the stew. */ + public void mix() { + LOGGER.info( + "Mixing the stew we find: {} potatoes, {} carrots, {} meat and {} peppers", + numPotatoes, + numCarrots, + numMeat, + numPeppers); + } + + /** Taste the stew. */ + public void taste() { + LOGGER.info("Tasting the stew"); + if (numPotatoes > 0) { + numPotatoes--; + } + if (numCarrots > 0) { + numCarrots--; + } + if (numMeat > 0) { + numMeat--; + } + if (numPeppers > 0) { + numPeppers--; + } + } +} diff --git a/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java b/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java index 8812cf29d6c7..31b918afc1b9 100644 --- a/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java +++ b/private-class-data/src/main/java/com/iluwatar/privateclassdata/StewData.java @@ -24,8 +24,5 @@ */ package com.iluwatar.privateclassdata; -/** - * Stew ingredients. - */ - +/** Stew ingredients. */ public record StewData(int numPotatoes, int numCarrots, int numMeat, int numPeppers) {} diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java index 575b377196ad..4ec59c8aeda6 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.privateclassdata; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java index 6a68bb001435..9caf7dd1b3ed 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/ImmutableStewTest.java @@ -31,10 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * ImmutableStewTest - * - */ +/** ImmutableStewTest */ class ImmutableStewTest { private InMemoryAppender appender; @@ -49,9 +46,7 @@ void tearDown() { appender.stop(); } - /** - * Verify if mixing the stew doesn't change the internal state - */ + /** Verify if mixing the stew doesn't change the internal state */ @Test void testMix() { var stew = new Stew(1, 2, 3, 4); @@ -65,22 +60,22 @@ void testMix() { assertEquals(20, appender.getLogSize()); } - /** - * Verify if tasting the stew actually removes one of each ingredient - */ + /** Verify if tasting the stew actually removes one of each ingredient */ @Test void testDrink() { final var stew = new Stew(1, 2, 3, 4); stew.mix(); - assertEquals("Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers", appender - .getLastMessage()); + assertEquals( + "Mixing the stew we find: 1 potatoes, 2 carrots, 3 meat and 4 peppers", + appender.getLastMessage()); stew.taste(); assertEquals("Tasting the stew", appender.getLastMessage()); stew.mix(); - assertEquals("Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers", appender - .getLastMessage()); + assertEquals( + "Mixing the stew we find: 0 potatoes, 1 carrots, 2 meat and 3 peppers", + appender.getLastMessage()); } } diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java index 0dbe5ca85a7b..8715160b6488 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/StewTest.java @@ -31,10 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * StewTest - * - */ +/** StewTest */ class StewTest { private InMemoryAppender appender; @@ -49,14 +46,12 @@ void tearDown() { appender.stop(); } - /** - * Verify if mixing the stew doesn't change the internal state - */ + /** Verify if mixing the stew doesn't change the internal state */ @Test void testMix() { final var stew = new ImmutableStew(1, 2, 3, 4); - final var expectedMessage = "Mixing the immutable stew we find: 1 potatoes, " - + "2 carrots, 3 meat and 4 peppers"; + final var expectedMessage = + "Mixing the immutable stew we find: 1 potatoes, " + "2 carrots, 3 meat and 4 peppers"; for (var i = 0; i < 20; i++) { stew.mix(); @@ -65,5 +60,4 @@ void testMix() { assertEquals(20, appender.getLogSize()); } - } diff --git a/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java b/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java index 262a41995436..edb7853541ce 100644 --- a/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java +++ b/private-class-data/src/test/java/com/iluwatar/privateclassdata/utils/InMemoryAppender.java @@ -31,9 +31,7 @@ import java.util.List; import org.slf4j.LoggerFactory; -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/producer-consumer/README.md b/producer-consumer/README.md index a40ef5a654bd..575be804cd45 100644 --- a/producer-consumer/README.md +++ b/producer-consumer/README.md @@ -36,6 +36,10 @@ Wikipedia says > Dijkstra wrote about the case: "We consider two processes, which are called the 'producer' and the 'consumer' respectively. The producer is a cyclic process that produces a certain portion of information, that has to be processed by the consumer. The consumer is also a cyclic process that needs to process the next portion of information, as has been produced by the producer. We assume the two processes to be connected for this purpose via a buffer with unbounded capacity." +Sequence diagram + +![Producer-Consumer sequence diagram](./etc/producer-consumer-sequence-diagram.png) + ## Programmatic Example of Producer-Consumer Pattern in Java Consider a manufacturing process of item, the producer will need to pause the production when manufacturing pipeline is full and the consumer will need to pause the consumption of item when the manufacturing pipeline is empty. We can separate the process of production and consumption which work together and pause at separate times. @@ -177,10 +181,6 @@ Program output: 08:10:17.483 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [10] produced by [Producer_1] ``` -## Detailed Explanation of Producer-Consumer Pattern with Real-World Examples - -![Producer-Consumer](./etc/producer-consumer.png "Producer-Consumer") - ## When to Use the Producer-Consumer Pattern in Java * When you need to manage a buffer or queue where producers add data and consumers take data, often in a multithreaded environment. diff --git a/producer-consumer/etc/producer-consumer-sequence-diagram.png b/producer-consumer/etc/producer-consumer-sequence-diagram.png new file mode 100644 index 000000000000..7a97024daa69 Binary files /dev/null and b/producer-consumer/etc/producer-consumer-sequence-diagram.png differ diff --git a/producer-consumer/pom.xml b/producer-consumer/pom.xml index f82049cd621f..41eda2a36e98 100644 --- a/producer-consumer/pom.xml +++ b/producer-consumer/pom.xml @@ -34,6 +34,14 @@ producer-consumer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java index 81ed325baff7..22aad7002488 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/App.java @@ -54,20 +54,22 @@ public static void main(String[] args) { for (var i = 0; i < 2; i++) { final var producer = new Producer("Producer_" + i, queue); - executorService.submit(() -> { - while (true) { - producer.produce(); - } - }); + executorService.submit( + () -> { + while (true) { + producer.produce(); + } + }); } for (var i = 0; i < 3; i++) { final var consumer = new Consumer("Consumer_" + i, queue); - executorService.submit(() -> { - while (true) { - consumer.consume(); - } - }); + executorService.submit( + () -> { + while (true) { + consumer.consume(); + } + }); } executorService.shutdown(); diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java index 1d26f2b4a5fa..4574473dbab8 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Consumer.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Class responsible for consume the {@link Item} produced by {@link Producer}. - */ +/** Class responsible for consume the {@link Item} produced by {@link Producer}. */ @Slf4j public class Consumer { @@ -41,13 +39,10 @@ public Consumer(String name, ItemQueue queue) { this.queue = queue; } - /** - * Consume item from the queue. - */ + /** Consume item from the queue. */ public void consume() throws InterruptedException { var item = queue.take(); - LOGGER.info("Consumer [{}] consume item [{}] produced by [{}]", name, - item.id(), item.producer()); - + LOGGER.info( + "Consumer [{}] consume item [{}] produced by [{}]", name, item.id(), item.producer()); } } diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java index 01ca81b5abb4..b670086f6f20 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Item.java @@ -24,7 +24,5 @@ */ package com.iluwatar.producer.consumer; -/** - * Class take part of an {@link Producer}-{@link Consumer} exchange. - */ +/** Class take part of an {@link Producer}-{@link Consumer} exchange. */ public record Item(String producer, int id) {} diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java index 7fd3debad9fc..cdfff3f996f4 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/ItemQueue.java @@ -27,9 +27,7 @@ import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -/** - * Class as a channel for {@link Producer}-{@link Consumer} exchange. - */ +/** Class as a channel for {@link Producer}-{@link Consumer} exchange. */ public class ItemQueue { private final BlockingQueue queue; @@ -48,5 +46,4 @@ public Item take() throws InterruptedException { return queue.take(); } - } diff --git a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java index b0d4bd3845ef..a28a9e78ddca 100644 --- a/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java +++ b/producer-consumer/src/main/java/com/iluwatar/producer/consumer/Producer.java @@ -45,9 +45,7 @@ public Producer(String name, ItemQueue queue) { this.queue = queue; } - /** - * Put item in the queue. - */ + /** Put item in the queue. */ public void produce() throws InterruptedException { var item = new Item(name, itemId++); diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java index 5dbf70ade854..aeb12f59b71b 100644 --- a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/AppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.producer.consumer; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java index 004faed52d94..2f65d8e7540f 100644 --- a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ConsumerTest.java @@ -31,10 +31,7 @@ import org.junit.jupiter.api.Test; -/** - * ConsumerTest - * - */ +/** ConsumerTest */ class ConsumerTest { private static final int ITEM_COUNT = 5; @@ -55,5 +52,4 @@ void testConsume() throws Exception { verify(queue, times(ITEM_COUNT)).take(); } - } diff --git a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java index af95a1d36034..3aef8bfe4991 100644 --- a/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java +++ b/producer-consumer/src/test/java/com/iluwatar/producer/consumer/ProducerTest.java @@ -33,23 +33,21 @@ import org.junit.jupiter.api.Test; -/** - * ProducerTest - * - */ +/** ProducerTest */ class ProducerTest { @Test void testProduce() { - assertTimeout(ofMillis(6000), () -> { - final var queue = mock(ItemQueue.class); - final var producer = new Producer("producer", queue); + assertTimeout( + ofMillis(6000), + () -> { + final var queue = mock(ItemQueue.class); + final var producer = new Producer("producer", queue); - producer.produce(); - verify(queue).put(any(Item.class)); + producer.produce(); + verify(queue).put(any(Item.class)); - verifyNoMoreInteractions(queue); - }); + verifyNoMoreInteractions(queue); + }); } - -} \ No newline at end of file +} diff --git a/promise/README.md b/promise/README.md index 76b67e2fb58e..9c919862a4dd 100644 --- a/promise/README.md +++ b/promise/README.md @@ -37,6 +37,10 @@ Wikipedia says > In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is not yet complete. +Sequence diagram + +![Promise sequence diagram](./etc/promise-sequence-diagram.png) + ## Programmatic Example of Promise Pattern in Java The Promise design pattern is a software design pattern that's often used in concurrent programming to handle asynchronous operations. It represents a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. diff --git a/promise/etc/promise-sequence-diagram.png b/promise/etc/promise-sequence-diagram.png new file mode 100644 index 000000000000..9209fea11441 Binary files /dev/null and b/promise/etc/promise-sequence-diagram.png differ diff --git a/promise/pom.xml b/promise/pom.xml index 7a104ee3bf14..b43d3f57a950 100644 --- a/promise/pom.xml +++ b/promise/pom.xml @@ -34,6 +34,14 @@ promise + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/promise/src/main/java/com/iluwatar/promise/App.java b/promise/src/main/java/com/iluwatar/promise/App.java index 1f66ae0da168..7bfed9dc9aab 100644 --- a/promise/src/main/java/com/iluwatar/promise/App.java +++ b/promise/src/main/java/com/iluwatar/promise/App.java @@ -27,7 +27,6 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import lombok.extern.slf4j.Slf4j; @@ -36,26 +35,28 @@ * The Promise object is used for asynchronous computations. A Promise represents an operation that * hasn't completed yet, but is expected in the future. * - *

    A Promise represents a proxy for a value not necessarily known when the promise is created. - * It allows you to associate dependent promises to an asynchronous action's eventual success value - * or failure reason. This lets asynchronous methods return values like synchronous methods: instead - * of the final value, the asynchronous method returns a promise of having a value at some point in - * the future. + *

    A Promise represents a proxy for a value not necessarily known when the promise is created. It + * allows you to associate dependent promises to an asynchronous action's eventual success value or + * failure reason. This lets asynchronous methods return values like synchronous methods: instead of + * the final value, the asynchronous method returns a promise of having a value at some point in the + * future. * *

    Promises provide a few advantages over callback objects: + * *

      - *
    • Functional composition and error handling - *
    • Prevents callback hell and provides callback aggregation + *
    • Functional composition and error handling + *
    • Prevents callback hell and provides callback aggregation *
    * *

    In this application the usage of promise is demonstrated with two examples: + * *

      - *
    • Count Lines: In this example a file is downloaded and its line count is calculated. - * The calculated line count is then consumed and printed on console. - *
    • Lowest Character Frequency: In this example a file is downloaded and its lowest frequency - * character is found and printed on console. This happens via a chain of promises, we start with - * a file download promise, then a promise of character frequency, then a promise of lowest - * frequency character which is finally consumed and result is printed on console. + *
    • Count Lines: In this example a file is downloaded and its line count is calculated. The + * calculated line count is then consumed and printed on console. + *
    • Lowest Character Frequency: In this example a file is downloaded and its lowest frequency + * character is found and printed on console. This happens via a chain of promises, we start + * with a file download promise, then a promise of character frequency, then a promise of + * lowest frequency character which is finally consumed and result is printed on console. *
    * * @see CompletableFuture @@ -99,12 +100,12 @@ private void promiseUsage() { * consume the result in a Consumer */ private void calculateLowestFrequencyChar() { - lowestFrequencyChar().thenAccept( - charFrequency -> { - LOGGER.info("Char with lowest frequency is: {}", charFrequency); - taskCompleted(); - } - ); + lowestFrequencyChar() + .thenAccept( + charFrequency -> { + LOGGER.info("Char with lowest frequency is: {}", charFrequency); + taskCompleted(); + }); } /* @@ -112,12 +113,12 @@ private void calculateLowestFrequencyChar() { * in a Consumer */ private void calculateLineCount() { - countLines().thenAccept( - count -> { - LOGGER.info("Line count is: {}", count); - taskCompleted(); - } - ); + countLines() + .thenAccept( + count -> { + LOGGER.info("Line count is: {}", count); + taskCompleted(); + }); } /* @@ -150,14 +151,12 @@ private Promise countLines() { */ private Promise download(String urlString) { return new Promise() - .fulfillInAsync( - () -> Utility.downloadFile(urlString), executor) + .fulfillInAsync(() -> Utility.downloadFile(urlString), executor) .onError( throwable -> { LOGGER.error("An error occurred: ", throwable); taskCompleted(); - } - ); + }); } private void stop() throws InterruptedException { diff --git a/promise/src/main/java/com/iluwatar/promise/Promise.java b/promise/src/main/java/com/iluwatar/promise/Promise.java index 5958147833f5..546e82a9bffd 100644 --- a/promise/src/main/java/com/iluwatar/promise/Promise.java +++ b/promise/src/main/java/com/iluwatar/promise/Promise.java @@ -44,9 +44,7 @@ public class Promise extends PromiseSupport { private Runnable fulfillmentAction; private Consumer exceptionHandler; - /** - * Creates a promise that will be fulfilled in the future. - */ + /** Creates a promise that will be fulfilled in the future. */ public Promise() { // Empty constructor } @@ -66,7 +64,7 @@ public void fulfill(T value) { * Fulfills the promise with exception due to error in execution. * * @param exception the exception will be wrapped in {@link ExecutionException} when accessing the - * value using {@link #get()}. + * value using {@link #get()}. */ @Override public void fulfillExceptionally(Exception exception) { @@ -93,18 +91,19 @@ private void postFulfillment() { * Executes the task using the executor in other thread and fulfills the promise returned once the * task completes either successfully or with an exception. * - * @param task the task that will provide the value to fulfill the promise. + * @param task the task that will provide the value to fulfill the promise. * @param executor the executor in which the task should be run. * @return a promise that represents the result of running the task provided. */ public Promise fulfillInAsync(final Callable task, Executor executor) { - executor.execute(() -> { - try { - fulfill(task.call()); - } catch (Exception ex) { - fulfillExceptionally(ex); - } - }); + executor.execute( + () -> { + try { + fulfill(task.call()); + } catch (Exception ex) { + fulfillExceptionally(ex); + } + }); return this; } @@ -125,7 +124,7 @@ public Promise thenAccept(Consumer action) { * Set the exception handler on this promise. * * @param exceptionHandler a consumer that will handle the exception occurred while fulfilling the - * promise. + * promise. * @return this */ public Promise onError(Consumer exceptionHandler) { @@ -198,4 +197,4 @@ public void run() { } } } -} \ No newline at end of file +} diff --git a/promise/src/main/java/com/iluwatar/promise/Utility.java b/promise/src/main/java/com/iluwatar/promise/Utility.java index e3a2e0ad8d02..903dcfe8e11b 100644 --- a/promise/src/main/java/com/iluwatar/promise/Utility.java +++ b/promise/src/main/java/com/iluwatar/promise/Utility.java @@ -39,9 +39,7 @@ import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; -/** - * Utility to perform various operations. - */ +/** Utility to perform various operations. */ @Slf4j public class Utility { @@ -53,7 +51,8 @@ public class Utility { */ public static Map characterFrequency(String fileLocation) { try (var bufferedReader = new BufferedReader(new FileReader(fileLocation))) { - return bufferedReader.lines() + return bufferedReader + .lines() .flatMapToInt(String::chars) .mapToObj(x -> (char) x) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); @@ -69,9 +68,7 @@ public static Map characterFrequency(String fileLocation) { * @return the character, {@code Optional.empty()} otherwise. */ public static Character lowestFrequencyChar(Map charFrequency) { - return charFrequency - .entrySet() - .stream() + return charFrequency.entrySet().stream() .min(Comparator.comparingLong(Entry::getValue)) .map(Entry::getKey) .orElseThrow(); @@ -101,7 +98,7 @@ public static String downloadFile(String urlString) throws IOException { var url = new URL(urlString); var file = File.createTempFile("promise_pattern", null); try (var bufferedReader = new BufferedReader(new InputStreamReader(url.openStream())); - var writer = new FileWriter(file)) { + var writer = new FileWriter(file)) { String line; while ((line = bufferedReader.readLine()) != null) { writer.write(line); diff --git a/promise/src/test/java/com/iluwatar/promise/AppTest.java b/promise/src/test/java/com/iluwatar/promise/AppTest.java index 502968466d1d..ecc2ad88d036 100644 --- a/promise/src/test/java/com/iluwatar/promise/AppTest.java +++ b/promise/src/test/java/com/iluwatar/promise/AppTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Application test. - */ +/** Application test. */ class AppTest { @Test diff --git a/promise/src/test/java/com/iluwatar/promise/PromiseTest.java b/promise/src/test/java/com/iluwatar/promise/PromiseTest.java index 99bf9aeed9e2..13c01eafd1f1 100644 --- a/promise/src/test/java/com/iluwatar/promise/PromiseTest.java +++ b/promise/src/test/java/com/iluwatar/promise/PromiseTest.java @@ -41,9 +41,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests Promise class. - */ +/** Tests Promise class. */ class PromiseTest { private Executor executor; @@ -66,17 +64,18 @@ void promiseIsFulfilledWithTheResultantValueOfExecutingTheTask() } @Test - void promiseIsFulfilledWithAnExceptionIfTaskThrowsAnException() - throws InterruptedException { + void promiseIsFulfilledWithAnExceptionIfTaskThrowsAnException() throws InterruptedException { testWaitingForeverForPromiseToBeFulfilled(); testWaitingSomeTimeForPromiseToBeFulfilled(); } private void testWaitingForeverForPromiseToBeFulfilled() throws InterruptedException { var promise = new Promise(); - promise.fulfillInAsync(() -> { - throw new RuntimeException("Barf!"); - }, executor); + promise.fulfillInAsync( + () -> { + throw new RuntimeException("Barf!"); + }, + executor); try { promise.get(); @@ -97,9 +96,11 @@ private void testWaitingForeverForPromiseToBeFulfilled() throws InterruptedExcep private void testWaitingSomeTimeForPromiseToBeFulfilled() throws InterruptedException { var promise = new Promise(); - promise.fulfillInAsync(() -> { - throw new RuntimeException("Barf!"); - }, executor); + promise.fulfillInAsync( + () -> { + throw new RuntimeException("Barf!"); + }, + executor); try { promise.get(1000, TimeUnit.SECONDS); @@ -116,15 +117,15 @@ private void testWaitingSomeTimeForPromiseToBeFulfilled() throws InterruptedExce assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); } - } @Test void dependentPromiseIsFulfilledAfterTheConsumerConsumesTheResultOfThisPromise() throws InterruptedException, ExecutionException { - var dependentPromise = promise - .fulfillInAsync(new NumberCrunchingTask(), executor) - .thenAccept(value -> assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value)); + var dependentPromise = + promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenAccept(value -> assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value)); dependentPromise.get(); assertTrue(dependentPromise.isDone()); @@ -134,16 +135,19 @@ void dependentPromiseIsFulfilledAfterTheConsumerConsumesTheResultOfThisPromise() @Test void dependentPromiseIsFulfilledWithAnExceptionIfConsumerThrowsAnException() throws InterruptedException { - var dependentPromise = promise - .fulfillInAsync(new NumberCrunchingTask(), executor) - .thenAccept(value -> { - throw new RuntimeException("Barf!"); - }); + var dependentPromise = + promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenAccept( + value -> { + throw new RuntimeException("Barf!"); + }); try { dependentPromise.get(); - fail("Fetching dependent promise should result in exception " - + "if the action threw an exception"); + fail( + "Fetching dependent promise should result in exception " + + "if the action threw an exception"); } catch (ExecutionException ex) { assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); @@ -151,8 +155,9 @@ void dependentPromiseIsFulfilledWithAnExceptionIfConsumerThrowsAnException() try { dependentPromise.get(1000, TimeUnit.SECONDS); - fail("Fetching dependent promise should result in exception " - + "if the action threw an exception"); + fail( + "Fetching dependent promise should result in exception " + + "if the action threw an exception"); } catch (ExecutionException ex) { assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); @@ -162,13 +167,14 @@ void dependentPromiseIsFulfilledWithAnExceptionIfConsumerThrowsAnException() @Test void dependentPromiseIsFulfilledAfterTheFunctionTransformsTheResultOfThisPromise() throws InterruptedException, ExecutionException { - var dependentPromise = promise - .fulfillInAsync(new NumberCrunchingTask(), executor) - .thenApply(value -> { - assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value); - return String.valueOf(value); - }); - + var dependentPromise = + promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenApply( + value -> { + assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value); + return String.valueOf(value); + }); assertEquals(String.valueOf(NumberCrunchingTask.CRUNCHED_NUMBER), dependentPromise.get()); assertTrue(dependentPromise.isDone()); @@ -178,16 +184,19 @@ void dependentPromiseIsFulfilledAfterTheFunctionTransformsTheResultOfThisPromise @Test void dependentPromiseIsFulfilledWithAnExceptionIfTheFunctionThrowsException() throws InterruptedException { - var dependentPromise = promise - .fulfillInAsync(new NumberCrunchingTask(), executor) - .thenApply(value -> { - throw new RuntimeException("Barf!"); - }); + var dependentPromise = + promise + .fulfillInAsync(new NumberCrunchingTask(), executor) + .thenApply( + value -> { + throw new RuntimeException("Barf!"); + }); try { dependentPromise.get(); - fail("Fetching dependent promise should result in exception " - + "if the function threw an exception"); + fail( + "Fetching dependent promise should result in exception " + + "if the function threw an exception"); } catch (ExecutionException ex) { assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); @@ -195,8 +204,9 @@ void dependentPromiseIsFulfilledWithAnExceptionIfTheFunctionThrowsException() try { dependentPromise.get(1000, TimeUnit.SECONDS); - fail("Fetching dependent promise should result in exception " - + "if the function threw an exception"); + fail( + "Fetching dependent promise should result in exception " + + "if the function threw an exception"); } catch (ExecutionException ex) { assertTrue(promise.isDone()); assertFalse(promise.isCancelled()); diff --git a/property/README.md b/property/README.md index 0bfd89183531..5eb1ddef44c0 100644 --- a/property/README.md +++ b/property/README.md @@ -31,6 +31,10 @@ In plain words > Define and manage a dynamic set of properties for an object, allowing customization without altering its structure. +Sequence diagram + +![Property sequence diagram](./etc/property-sequence-diagram.png) + ## Programmatic Example of Property Pattern in Java The Property design pattern, also known as Prototype inheritance, is a pattern that allows objects to be created from other objects, forming object hierarchies. This pattern is particularly useful when you want to create a new object that is a slight variation of an existing object. diff --git a/property/etc/property-sequence-diagram.png b/property/etc/property-sequence-diagram.png new file mode 100644 index 000000000000..00c9ee3b762d Binary files /dev/null and b/property/etc/property-sequence-diagram.png differ diff --git a/property/pom.xml b/property/pom.xml index 17d8e35a0bf6..a2d894e2bb04 100644 --- a/property/pom.xml +++ b/property/pom.xml @@ -34,6 +34,14 @@ property + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/property/src/main/java/com/iluwatar/property/Character.java b/property/src/main/java/com/iluwatar/property/Character.java index 83506abe28a8..0738260f070b 100644 --- a/property/src/main/java/com/iluwatar/property/Character.java +++ b/property/src/main/java/com/iluwatar/property/Character.java @@ -27,16 +27,14 @@ import java.util.HashMap; import java.util.Map; -/** - * Represents Character in game and his abilities (base stats). - */ +/** Represents Character in game and his abilities (base stats). */ public class Character implements Prototype { - /** - * Enumeration of Character types. - */ + /** Enumeration of Character types. */ public enum Type { - WARRIOR, MAGE, ROGUE + WARRIOR, + MAGE, + ROGUE } private final Prototype prototype; @@ -45,31 +43,30 @@ public enum Type { private String name; private Type type; - /** - * Constructor. - */ + /** Constructor. */ public Character() { - this.prototype = new Prototype() { // Null-value object - @Override - public Integer get(Stats stat) { - return null; - } - - @Override - public boolean has(Stats stat) { - return false; - } - - @Override - public void set(Stats stat, Integer val) { - // Does Nothing - } - - @Override - public void remove(Stats stat) { - // Does Nothing. - } - }; + this.prototype = + new Prototype() { // Null-value object + @Override + public Integer get(Stats stat) { + return null; + } + + @Override + public boolean has(Stats stat) { + return false; + } + + @Override + public void set(Stats stat, Integer val) { + // Does Nothing + } + + @Override + public void remove(Stats stat) { + // Does Nothing. + } + }; } public Character(Type type, Prototype prototype) { @@ -77,9 +74,7 @@ public Character(Type type, Prototype prototype) { this.prototype = prototype; } - /** - * Constructor. - */ + /** Constructor. */ public Character(String name, Character prototype) { this.name = name; this.type = prototype.type; @@ -140,5 +135,4 @@ public String toString() { } return builder.toString(); } - } diff --git a/property/src/main/java/com/iluwatar/property/Prototype.java b/property/src/main/java/com/iluwatar/property/Prototype.java index 9c92095640c6..a353f8a57794 100644 --- a/property/src/main/java/com/iluwatar/property/Prototype.java +++ b/property/src/main/java/com/iluwatar/property/Prototype.java @@ -24,9 +24,7 @@ */ package com.iluwatar.property; -/** - * Interface for prototype inheritance. - */ +/** Interface for prototype inheritance. */ public interface Prototype { Integer get(Stats stat); diff --git a/property/src/main/java/com/iluwatar/property/Stats.java b/property/src/main/java/com/iluwatar/property/Stats.java index 2605f3e4fe58..ab83b80ce23f 100644 --- a/property/src/main/java/com/iluwatar/property/Stats.java +++ b/property/src/main/java/com/iluwatar/property/Stats.java @@ -24,10 +24,14 @@ */ package com.iluwatar.property; -/** - * All possible attributes that Character can have. - */ +/** All possible attributes that Character can have. */ public enum Stats { - - AGILITY, STRENGTH, ATTACK_POWER, ARMOR, INTELLECT, SPIRIT, ENERGY, RAGE + AGILITY, + STRENGTH, + ATTACK_POWER, + ARMOR, + INTELLECT, + SPIRIT, + ENERGY, + RAGE } diff --git a/property/src/test/java/com/iluwatar/property/AppTest.java b/property/src/test/java/com/iluwatar/property/AppTest.java index 761f092089c6..86dd774a4f77 100644 --- a/property/src/test/java/com/iluwatar/property/AppTest.java +++ b/property/src/test/java/com/iluwatar/property/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.property; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/property/src/test/java/com/iluwatar/property/CharacterTest.java b/property/src/test/java/com/iluwatar/property/CharacterTest.java index 6db74bbe9c53..0d3147b11bf9 100644 --- a/property/src/test/java/com/iluwatar/property/CharacterTest.java +++ b/property/src/test/java/com/iluwatar/property/CharacterTest.java @@ -33,10 +33,7 @@ import java.util.Arrays; import org.junit.jupiter.api.Test; -/** - * CharacterTest - * - */ +/** CharacterTest */ class CharacterTest { @Test @@ -56,7 +53,6 @@ void testPrototypeStats() { assertFalse(prototype.has(stat)); assertNull(prototype.get(stat)); } - } @Test @@ -78,7 +74,8 @@ void testToString() { prototype.set(Stats.ARMOR, 1); prototype.set(Stats.AGILITY, 2); prototype.set(Stats.INTELLECT, 3); - var message = """ + var message = + """ Stats: - AGILITY:2 - ARMOR:1 @@ -88,7 +85,8 @@ void testToString() { final var stupid = new Character(Type.ROGUE, prototype); stupid.remove(Stats.INTELLECT); - String expectedStupidString = """ + String expectedStupidString = + """ Character type: ROGUE Stats: - AGILITY:2 @@ -98,14 +96,14 @@ void testToString() { final var weak = new Character("weak", prototype); weak.remove(Stats.ARMOR); - String expectedWeakString = """ + String expectedWeakString = + """ Player: weak Stats: - AGILITY:2 - INTELLECT:3 """; assertEquals(expectedWeakString, weak.toString()); - } @Test @@ -139,5 +137,4 @@ void testType() { weak.remove(Stats.ARMOR); assertNull(weak.type()); } - -} \ No newline at end of file +} diff --git a/prototype/README.md b/prototype/README.md index c68c5fbc001f..b97df0707ffc 100644 --- a/prototype/README.md +++ b/prototype/README.md @@ -5,7 +5,7 @@ description: "Explore the Prototype design pattern in Java with a comprehensive category: Creational language: en tag: - - Gang Of Four + - Gang of Four - Instantiation - Object composition - Polymorphism @@ -35,6 +35,10 @@ Wikipedia says > The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects. +Sequence diagram + +![Prototype sequence diagram](./etc/prototype-sequence-diagram.png) + ## Programmatic Example of Prototype Pattern in Java In Java, the prototype pattern is recommended to be implemented as follows. First, create an interface with a method for cloning objects. In this example, `Prototype` interface accomplishes this with its `copy` method. @@ -152,10 +156,6 @@ Here's the console output from running the example. 08:36:19.014 [main] INFO com.iluwatar.prototype.App -- Orcish wolf attacks with laser ``` -## Detailed Explanation of Prototype Pattern with Real-World Examples - -![alt text](./etc/prototype.urm.png "Prototype pattern class diagram") - ## When to Use the Prototype Pattern in Java * When the classes to instantiate are specified at run-time, for example, by dynamic loading. diff --git a/prototype/etc/prototype-sequence-diagram.png b/prototype/etc/prototype-sequence-diagram.png new file mode 100644 index 000000000000..fbab1beeeceb Binary files /dev/null and b/prototype/etc/prototype-sequence-diagram.png differ diff --git a/prototype/pom.xml b/prototype/pom.xml index 6ab8640f3226..4e6a44b18733 100644 --- a/prototype/pom.xml +++ b/prototype/pom.xml @@ -34,6 +34,14 @@ prototype + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/prototype/src/main/java/com/iluwatar/prototype/App.java b/prototype/src/main/java/com/iluwatar/prototype/App.java index f29747353440..fdcad907720c 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/App.java +++ b/prototype/src/main/java/com/iluwatar/prototype/App.java @@ -33,8 +33,8 @@ * application, like the abstract factory pattern, does. - avoid the inherent cost of creating a new * object in the standard way (e.g., using the 'new' keyword) * - *

    In this example we have a factory class ({@link HeroFactoryImpl}) producing objects by - * cloning the existing ones. The factory's prototype objects are given as constructor parameters. + *

    In this example we have a factory class ({@link HeroFactoryImpl}) producing objects by cloning + * the existing ones. The factory's prototype objects are given as constructor parameters. */ @Slf4j public class App { @@ -45,11 +45,9 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var factory = new HeroFactoryImpl( - new ElfMage("cooking"), - new ElfWarlord("cleaning"), - new ElfBeast("protecting") - ); + var factory = + new HeroFactoryImpl( + new ElfMage("cooking"), new ElfWarlord("cleaning"), new ElfBeast("protecting")); var mage = factory.createMage(); var warlord = factory.createWarlord(); var beast = factory.createBeast(); @@ -57,11 +55,8 @@ public static void main(String[] args) { LOGGER.info(warlord.toString()); LOGGER.info(beast.toString()); - factory = new HeroFactoryImpl( - new OrcMage("axe"), - new OrcWarlord("sword"), - new OrcBeast("laser") - ); + factory = + new HeroFactoryImpl(new OrcMage("axe"), new OrcWarlord("sword"), new OrcBeast("laser")); mage = factory.createMage(); warlord = factory.createWarlord(); beast = factory.createBeast(); diff --git a/prototype/src/main/java/com/iluwatar/prototype/Beast.java b/prototype/src/main/java/com/iluwatar/prototype/Beast.java index 1888133a765d..027ad2b18ad4 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/Beast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/Beast.java @@ -27,14 +27,10 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -/** - * Beast. - */ +/** Beast. */ @EqualsAndHashCode(callSuper = false) @NoArgsConstructor public abstract class Beast extends Prototype { - public Beast(Beast source) { - } - + public Beast(Beast source) {} } diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java index 0dbfd789fc5b..0fa9822bee36 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfBeast.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * ElfBeast. - */ +/** ElfBeast. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class ElfBeast extends Beast { @@ -45,5 +43,4 @@ public ElfBeast(ElfBeast elfBeast) { public String toString() { return "Elven eagle helps in " + helpType; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java index f1cd27c841eb..5b00275e46a4 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfMage.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * ElfMage. - */ +/** ElfMage. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class ElfMage extends Mage { @@ -45,5 +43,4 @@ public ElfMage(ElfMage elfMage) { public String toString() { return "Elven mage helps in " + helpType; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java index a876cdcb3b55..6a53e7309deb 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/ElfWarlord.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * ElfWarlord. - */ +/** ElfWarlord. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class ElfWarlord extends Warlord { diff --git a/prototype/src/main/java/com/iluwatar/prototype/HeroFactory.java b/prototype/src/main/java/com/iluwatar/prototype/HeroFactory.java index 91aa37229b2f..8295e0bdb8c1 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/HeroFactory.java +++ b/prototype/src/main/java/com/iluwatar/prototype/HeroFactory.java @@ -1,38 +1,35 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.prototype; - -/** - * Interface for the factory class. - */ -public interface HeroFactory { - - Mage createMage(); - - Warlord createWarlord(); - - Beast createBeast(); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.prototype; + +/** Interface for the factory class. */ +public interface HeroFactory { + + Mage createMage(); + + Warlord createWarlord(); + + Beast createBeast(); +} diff --git a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java index 21c20a5fd146..c959aa89d1ad 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java +++ b/prototype/src/main/java/com/iluwatar/prototype/HeroFactoryImpl.java @@ -26,9 +26,7 @@ import lombok.RequiredArgsConstructor; -/** - * Concrete factory class. - */ +/** Concrete factory class. */ @RequiredArgsConstructor public class HeroFactoryImpl implements HeroFactory { @@ -36,25 +34,18 @@ public class HeroFactoryImpl implements HeroFactory { private final Warlord warlord; private final Beast beast; - /** - * Create mage. - */ + /** Create mage. */ public Mage createMage() { return mage.copy(); } - /** - * Create warlord. - */ + /** Create warlord. */ public Warlord createWarlord() { return warlord.copy(); } - /** - * Create beast. - */ + /** Create beast. */ public Beast createBeast() { return beast.copy(); } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/Mage.java b/prototype/src/main/java/com/iluwatar/prototype/Mage.java index 8f90e53f00da..70a7770515f1 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/Mage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/Mage.java @@ -27,14 +27,10 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -/** - * Mage. - */ +/** Mage. */ @EqualsAndHashCode(callSuper = false) @NoArgsConstructor public abstract class Mage extends Prototype { - public Mage(Mage source) { - } - + public Mage(Mage source) {} } diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java index 4cd24ed7e8fe..0ab3c3b9cfc6 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcBeast.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * OrcBeast. - */ +/** OrcBeast. */ @EqualsAndHashCode(callSuper = false) @RequiredArgsConstructor public class OrcBeast extends Beast { @@ -45,5 +43,4 @@ public OrcBeast(OrcBeast orcBeast) { public String toString() { return "Orcish wolf attacks with " + weapon; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java index 7a4aa0a4db31..33c0cac9f42a 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcMage.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * OrcMage. - */ +/** OrcMage. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class OrcMage extends Mage { @@ -45,5 +43,4 @@ public OrcMage(OrcMage orcMage) { public String toString() { return "Orcish mage attacks with " + weapon; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java index 5ee7da40ba39..54964bd333f7 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/OrcWarlord.java @@ -27,9 +27,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; -/** - * OrcWarlord. - */ +/** OrcWarlord. */ @EqualsAndHashCode(callSuper = true) @RequiredArgsConstructor public class OrcWarlord extends Warlord { @@ -45,5 +43,4 @@ public OrcWarlord(OrcWarlord orcWarlord) { public String toString() { return "Orcish warlord attacks with " + weapon; } - } diff --git a/prototype/src/main/java/com/iluwatar/prototype/Prototype.java b/prototype/src/main/java/com/iluwatar/prototype/Prototype.java index b5cd10188066..ead3d4e860eb 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/Prototype.java +++ b/prototype/src/main/java/com/iluwatar/prototype/Prototype.java @@ -27,15 +27,11 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -/** - * Prototype. - */ +/** Prototype. */ @Slf4j public abstract class Prototype implements Cloneable { - /** - * Object a shallow copy of this object or null if this object is not Cloneable. - */ + /** Object a shallow copy of this object or null if this object is not Cloneable. */ @SuppressWarnings("unchecked") @SneakyThrows public T copy() { diff --git a/prototype/src/main/java/com/iluwatar/prototype/Warlord.java b/prototype/src/main/java/com/iluwatar/prototype/Warlord.java index f407673d133a..a1cf5562b22f 100644 --- a/prototype/src/main/java/com/iluwatar/prototype/Warlord.java +++ b/prototype/src/main/java/com/iluwatar/prototype/Warlord.java @@ -27,14 +27,10 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -/** - * Warlord. - */ +/** Warlord. */ @EqualsAndHashCode(callSuper = false) @NoArgsConstructor public abstract class Warlord extends Prototype { - public Warlord(Warlord source) { - } - + public Warlord(Warlord source) {} } diff --git a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java index c9c55e0c6776..2407c1c8d8b3 100644 --- a/prototype/src/test/java/com/iluwatar/prototype/AppTest.java +++ b/prototype/src/test/java/com/iluwatar/prototype/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java b/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java index e20d6e0d52de..b8cdb73b8a43 100644 --- a/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java +++ b/prototype/src/test/java/com/iluwatar/prototype/PrototypeTest.java @@ -42,13 +42,12 @@ class PrototypeTest

    > { static Collection dataProvider() { return List.of( - new Object[]{new OrcBeast("axe"), "Orcish wolf attacks with axe"}, - new Object[]{new OrcMage("sword"), "Orcish mage attacks with sword"}, - new Object[]{new OrcWarlord("laser"), "Orcish warlord attacks with laser"}, - new Object[]{new ElfBeast("cooking"), "Elven eagle helps in cooking"}, - new Object[]{new ElfMage("cleaning"), "Elven mage helps in cleaning"}, - new Object[]{new ElfWarlord("protecting"), "Elven warlord helps in protecting"} - ); + new Object[] {new OrcBeast("axe"), "Orcish wolf attacks with axe"}, + new Object[] {new OrcMage("sword"), "Orcish mage attacks with sword"}, + new Object[] {new OrcWarlord("laser"), "Orcish warlord attacks with laser"}, + new Object[] {new ElfBeast("cooking"), "Elven eagle helps in cooking"}, + new Object[] {new ElfMage("cleaning"), "Elven mage helps in cleaning"}, + new Object[] {new ElfWarlord("protecting"), "Elven warlord helps in protecting"}); } @ParameterizedTest @@ -62,5 +61,4 @@ void testPrototype(P testedPrototype, String expectedToString) { assertSame(testedPrototype.getClass(), clone.getClass()); assertEquals(clone, testedPrototype); } - } diff --git a/proxy/README.md b/proxy/README.md index f237fb47e9e5..5e5109348815 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -7,7 +7,7 @@ language: en tag: - Decoupling - Encapsulation - - Gang Of Four + - Gang of Four - Lazy initialization - Proxy - Security @@ -36,6 +36,10 @@ Wikipedia says > A proxy, in its most general form, is a class functioning as an interface to something else. A proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked. +Sequence diagram + +![Proxy sequence diagram](./etc/proxy-sequence-diagram.png) + ## Programmatic Example of Proxy Pattern in Java Imagine a tower where the local wizards go to study their spells. The ivory tower can only be accessed through a proxy which ensures that only the first three wizards can enter. Here the proxy represents the functionality of the tower and adds access control to it. diff --git a/proxy/etc/proxy-sequence-diagram.png b/proxy/etc/proxy-sequence-diagram.png new file mode 100644 index 000000000000..938dffc28c13 Binary files /dev/null and b/proxy/etc/proxy-sequence-diagram.png differ diff --git a/proxy/pom.xml b/proxy/pom.xml index f700c2fbb4ee..79a9f2c58e72 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -34,6 +34,14 @@ proxy + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/proxy/src/main/java/com/iluwatar/proxy/App.java b/proxy/src/main/java/com/iluwatar/proxy/App.java index dfd8550c6584..9b4cd6b5a497 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/App.java +++ b/proxy/src/main/java/com/iluwatar/proxy/App.java @@ -40,9 +40,7 @@ */ public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var proxy = new WizardTowerProxy(new IvoryTower()); @@ -51,6 +49,5 @@ public static void main(String[] args) { proxy.enter(new Wizard("Black wizard")); proxy.enter(new Wizard("Green wizard")); proxy.enter(new Wizard("Brown wizard")); - } } diff --git a/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java b/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java index 64f69f1e6659..2f815f511ecf 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java +++ b/proxy/src/main/java/com/iluwatar/proxy/IvoryTower.java @@ -26,14 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * The object to be proxied. - */ +/** The object to be proxied. */ @Slf4j public class IvoryTower implements WizardTower { public void enter(Wizard wizard) { LOGGER.info("{} enters the tower.", wizard); } - } diff --git a/proxy/src/main/java/com/iluwatar/proxy/Wizard.java b/proxy/src/main/java/com/iluwatar/proxy/Wizard.java index e37d33805e1f..ebc3307cbd8a 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/Wizard.java +++ b/proxy/src/main/java/com/iluwatar/proxy/Wizard.java @@ -26,9 +26,7 @@ import lombok.RequiredArgsConstructor; -/** - * Wizard. - */ +/** Wizard. */ @RequiredArgsConstructor public class Wizard { @@ -38,5 +36,4 @@ public class Wizard { public String toString() { return name; } - } diff --git a/proxy/src/main/java/com/iluwatar/proxy/WizardTower.java b/proxy/src/main/java/com/iluwatar/proxy/WizardTower.java index 8dcbdb2d372c..5e89539d0333 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/WizardTower.java +++ b/proxy/src/main/java/com/iluwatar/proxy/WizardTower.java @@ -24,9 +24,7 @@ */ package com.iluwatar.proxy; -/** - * WizardTower interface. - */ +/** WizardTower interface. */ public interface WizardTower { void enter(Wizard wizard); diff --git a/proxy/src/main/java/com/iluwatar/proxy/WizardTowerProxy.java b/proxy/src/main/java/com/iluwatar/proxy/WizardTowerProxy.java index 9f27fad34d29..11f4effd58f0 100644 --- a/proxy/src/main/java/com/iluwatar/proxy/WizardTowerProxy.java +++ b/proxy/src/main/java/com/iluwatar/proxy/WizardTowerProxy.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * The proxy controlling access to the {@link IvoryTower}. - */ +/** The proxy controlling access to the {@link IvoryTower}. */ @Slf4j public class WizardTowerProxy implements WizardTower { diff --git a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java index 066eb6fe06eb..bf96c6c00a40 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/AppTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.proxy; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/proxy/src/test/java/com/iluwatar/proxy/IvoryTowerTest.java b/proxy/src/test/java/com/iluwatar/proxy/IvoryTowerTest.java index 4531a67fcaaa..550814e2644e 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/IvoryTowerTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/IvoryTowerTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests for {@link IvoryTower} - */ +/** Tests for {@link IvoryTower} */ class IvoryTowerTest { private InMemoryAppender appender; @@ -52,12 +50,12 @@ void tearDown() { @Test void testEnter() { - final var wizards = List.of( - new Wizard("Gandalf"), - new Wizard("Dumbledore"), - new Wizard("Oz"), - new Wizard("Merlin") - ); + final var wizards = + List.of( + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin")); var tower = new IvoryTower(); wizards.forEach(tower::enter); diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java index 68ec25192969..73ff41ac87dd 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTest.java @@ -29,9 +29,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * Tests for {@link Wizard} - */ +/** Tests for {@link Wizard} */ class WizardTest { @Test @@ -39,4 +37,4 @@ void testToString() { List.of("Gandalf", "Dumbledore", "Oz", "Merlin") .forEach(name -> assertEquals(name, new Wizard(name).toString())); } -} \ No newline at end of file +} diff --git a/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java index 1b3305296734..1e73e690edd1 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java +++ b/proxy/src/test/java/com/iluwatar/proxy/WizardTowerProxyTest.java @@ -33,9 +33,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests for {@link WizardTowerProxy} - */ +/** Tests for {@link WizardTowerProxy} */ class WizardTowerProxyTest { private InMemoryAppender appender; @@ -52,12 +50,12 @@ void tearDown() { @Test void testEnter() { - final var wizards = List.of( - new Wizard("Gandalf"), - new Wizard("Dumbledore"), - new Wizard("Oz"), - new Wizard("Merlin") - ); + final var wizards = + List.of( + new Wizard("Gandalf"), + new Wizard("Dumbledore"), + new Wizard("Oz"), + new Wizard("Merlin")); final var proxy = new WizardTowerProxy(new IvoryTower()); wizards.forEach(proxy::enter); diff --git a/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java b/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java index fb982b96df07..b3341c19f358 100644 --- a/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java +++ b/proxy/src/test/java/com/iluwatar/proxy/utils/InMemoryAppender.java @@ -31,10 +31,7 @@ import java.util.List; import org.slf4j.LoggerFactory; - -/** - * InMemory Log Appender Util. - */ +/** InMemory Log Appender Util. */ public class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/publish-subscribe/README.md b/publish-subscribe/README.md new file mode 100644 index 000000000000..86f6c53166f9 --- /dev/null +++ b/publish-subscribe/README.md @@ -0,0 +1,275 @@ +--- +title: "Publish-Subscribe Pattern in Java: Decoupling components with asynchronous communication" +shortTitle: Publish-Subscribe +description: "Explore the Publish-Subscribe design pattern in Java with detailed examples. Learn how it helps to create loosely coupled, scalable, and flexible systems by allowing components to communicate asynchronously without knowing each other directly." +category: Messaging +language: en +tag: + - Architecture + - Asynchronous + - Decoupling + - Event-driven + - Messaging + - Microservices + - Publish/subscribe + - Scalability +--- + +## Intent of the Publish-Subscribe Design Pattern + +Defines a one-to-many dependency between objects, enabling automatic notification of multiple subscribers when a publisher's state changes or an event occurs. + +## Detailed Explanation of Publish-Subscribe Pattern with Real-World Examples + +Real-world example + +> An analogous real-world example of the Publish-Subscribe pattern is a news broadcasting system. A news agency (publisher) broadcasts breaking news stories without knowing who specifically receives them. Subscribers, such as television stations, news websites, or mobile news apps, independently decide which types of news they want to receive (e.g., sports, politics, weather) and are automatically notified whenever relevant events occur. This approach keeps the news agency unaware of subscribers' specifics, allowing flexible and scalable distribution of information. + +In plain words + +> The Publish-Subscribe design pattern allows senders (publishers) to broadcast messages to multiple receivers (subscribers) without knowing who they are, enabling loose coupling and asynchronous communication in a system. + +Wikipedia says + +> In software architecture, publish–subscribe or pub/sub is a messaging pattern where publishers categorize messages into classes that are received by subscribers. +This is contrasted to the typical messaging pattern model where publishers send messages directly to subscribers. Similarly, subscribers express interest in one or more classes and only receive messages that are of interest, without knowledge of which publishers, if any, there are. Publish–subscribe is a sibling of the message queue paradigm, and is typically one part of a larger message-oriented middleware system. +Most messaging systems support both the pub/sub and message queue models in their API; e.g., Java Message Service (JMS). + +Sequence diagram + +![Publish-Subscribe sequence diagram](./etc/publish-subscribe-sequence-diagram.png) + +## Programmatic Example of Publish-Subscribe Pattern in Java + +First, we identify events that trigger the publisher-subscriber interactions. Common examples include: + +* Sending alerts based on weather events, like earthquakes, floods, and tornadoes. +* Sending notifications based on temperature changes. +* Sending emails to customer support when support tickets are created. + +### Defining the Message + +We start with a simple message class encapsulating the information sent from publishers to subscribers. + +```java +public record Message(Object content) { +} +``` + +### Defining Topics + +A Topic represents an event category that subscribers can register to and publishers can publish messages to. Each topic has: + +* A unique identifier or name (e.g., WEATHER, TEMPERATURE, CUSTOMER_SUPPORT). +* A collection of subscribers listening to this topic. + +Subscribers can dynamically subscribe or unsubscribe. + +```java +@Getter +@Setter +@RequiredArgsConstructor +public class Topic { + + private final String topicName; + private final Set subscribers = new CopyOnWriteArraySet<>(); + + public void addSubscriber(Subscriber subscriber) { + subscribers.add(subscriber); + } + + public void removeSubscriber(Subscriber subscriber) { + subscribers.remove(subscriber); + } + + public void publish(Message message) { + for (Subscriber subscriber : subscribers) { + CompletableFuture.runAsync(() -> subscriber.onMessage(message)); + } + } +} +``` + +### Publisher Implementation + +The Publisher maintains a collection of topics it can publish to. + +* Before publishing, a topic must be registered. +* Upon publishing, it forwards messages to subscribers of the corresponding topic. + +```java +public class PublisherImpl implements Publisher { + + private static final Logger logger = LoggerFactory.getLogger(PublisherImpl.class); + private final Set topics = new HashSet<>(); + + @Override + public void registerTopic(Topic topic) { + topics.add(topic); + } + + @Override + public void publish(Topic topic, Message message) { + if (!topics.contains(topic)) { + logger.error("This topic is not registered: {}", topic.getName()); + return; + } + topic.publish(message); + } +} +``` + +### Defining Subscribers + +Subscribers implement an interface that handles incoming messages. + +* Each subscriber processes messages according to specific logic. +* Subscribers can be registered to multiple topics. + +```java +public interface Subscriber { + void onMessage(Message message); +} +``` + +Subscriber examples: + +* WeatherSubscriber: handles alerts for weather events or temperature changes. +* CustomerSupportSubscriber: handles support tickets by sending emails. +* DelayedWeatherSubscriber: simulates delayed processing for demonstrating asynchronous behavior. + +### Example Usage (Invocation) + +Here's how all components connect: + +1. Create Publisher +2. Register Topics with Publisher +3. Create Subscribers and Subscribe to Relevant Topics +4. Publish Messages +5. Manage Subscriptions Dynamically + +```java +public static void main(String[] args) throws InterruptedException { + + final String topicWeather = "WEATHER"; + final String topicTemperature = "TEMPERATURE"; + final String topicCustomerSupport = "CUSTOMER_SUPPORT"; + + // 1. create the publisher. + Publisher publisher = new PublisherImpl(); + + // 2. define the topics and register on publisher + Topic weatherTopic = new Topic(topicWeather); + publisher.registerTopic(weatherTopic); + + Topic temperatureTopic = new Topic(topicTemperature); + publisher.registerTopic(temperatureTopic); + + Topic supportTopic = new Topic(topicCustomerSupport); + publisher.registerTopic(supportTopic); + + // 3. Create the subscribers and subscribe to the relevant topics + // weatherSub1 will subscribe to two topics WEATHER and TEMPERATURE. + Subscriber weatherSub1 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSub1); + temperatureTopic.addSubscriber(weatherSub1); + + // weatherSub2 will subscribe to WEATHER topic + Subscriber weatherSub2 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSub2); + + // delayedWeatherSub will subscribe to WEATHER topic + // NOTE :: DelayedWeatherSubscriber has a 0.2 sec delay of processing message. + Subscriber delayedWeatherSub = new DelayedWeatherSubscriber(); + weatherTopic.addSubscriber(delayedWeatherSub); + + // subscribe the customer support subscribers to the CUSTOMER_SUPPORT topic. + Subscriber supportSub1 = new CustomerSupportSubscriber(); + supportTopic.addSubscriber(supportSub1); + Subscriber supportSub2 = new CustomerSupportSubscriber(); + supportTopic.addSubscriber(supportSub2); + + // 4. publish message from each topic + publisher.publish(weatherTopic, new Message("earthquake")); + publisher.publish(temperatureTopic, new Message("23C")); + publisher.publish(supportTopic, new Message("support@test.de")); + + // 5. unregister subscriber from TEMPERATURE topic + temperatureTopic.removeSubscriber(weatherSub1); + + // 6. publish message under TEMPERATURE topic + publisher.publish(temperatureTopic, new Message("0C")); + + /* + * Finally, we wait for the subscribers to consume messages to check the output. + * The output can change on each run, depending on how long the execution on each + * subscriber would take + * Expected behavior: + * - weatherSub1 will consume earthquake and 23C + * - weatherSub2 will consume earthquake + * - delayedWeatherSub will take longer and consume earthquake + * - supportSub1, supportSub2 will consume support@test.de + * - the message 0C will not be consumed because weatherSub1 unsubscribed from TEMPERATURE topic + */ + TimeUnit.SECONDS.sleep(2); +} +``` + +### Program output + +Output may vary due to asynchronous subscriber processing: + +``` +14:01:45.599 [ForkJoinPool.commonPool-worker-6] INFO com.iluwatar.publish.subscribe.subscriber.CustomerSupportSubscriber -- Customer Support Subscriber: 1416331388 sent the email to: support@test.de +14:01:45.599 [ForkJoinPool.commonPool-worker-4] INFO com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber -- Weather Subscriber: 1949521124 issued message: 23C +14:01:45.599 [ForkJoinPool.commonPool-worker-2] INFO com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber -- Weather Subscriber: 60629172 issued message: earthquake +14:01:45.599 [ForkJoinPool.commonPool-worker-5] INFO com.iluwatar.publish.subscribe.subscriber.CustomerSupportSubscriber -- Customer Support Subscriber: 1807508804 sent the email to: support@test.de +14:01:45.599 [ForkJoinPool.commonPool-worker-1] INFO com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber -- Weather Subscriber: 1949521124 issued message: earthquake +14:01:47.600 [ForkJoinPool.commonPool-worker-3] INFO com.iluwatar.publish.subscribe.subscriber.DelayedWeatherSubscriber -- Delayed Weather Subscriber: 2085808749 issued message: earthquake +``` + +This demonstrates: + +* Subscribers reacting independently to messages published to subscribed topics. +* Dynamic subscription management allows changing which subscribers listen to specific topics. +* The asynchronous and loosely coupled nature of the publish-subscribe pattern in Java applications. + +## When to Use the Publish-Subscribe Pattern + +* When an application requires loose coupling between event producers and consumers. +* In scenarios where multiple subscribers independently react to the same event. +* When developing scalable, asynchronous messaging systems, particularly within microservices architectures. + +## Real-World Applications of Publish-Subscribe Pattern in Java + +* Java Message Service (JMS) implementations (ActiveMQ, RabbitMQ) +* Apache Kafka (used extensively in Java-based microservices) +* Spring Framework's event publishing and listening mechanisms +* Google Cloud Pub/Sub in Java applications +* AWS Simple Notification Service (SNS) with Java SDK + +## Benefits and Trade-offs of Publish-Subscribe Pattern + +Benefits: + +* Loose coupling between publishers and subscribers promotes flexibility. +* Improved scalability and maintainability as new subscribers can be easily added. +* Supports asynchronous communication, enhancing system responsiveness. + +Trade-offs: + +* Increased complexity due to asynchronous message handling and debugging difficulties. +* Potential message delivery delays and inconsistency if the infrastructure isn't reliable. +* Risk of message flooding, requiring proper infrastructure and consumer management. + +## Related Java Design Patterns + +* [Observer Pattern](https://java-design-patterns.com/patterns/observer/): Both patterns establish a publisher-subscriber relationship; however, Observer typically works within a single application boundary synchronously, whereas Publish-Subscribe is often distributed and asynchronous. +* [Mediator Pattern](https://java-design-patterns.com/patterns/mediator/): Mediator encapsulates interactions between objects in a centralized manner, whereas Publish-Subscribe provides decentralized, loosely-coupled interactions. + +## References and Credits + +* [Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions](https://amzn.to/3WcFVui) +* [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +* [Pattern-Oriented Software Architecture Volume 2: Patterns for Concurrent and Networked Objects](https://amzn.to/3UgC24V) +* [Publisher-Subscriber Pattern (Microsoft)](https://learn.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber) diff --git a/publish-subscribe/etc/pub-sub.png b/publish-subscribe/etc/pub-sub.png new file mode 100644 index 000000000000..9783fdffeab3 Binary files /dev/null and b/publish-subscribe/etc/pub-sub.png differ diff --git a/publish-subscribe/etc/publish-subscribe-sequence-diagram.png b/publish-subscribe/etc/publish-subscribe-sequence-diagram.png new file mode 100644 index 000000000000..8fb1213417b5 Binary files /dev/null and b/publish-subscribe/etc/publish-subscribe-sequence-diagram.png differ diff --git a/publish-subscribe/pom.xml b/publish-subscribe/pom.xml new file mode 100644 index 000000000000..2dce76d5e019 --- /dev/null +++ b/publish-subscribe/pom.xml @@ -0,0 +1,52 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + publish-subscribe + + + + org.junit.jupiter + junit-jupiter-engine + test + + + ch.qos.logback + logback-classic + + + + \ No newline at end of file diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java new file mode 100644 index 000000000000..19e12b9ad1f0 --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/App.java @@ -0,0 +1,139 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe; + +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; +import com.iluwatar.publish.subscribe.publisher.Publisher; +import com.iluwatar.publish.subscribe.publisher.PublisherImpl; +import com.iluwatar.publish.subscribe.subscriber.CustomerSupportSubscriber; +import com.iluwatar.publish.subscribe.subscriber.DelayedWeatherSubscriber; +import com.iluwatar.publish.subscribe.subscriber.Subscriber; +import com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber; +import java.util.concurrent.TimeUnit; + +/** + * The Publish and Subscribe pattern is a messaging paradigm used in software architecture with + * several key points: + *

  • Decoupling of publishers and subscribers: Publishers and subscribers operate independently, + * and there's no direct link between them. This enhances the scalability and * modularity of + * applications. + *
  • Event-driven communication: The pattern facilitates event-driven architectures by allowing + * publishers to broadcast events without concerning themselves with who receives the events. + *
  • Dynamic subscription: Subscribers can dynamically choose to listen for specific events or + * messages they are interested in, often by subscribing to a particular topic or channel. + *
  • Asynchronous processing: The pattern inherently supports asynchronous message processing, + * enabling efficient handling of events and improving application responsiveness. + *
  • Scalability: By decoupling senders and receivers, the pattern can support a large number of + * publishers and subscribers, making it suitable for scalable systems. + *
  • Flexibility and adaptability: New subscribers or publishers can be added to the system + * without significant changes to the existing components, making the system highly adaptable to + * evolving requirements. + * + *

    In this example we will create three topics WEATHER, TEMPERATURE and CUSTOMER_SUPPORT. + * Then we will register those topics in the {@link Publisher}. After that we will create two + * {@link WeatherSubscriber}s, one {@link DelayedWeatherSubscriber} and two {@link + * CustomerSupportSubscriber}.The subscribers will subscribe to the relevant topics. One {@link + * WeatherSubscriber} will subscribe to two topics (WEATHER, TEMPERATURE). {@link + * DelayedWeatherSubscriber} has a delay in message processing. Now we can publish the three + * {@link Topic}s with different content in the {@link Message}s. And we can observe the output + * in the log where, one {@link WeatherSubscriber} will output the message with weather and the + * other {@link WeatherSubscriber} will output weather and temperature. {@link + * CustomerSupportSubscriber}s will output the message with customer support email. {@link + * DelayedWeatherSubscriber} has a delay in processing and will output the message at last. Each + * subscriber is only listening to the subscribed topics. + */ +public class App { + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) throws InterruptedException { + + final String topicWeather = "WEATHER"; + final String topicTemperature = "TEMPERATURE"; + final String topicCustomerSupport = "CUSTOMER_SUPPORT"; + + // 1. create the publisher. + Publisher publisher = new PublisherImpl(); + + // 2. define the topics and register on publisher + Topic weatherTopic = new Topic(topicWeather); + publisher.registerTopic(weatherTopic); + + Topic temperatureTopic = new Topic(topicTemperature); + publisher.registerTopic(temperatureTopic); + + Topic supportTopic = new Topic(topicCustomerSupport); + publisher.registerTopic(supportTopic); + + // 3. Create the subscribers and subscribe to the relevant topics + // weatherSub1 will subscribe to two topics WEATHER and TEMPERATURE. + Subscriber weatherSub1 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSub1); + temperatureTopic.addSubscriber(weatherSub1); + + // weatherSub2 will subscribe to WEATHER topic + Subscriber weatherSub2 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSub2); + + // delayedWeatherSub will subscribe to WEATHER topic + // NOTE :: DelayedWeatherSubscriber has a 0.2 sec delay of processing message. + Subscriber delayedWeatherSub = new DelayedWeatherSubscriber(); + weatherTopic.addSubscriber(delayedWeatherSub); + + // subscribe the customer support subscribers to the CUSTOMER_SUPPORT topic. + Subscriber supportSub1 = new CustomerSupportSubscriber(); + supportTopic.addSubscriber(supportSub1); + Subscriber supportSub2 = new CustomerSupportSubscriber(); + supportTopic.addSubscriber(supportSub2); + + // 4. publish message from each topic + publisher.publish(weatherTopic, new Message("earthquake")); + publisher.publish(temperatureTopic, new Message("23C")); + publisher.publish(supportTopic, new Message("support@test.de")); + + // 5. unregister subscriber from TEMPERATURE topic + temperatureTopic.removeSubscriber(weatherSub1); + + // 6. publish message under TEMPERATURE topic + publisher.publish(temperatureTopic, new Message("0C")); + + /* + * Finally, we wait for the subscribers to consume messages to check the output. + * The output can change on each run, depending on how long the execution on each + * subscriber would take + * Expected behavior: + * - weatherSub1 will consume earthquake and 23C + * - weatherSub2 will consume earthquake + * - delayedWeatherSub will take longer and consume earthquake + * - supportSub1, supportSub2 will consume support@test.de + * - the message 0C will not be consumed because weatherSub1 unsubscribed from TEMPERATURE topic + */ + TimeUnit.SECONDS.sleep(2); + } +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Message.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Message.java new file mode 100644 index 000000000000..0d6017900a0c --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Message.java @@ -0,0 +1,28 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.model; + +/** This class represents a Message that holds the published content. */ +public record Message(Object content) {} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Topic.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Topic.java new file mode 100644 index 000000000000..4c421afe735a --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/model/Topic.java @@ -0,0 +1,72 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.model; + +import com.iluwatar.publish.subscribe.subscriber.Subscriber; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArraySet; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +/** This class represents a Topic that topic name and subscribers. */ +@Getter +@Setter +@RequiredArgsConstructor +public class Topic { + + private final String topicName; + private final Set subscribers = new CopyOnWriteArraySet<>(); + + /** + * Add a subscriber to the list of subscribers. + * + * @param subscriber subscriber to add + */ + public void addSubscriber(Subscriber subscriber) { + subscribers.add(subscriber); + } + + /** + * Remove a subscriber to the list of subscribers. + * + * @param subscriber subscriber to remove + */ + public void removeSubscriber(Subscriber subscriber) { + subscribers.remove(subscriber); + } + + /** + * Publish a message to subscribers. + * + * @param message message with content to publish + */ + public void publish(Message message) { + for (Subscriber subscriber : subscribers) { + CompletableFuture.runAsync(() -> subscriber.onMessage(message)); + } + } +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/Publisher.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/Publisher.java new file mode 100644 index 000000000000..3f58d3719bca --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/Publisher.java @@ -0,0 +1,47 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.publisher; + +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; + +/** This class represents a Publisher. */ +public interface Publisher { + + /** + * Register a topic in the publisher. + * + * @param topic the topic to be registered + */ + void registerTopic(Topic topic); + + /** + * Register a topic in the publisher. + * + * @param topic the topic to publish the message under + * @param message message with content to be published + */ + void publish(Topic topic, Message message); +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/PublisherImpl.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/PublisherImpl.java new file mode 100644 index 000000000000..eb895adaa3cf --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/publisher/PublisherImpl.java @@ -0,0 +1,53 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.publisher; + +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; +import java.util.HashSet; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is an implementation of the Publisher. */ +public class PublisherImpl implements Publisher { + + private static final Logger logger = LoggerFactory.getLogger(PublisherImpl.class); + private final Set topics = new HashSet<>(); + + @Override + public void registerTopic(Topic topic) { + topics.add(topic); + } + + @Override + public void publish(Topic topic, Message message) { + if (!topics.contains(topic)) { + logger.error("This topic is not registered: {}", topic.getTopicName()); + return; + } + topic.publish(message); + } +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/CustomerSupportSubscriber.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/CustomerSupportSubscriber.java new file mode 100644 index 000000000000..385862d8dd44 --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/CustomerSupportSubscriber.java @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.subscriber; + +import com.iluwatar.publish.subscribe.model.Message; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class subscribes to CUSTOMER_SUPPORT topic. */ +@Slf4j +public class CustomerSupportSubscriber implements Subscriber { + + private static final Logger logger = LoggerFactory.getLogger(CustomerSupportSubscriber.class); + + @Override + public void onMessage(Message message) { + if (message.content() instanceof String content) { + logger.info( + "Customer Support Subscriber: {} sent the email to: {}", this.hashCode(), content); + } else { + logger.error( + "Unknown content type: {} expected: {}", + message.content().getClass().getSimpleName(), + String.class.getSimpleName()); + } + } +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/DelayedWeatherSubscriber.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/DelayedWeatherSubscriber.java new file mode 100644 index 000000000000..1f69cf0c57a7 --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/DelayedWeatherSubscriber.java @@ -0,0 +1,61 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.subscriber; + +import com.iluwatar.publish.subscribe.model.Message; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class subscribes to WEATHER topic. */ +@Slf4j +public class DelayedWeatherSubscriber implements Subscriber { + + private static final Logger logger = LoggerFactory.getLogger(DelayedWeatherSubscriber.class); + + @Override + public void onMessage(Message message) { + if (message.content() instanceof String content) { + processData(); + logger.info("Delayed Weather Subscriber: {} issued message: {}", this.hashCode(), content); + } else { + logger.error( + "Unknown content type: {} expected: {}", + message.content().getClass().getSimpleName(), + String.class.getSimpleName()); + } + } + + /** create an artificial delay to mimic the persistence and timeouts in real world. */ + private void processData() { + try { + TimeUnit.MILLISECONDS.sleep(2000); + } catch (InterruptedException e) { + logger.error("Interrupted!", e); + Thread.currentThread().interrupt(); + } + } +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/Subscriber.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/Subscriber.java new file mode 100644 index 000000000000..5deb441c6a52 --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/Subscriber.java @@ -0,0 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.subscriber; + +import com.iluwatar.publish.subscribe.model.Message; + +/** This class represents a Subscriber. */ +public interface Subscriber { + + /** + * On message method will trigger when the subscribed event is published. + * + * @param message the message contains the content of the published event + */ + void onMessage(Message message); +} diff --git a/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/WeatherSubscriber.java b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/WeatherSubscriber.java new file mode 100644 index 000000000000..eda7e6d6cf9b --- /dev/null +++ b/publish-subscribe/src/main/java/com/iluwatar/publish/subscribe/subscriber/WeatherSubscriber.java @@ -0,0 +1,49 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.subscriber; + +import com.iluwatar.publish.subscribe.model.Message; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class subscribes to WEATHER or TEMPERATURE topic. */ +@Slf4j +public class WeatherSubscriber implements Subscriber { + + private static final Logger logger = LoggerFactory.getLogger(WeatherSubscriber.class); + + @Override + public void onMessage(Message message) { + if (message.content() instanceof String content) { + logger.info("Weather Subscriber: {} issued message: {}", this.hashCode(), content); + } else { + logger.error( + "Unknown content type: {} expected: {}", + message.content().getClass().getSimpleName(), + String.class.getSimpleName()); + } + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java new file mode 100644 index 000000000000..50c780cb682d --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/AppTest.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +class AppTest { + + @Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java new file mode 100644 index 000000000000..ecf015752e48 --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/LoggerExtension.java @@ -0,0 +1,61 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.read.ListAppender; +import java.util.List; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.LoggerFactory; + +public class LoggerExtension implements BeforeEachCallback, AfterEachCallback { + + private final ListAppender listAppender = new ListAppender<>(); + private final Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + + @Override + public void afterEach(ExtensionContext extensionContext) throws Exception { + listAppender.stop(); + listAppender.list.clear(); + logger.detachAppender(listAppender); + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + logger.addAppender(listAppender); + listAppender.start(); + } + + public List getMessages() { + return listAppender.list.stream().map(e -> e.getMessage()).toList(); + } + + public List getFormattedMessages() { + return listAppender.list.stream().map(e -> e.getFormattedMessage()).toList(); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java new file mode 100644 index 000000000000..636e1ed66c0f --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/MessageTest.java @@ -0,0 +1,41 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import org.junit.jupiter.api.Test; + +class MessageTest { + + @Test + void testMessage() { + final String content = "some content"; + Message message = new Message(content); + assertInstanceOf(String.class, message.content()); + assertEquals(content, String.valueOf(message.content())); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java new file mode 100644 index 000000000000..cbb5a9882e71 --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/model/TopicTest.java @@ -0,0 +1,64 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import com.iluwatar.publish.subscribe.subscriber.Subscriber; +import com.iluwatar.publish.subscribe.subscriber.WeatherSubscriber; +import java.lang.reflect.Field; +import java.util.Set; +import org.junit.jupiter.api.Test; + +class TopicTest { + + private static final String TOPIC_WEATHER = "WEATHER"; + + @Test + void testTopic() { + Topic topic = new Topic(TOPIC_WEATHER); + assertEquals(TOPIC_WEATHER, topic.getTopicName()); + } + + @Test + void testSubscribing() throws NoSuchFieldException, IllegalAccessException { + + Topic topic = new Topic(TOPIC_WEATHER); + Subscriber sub = new WeatherSubscriber(); + topic.addSubscriber(sub); + + Field field = topic.getClass().getDeclaredField("subscribers"); + field.setAccessible(true); + Object value = field.get(topic); + assertInstanceOf(Set.class, value); + + Set subscribers = (Set) field.get(topic); + assertEquals(1, subscribers.size()); + + topic.removeSubscriber(sub); + assertEquals(0, subscribers.size()); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java new file mode 100644 index 000000000000..7105db20fa93 --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/publisher/PublisherTest.java @@ -0,0 +1,84 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.publisher; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import com.iluwatar.publish.subscribe.LoggerExtension; +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; +import java.lang.reflect.Field; +import java.util.Set; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class PublisherTest { + + @RegisterExtension public LoggerExtension loggerExtension = new LoggerExtension(); + + private static final String TOPIC_WEATHER = "WEATHER"; + private static final String TOPIC_CUSTOMER_SUPPORT = "CUSTOMER_SUPPORT"; + + @Test + void testRegisterTopic() throws NoSuchFieldException, IllegalAccessException { + Topic topic = new Topic(TOPIC_CUSTOMER_SUPPORT); + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(topic); + + Field field = publisher.getClass().getDeclaredField("topics"); + field.setAccessible(true); + Object value = field.get(publisher); + assertInstanceOf(Set.class, value); + + Set topics = (Set) field.get(publisher); + assertEquals(1, topics.size()); + } + + @Test + void testPublish() { + Topic topic = new Topic(TOPIC_WEATHER); + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(topic); + + Message message = new Message("weather"); + assertDoesNotThrow(() -> publisher.publish(topic, message)); + } + + @Test + void testPublishUnregisteredTopic() { + Topic topic = new Topic(TOPIC_WEATHER); + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(topic); + + Topic topicUnregistered = new Topic(TOPIC_CUSTOMER_SUPPORT); + Message message = new Message("support"); + publisher.publish(topicUnregistered, message); + assertEquals( + "This topic is not registered: CUSTOMER_SUPPORT", + loggerExtension.getFormattedMessages().getFirst()); + } +} diff --git a/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/subscriber/SubscriberTest.java b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/subscriber/SubscriberTest.java new file mode 100644 index 000000000000..8fdc66ab7225 --- /dev/null +++ b/publish-subscribe/src/test/java/com/iluwatar/publish/subscribe/subscriber/SubscriberTest.java @@ -0,0 +1,207 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.publish.subscribe.subscriber; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.iluwatar.publish.subscribe.LoggerExtension; +import com.iluwatar.publish.subscribe.model.Message; +import com.iluwatar.publish.subscribe.model.Topic; +import com.iluwatar.publish.subscribe.publisher.Publisher; +import com.iluwatar.publish.subscribe.publisher.PublisherImpl; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SubscriberTest { + + private static final Logger logger = LoggerFactory.getLogger(SubscriberTest.class); + @RegisterExtension public LoggerExtension loggerExtension = new LoggerExtension(); + + private static final String TOPIC_WEATHER = "WEATHER"; + private static final String TOPIC_TEMPERATURE = "TEMPERATURE"; + private static final String TOPIC_CUSTOMER_SUPPORT = "CUSTOMER_SUPPORT"; + + @Test + void testSubscribeToMultipleTopics() { + + Topic topicWeather = new Topic(TOPIC_WEATHER); + Topic topicTemperature = new Topic(TOPIC_TEMPERATURE); + Subscriber weatherSubscriber = new WeatherSubscriber(); + + topicWeather.addSubscriber(weatherSubscriber); + topicTemperature.addSubscriber(weatherSubscriber); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(topicWeather); + publisher.registerTopic(topicTemperature); + + publisher.publish(topicWeather, new Message("earthquake")); + publisher.publish(topicTemperature, new Message("-2C")); + + waitForOutput(); + assertEquals(2, loggerExtension.getFormattedMessages().size()); + } + + @Test + void testOnlyReceiveSubscribedTopic() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber); + + Topic customerSupportTopic = new Topic(TOPIC_CUSTOMER_SUPPORT); + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + publisher.registerTopic(customerSupportTopic); + + publisher.publish(customerSupportTopic, new Message("support@test.de")); + + waitForOutput(); + assertEquals(0, loggerExtension.getFormattedMessages().size()); + } + + @Test + void testMultipleSubscribersOnSameTopic() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber1 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber1); + + Subscriber weatherSubscriber2 = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber2); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + + publisher.publish(weatherTopic, new Message("tornado")); + + waitForOutput(); + assertEquals(2, loggerExtension.getFormattedMessages().size()); + assertEquals( + "Weather Subscriber: " + weatherSubscriber1.hashCode() + " issued message: tornado", + getMessage(weatherSubscriber1.hashCode())); + assertEquals( + "Weather Subscriber: " + weatherSubscriber2.hashCode() + " issued message: tornado", + getMessage(weatherSubscriber2.hashCode())); + } + + @Test + void testMultipleSubscribersOnDifferentTopics() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber); + + Topic customerSupportTopic = new Topic(TOPIC_CUSTOMER_SUPPORT); + Subscriber customerSupportSubscriber = new CustomerSupportSubscriber(); + customerSupportTopic.addSubscriber(customerSupportSubscriber); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + publisher.registerTopic(customerSupportTopic); + + publisher.publish(weatherTopic, new Message("flood")); + publisher.publish(customerSupportTopic, new Message("support@test.at")); + + waitForOutput(); + assertEquals(2, loggerExtension.getFormattedMessages().size()); + assertEquals( + "Weather Subscriber: " + weatherSubscriber.hashCode() + " issued message: flood", + getMessage(weatherSubscriber.hashCode())); + assertEquals( + "Customer Support Subscriber: " + + customerSupportSubscriber.hashCode() + + " sent the email to: support@test.at", + getMessage(customerSupportSubscriber.hashCode())); + } + + @Test + void testInvalidContentOnTopics() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber); + + Topic customerSupportTopic = new Topic(TOPIC_CUSTOMER_SUPPORT); + Subscriber customerSupportSubscriber = new CustomerSupportSubscriber(); + customerSupportTopic.addSubscriber(customerSupportSubscriber); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + publisher.registerTopic(customerSupportTopic); + + publisher.publish(weatherTopic, new Message(123)); + publisher.publish(customerSupportTopic, new Message(34.56)); + + waitForOutput(); + assertTrue(loggerExtension.getFormattedMessages().getFirst().contains("Unknown content type")); + assertTrue(loggerExtension.getFormattedMessages().get(1).contains("Unknown content type")); + } + + @Test + void testUnsubscribe() { + + Topic weatherTopic = new Topic(TOPIC_WEATHER); + Subscriber weatherSubscriber = new WeatherSubscriber(); + weatherTopic.addSubscriber(weatherSubscriber); + + Publisher publisher = new PublisherImpl(); + publisher.registerTopic(weatherTopic); + + publisher.publish(weatherTopic, new Message("earthquake")); + + weatherTopic.removeSubscriber(weatherSubscriber); + publisher.publish(weatherTopic, new Message("tornado")); + + waitForOutput(); + assertEquals(1, loggerExtension.getFormattedMessages().size()); + assertTrue(loggerExtension.getFormattedMessages().getFirst().contains("earthquake")); + assertFalse(loggerExtension.getFormattedMessages().getFirst().contains("tornado")); + } + + private String getMessage(int subscriberHash) { + Optional message = + loggerExtension.getFormattedMessages().stream() + .filter(str -> str.contains(String.valueOf(subscriberHash))) + .findFirst(); + assertTrue(message.isPresent()); + return message.get(); + } + + private void waitForOutput() { + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + logger.error("Interrupted!", e); + Thread.currentThread().interrupt(); + } + } +} diff --git a/queue-based-load-leveling/README.md b/queue-based-load-leveling/README.md index a72d39ea3c43..5bf577052f80 100644 --- a/queue-based-load-leveling/README.md +++ b/queue-based-load-leveling/README.md @@ -40,6 +40,10 @@ Wikipedia says > Message Queues are essential components for inter-process communication (IPC) and inter-thread communication, using queues to manage the passing of messages. They help in decoupling producers and consumers, allowing asynchronous processing, which is a key aspect of the Queue-Based Load Leveling pattern. +Flowchart + +![Queue-Based Load Leveling flowchart](./etc/queue-based-load-leveling-flowchart.png) + ## Programmatic Example of Queue-Based Load Leveling Pattern in Java The Queue-Based Load Leveling pattern helps to manage high-volume, sporadic bursts of tasks that can overwhelm a system. It uses a queue as a buffer to hold tasks, decoupling the task generation from task processing. Tasks are processed at a controlled rate, ensuring optimal load management and fault tolerance, crucial for maintaining robust system architecture. diff --git a/queue-based-load-leveling/etc/queue-based-load-leveling-flowchart.png b/queue-based-load-leveling/etc/queue-based-load-leveling-flowchart.png new file mode 100644 index 000000000000..2fdf718414c5 Binary files /dev/null and b/queue-based-load-leveling/etc/queue-based-load-leveling-flowchart.png differ diff --git a/queue-based-load-leveling/etc/queue-based-load-leveling.urm.puml b/queue-based-load-leveling/etc/queue-based-load-leveling.urm.puml new file mode 100644 index 000000000000..ca90842d92dd --- /dev/null +++ b/queue-based-load-leveling/etc/queue-based-load-leveling.urm.puml @@ -0,0 +1,44 @@ +@startuml +package com.iluwatar.queue.load.leveling { + class App { + - LOGGER : Logger {static} + - SHUTDOWN_TIME : int {static} + + App() + + main(args : String[]) {static} + } + class Message { + - msg : String + + Message(msg : String) + + getMsg() : String + + toString() : String + } + class MessageQueue { + - LOGGER : Logger {static} + - blkQueue : BlockingQueue + + MessageQueue() + + retrieveMsg() : Message + + submitMsg(msg : Message) + } + class ServiceExecutor { + - LOGGER : Logger {static} + - msgQueue : MessageQueue + + ServiceExecutor(msgQueue : MessageQueue) + + run() + } + interface Task { + + submit(Message) {abstract} + } + class TaskGenerator { + - LOGGER : Logger {static} + - msgCount : int + - msgQueue : MessageQueue + + TaskGenerator(msgQueue : MessageQueue, msgCount : int) + + run() + + submit(msg : Message) + } +} +MessageQueue --> "-blkQueue" Message +ServiceExecutor --> "-msgQueue" MessageQueue +TaskGenerator --> "-msgQueue" MessageQueue +TaskGenerator ..|> Task +@enduml \ No newline at end of file diff --git a/queue-based-load-leveling/pom.xml b/queue-based-load-leveling/pom.xml index c789c6a3959d..701123f60bab 100644 --- a/queue-based-load-leveling/pom.xml +++ b/queue-based-load-leveling/pom.xml @@ -34,6 +34,14 @@ queue-based-load-leveling + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java index 7042ff7b79a2..368346630db0 100644 --- a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/App.java @@ -31,11 +31,10 @@ /** * Many solutions in the cloud involve running tasks that invoke services. In this environment, if a - * service is subjected to intermittent heavy loads, it can cause performance or reliability - * issues. + * service is subjected to intermittent heavy loads, it can cause performance or reliability issues. * - *

    A service could be a component that is part of the same solution as the tasks that utilize - * it, or it could be a third-party service providing access to frequently used resources such as a + *

    A service could be a component that is part of the same solution as the tasks that utilize it, + * or it could be a third-party service providing access to frequently used resources such as a * cache or a storage service. If the same service is utilized by a number of tasks running * concurrently, it can be difficult to predict the volume of requests to which the service might be * subjected at any given point in time. @@ -45,8 +44,7 @@ * The task posts a message containing the data required by the service to a queue. The queue acts * as a buffer, storing the message until it is retrieved by the service. The service retrieves the * messages from the queue and processes them. Requests from a number of tasks, which can be - * generated at a highly variable rate, can be passed to the service through the same message - * queue. + * generated at a highly variable rate, can be passed to the service through the same message queue. * *

    The queue effectively decouples the tasks from the service, and the service can handle the * messages at its own pace irrespective of the volume of requests from concurrent tasks. @@ -61,7 +59,7 @@ @Slf4j public class App { - //Executor shut down time limit. + // Executor shut down time limit. private static final int SHUTDOWN_TIME = 15; /** @@ -71,7 +69,7 @@ public class App { */ public static void main(String[] args) { - // An Executor that provides methods to manage termination and methods that can + // An Executor that provides methods to manage termination and methods that can // produce a Future for tracking progress of one or more asynchronous tasks. ExecutorService executor = null; @@ -100,12 +98,13 @@ public static void main(String[] args) { executor.submit(srvRunnable); // Initiates an orderly shutdown. - LOGGER.info("Initiating shutdown." - + " Executor will shutdown only after all the Threads are completed."); + LOGGER.info( + "Initiating shutdown." + + " Executor will shutdown only after all the Threads are completed."); executor.shutdown(); - // Wait for SHUTDOWN_TIME seconds for all the threads to complete - // their tasks and then shut down the executor and then exit. + // Wait for SHUTDOWN_TIME seconds for all the threads to complete + // their tasks and then shut down the executor and then exit. if (!executor.awaitTermination(SHUTDOWN_TIME, TimeUnit.SECONDS)) { LOGGER.info("Executor was shut down and Exiting."); executor.shutdownNow(); @@ -114,4 +113,4 @@ public static void main(String[] args) { LOGGER.error(e.getMessage()); } } -} \ No newline at end of file +} diff --git a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java index c57c171a1c89..3b1eb84b0a41 100644 --- a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Message.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Message class with only one parameter. - */ +/** Message class with only one parameter. */ @Getter @RequiredArgsConstructor public class Message { @@ -39,4 +37,4 @@ public class Message { public String toString() { return msg; } -} \ No newline at end of file +} diff --git a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java index 1d28faa54ca5..f027937f08ae 100644 --- a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/MessageQueue.java @@ -37,7 +37,7 @@ public class MessageQueue { private final BlockingQueue blkQueue; - // Default constructor when called creates Blocking Queue object. + // Default constructor when called creates Blocking Queue object. public MessageQueue() { this.blkQueue = new ArrayBlockingQueue<>(1024); } diff --git a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java index 02530042b370..0a8f1b5a7642 100644 --- a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/ServiceExecutor.java @@ -39,9 +39,7 @@ public ServiceExecutor(MessageQueue msgQueue) { this.msgQueue = msgQueue; } - /** - * The ServiceExecutor thread will retrieve each message and process it. - */ + /** The ServiceExecutor thread will retrieve each message and process it. */ public void run() { try { while (!Thread.currentThread().isInterrupted()) { diff --git a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java index ab2b79e28acb..dfdb4454489f 100644 --- a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/Task.java @@ -24,9 +24,7 @@ */ package com.iluwatar.queue.load.leveling; -/** - * Task Interface. - */ +/** Task Interface. */ public interface Task { void submit(Message msg); } diff --git a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java index 9b1407277a27..904bb410182d 100644 --- a/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java +++ b/queue-based-load-leveling/src/main/java/com/iluwatar/queue/load/leveling/TaskGenerator.java @@ -45,9 +45,7 @@ public TaskGenerator(MessageQueue msgQueue, int msgCount) { this.msgCount = msgCount; } - /** - * Submit messages to the Blocking Queue. - */ + /** Submit messages to the Blocking Queue. */ public void submit(Message msg) { try { this.msgQueue.submitMsg(msg); @@ -80,4 +78,4 @@ public void run() { LOGGER.error(e.getMessage()); } } -} \ No newline at end of file +} diff --git a/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java index 6ba3d87b43e0..06da02738391 100644 --- a/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java +++ b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.queue.load.leveling; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java index 4ddcc9937f3b..fa7d6f3b156a 100644 --- a/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java +++ b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageQueueTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test case for submitting and retrieving messages from Blocking Queue. - */ +/** Test case for submitting and retrieving messages from Blocking Queue. */ class MessageQueueTest { @Test @@ -44,5 +42,4 @@ void messageQueueTest() { // retrieve message assertEquals("MessageQueue Test", msgQueue.retrieveMsg().getMsg()); } - } diff --git a/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java index a0314ed04495..8af65cc1e947 100644 --- a/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java +++ b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/MessageTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test case for creating and checking the Message. - */ +/** Test case for creating and checking the Message. */ class MessageTest { @Test diff --git a/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java index 0a03bc560a1d..395f5ec40bda 100644 --- a/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java +++ b/queue-based-load-leveling/src/test/java/com/iluwatar/queue/load/leveling/TaskGenSrvExeTest.java @@ -52,5 +52,4 @@ void taskGeneratorTest() { assertNotNull(srvExeThr); } - } diff --git a/reactor/README.md b/reactor/README.md index 50ee482cb15a..ce6109111056 100644 --- a/reactor/README.md +++ b/reactor/README.md @@ -38,6 +38,10 @@ Wikipedia says > The reactor software design pattern is an event handling strategy that can respond to many potential service requests concurrently. The pattern's key component is an event loop, running in a single thread or process, which demultiplexes incoming requests and dispatches them to the correct request handler. +Sequence diagram + +![Reactor Sequence diagram](./etc/reactor-sequence-diagram.png) + ## Programmatic Example of Reactor Pattern in Java The Reactor design pattern is a concurrency model that efficiently handles multiple simultaneous I/O operations using a single or a limited number of threads. It is particularly useful in scenarios where an application needs to handle multiple clients sending service requests concurrently. @@ -157,10 +161,6 @@ Running the code produces the following output: This concludes our detailed explanation of the Reactor design pattern. The Reactor pattern allows us to handle multiple simultaneous I/O operations efficiently using a single or a limited number of threads. -## Detailed Explanation of Reactor Pattern with Real-World Examples - -![Reactor](./etc/reactor.png "Reactor") - ## When to Use the Reactor Pattern in Java Employ the Reactor pattern in scenarios requiring low-latency and high-throughput in server-side applications, making it an essential strategy for modern networking frameworks and web servers. diff --git a/reactor/etc/reactor-sequence-diagram.png b/reactor/etc/reactor-sequence-diagram.png new file mode 100644 index 000000000000..11b7cc83e5fc Binary files /dev/null and b/reactor/etc/reactor-sequence-diagram.png differ diff --git a/reactor/pom.xml b/reactor/pom.xml index ff884ab586b1..738959d7ccc5 100644 --- a/reactor/pom.xml +++ b/reactor/pom.xml @@ -34,6 +34,14 @@ reactor + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/App.java b/reactor/src/main/java/com/iluwatar/reactor/app/App.java index cb10d5edb3c0..a01738d2e64b 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/App.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/App.java @@ -47,45 +47,35 @@ *

    PROBLEM
    * Server applications in a distributed system must handle multiple clients that send them service * requests. Following forces need to be resolved: + * *

      - *
    • Availability
    • - *
    • Efficiency
    • - *
    • Programming Simplicity
    • - *
    • Adaptability
    • + *
    • Availability + *
    • Efficiency + *
    • Programming Simplicity + *
    • Adaptability *
    * *

    PARTICIPANTS
    + * *

      - *
    • Synchronous Event De-multiplexer - *

      - * {@link NioReactor} plays the role of synchronous event de-multiplexer. - * It waits for events on multiple channels registered to it in an event loop. - *

      - *
    • - *
    • Initiation Dispatcher - *

      - * {@link NioReactor} plays this role as the application specific {@link ChannelHandler}s - * are registered to the reactor. - *

      - *
    • - *
    • Handle - *

      - * {@link AbstractNioChannel} acts as a handle that is registered to the reactor. - * When any events occur on a handle, reactor calls the appropriate handler. - *

      - *
    • - *
    • Event Handler - *

      - * {@link ChannelHandler} acts as an event handler, which is bound to a - * channel and is called back when any event occurs on any of its associated handles. Application - * logic resides in event handlers. - *

      - *
    • + *
    • Synchronous Event De-multiplexer + *

      {@link NioReactor} plays the role of synchronous event de-multiplexer. It waits for + * events on multiple channels registered to it in an event loop. + *

    • Initiation Dispatcher + *

      {@link NioReactor} plays this role as the application specific {@link ChannelHandler}s + * are registered to the reactor. + *

    • Handle + *

      {@link AbstractNioChannel} acts as a handle that is registered to the reactor. When any + * events occur on a handle, reactor calls the appropriate handler. + *

    • Event Handler + *

      {@link ChannelHandler} acts as an event handler, which is bound to a channel and is + * called back when any event occurs on any of its associated handles. Application logic + * resides in event handlers. *

    + * * The application utilizes single thread to listen for requests on all ports. It does not create a * separate thread for each client, which provides better scalability under load (number of clients - * increase). - * The example uses Java NIO framework to implement the Reactor. + * increase). The example uses Java NIO framework to implement the Reactor. */ public class App { @@ -103,9 +93,7 @@ public App(Dispatcher dispatcher) { this.dispatcher = dispatcher; } - /** - * App entry. - */ + /** App entry. */ public static void main(String[] args) throws IOException { new App(new ThreadPoolDispatcher(2)).start(); } @@ -143,7 +131,7 @@ public void start() throws IOException { * Stops the NIO reactor. This is a blocking call. * * @throws InterruptedException if interrupted while stopping the reactor. - * @throws IOException if any I/O error occurs + * @throws IOException if any I/O error occurs */ public void stop() throws InterruptedException, IOException { reactor.stop(); diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java index 53f67199158d..8636150bd1bf 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/AppClient.java @@ -70,9 +70,7 @@ public void start() throws IOException { service.execute(new UdpLoggingClient("Client 4", 16669)); } - /** - * Stops logging clients. This is a blocking call. - */ + /** Stops logging clients. This is a blocking call. */ public void stop() { service.shutdown(); if (!service.isTerminated()) { @@ -94,9 +92,7 @@ private static void artificialDelayOf(long millis) { } } - /** - * A logging client that sends requests to Reactor on TCP socket. - */ + /** A logging client that sends requests to Reactor on TCP socket. */ static class TcpLoggingClient implements Runnable { private final int serverPort; @@ -141,12 +137,9 @@ private void sendLogRequests(PrintWriter writer, InputStream inputStream) throws artificialDelayOf(100); } } - } - /** - * A logging client that sends requests to Reactor on UDP socket. - */ + /** A logging client that sends requests to Reactor on UDP socket. */ static class UdpLoggingClient implements Runnable { private final String clientName; private final InetSocketAddress remoteAddress; @@ -155,7 +148,7 @@ static class UdpLoggingClient implements Runnable { * Creates a new UDP logging client. * * @param clientName the name of the client to be sent in logging requests. - * @param port the port on which client will send logging requests. + * @param port the port on which client will send logging requests. * @throws UnknownHostException if localhost is unknown */ public UdpLoggingClient(String clientName, int port) throws UnknownHostException { diff --git a/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java b/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java index 74ec2ed7e3bb..bc71b0353bf2 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java +++ b/reactor/src/main/java/com/iluwatar/reactor/app/LoggingHandler.java @@ -40,9 +40,7 @@ public class LoggingHandler implements ChannelHandler { private static final byte[] ACK = "Data logged successfully".getBytes(); - /** - * Decodes the received data and logs it on standard console. - */ + /** Decodes the received data and logs it on standard console. */ @Override public void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key) { /* @@ -61,10 +59,7 @@ public void handleChannelRead(AbstractNioChannel channel, Object readObject, Sel } private static void sendReply( - AbstractNioChannel channel, - DatagramPacket incomingPacket, - SelectionKey key - ) { + AbstractNioChannel channel, DatagramPacket incomingPacket, SelectionKey key) { /* * Create a reply acknowledgement datagram packet setting the receiver to the sender of incoming * message. diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java index 83a2d930358d..df7ec1c827d8 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/AbstractNioChannel.java @@ -47,8 +47,7 @@ public abstract class AbstractNioChannel { private final SelectableChannel channel; - @Getter - private final ChannelHandler handler; + @Getter private final ChannelHandler handler; private final Map> channelToPendingWrites; private NioReactor reactor; @@ -64,9 +63,7 @@ public AbstractNioChannel(ChannelHandler handler, SelectableChannel channel) { this.channelToPendingWrites = new ConcurrentHashMap<>(); } - /** - * Injects the reactor in this channel. - */ + /** Injects the reactor in this channel. */ void setReactor(NioReactor reactor) { this.reactor = reactor; } @@ -125,7 +122,7 @@ void flush(SelectionKey key) throws IOException { * Writes the data to the channel. * * @param pendingWrite the data to be written on channel. - * @param key the key which is writable. + * @param key the key which is writable. * @throws IOException if any I/O error occurs. */ protected abstract void doWrite(Object pendingWrite, SelectionKey key) throws IOException; @@ -149,13 +146,15 @@ void flush(SelectionKey key) throws IOException { * * * @param data the data to be written on underlying channel. - * @param key the key which is writable. + * @param key the key which is writable. */ public void write(Object data, SelectionKey key) { var pendingWrites = this.channelToPendingWrites.get(key.channel()); if (pendingWrites == null) { synchronized (this.channelToPendingWrites) { - pendingWrites = this.channelToPendingWrites.computeIfAbsent(key.channel(), k -> new ConcurrentLinkedQueue<>()); + pendingWrites = + this.channelToPendingWrites.computeIfAbsent( + key.channel(), k -> new ConcurrentLinkedQueue<>()); } } pendingWrites.add(data); diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/ChannelHandler.java b/reactor/src/main/java/com/iluwatar/reactor/framework/ChannelHandler.java index 46cb2fc54cf6..1a37f2b5984c 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/ChannelHandler.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/ChannelHandler.java @@ -31,17 +31,16 @@ * to it by the {@link Dispatcher}. This is where the application logic resides. * *

    A {@link ChannelHandler} can be associated with one or many {@link AbstractNioChannel}s, and - * whenever an event occurs on any of the associated channels, the handler is notified of the - * event. + * whenever an event occurs on any of the associated channels, the handler is notified of the event. */ public interface ChannelHandler { /** * Called when the {@code channel} receives some data from remote peer. * - * @param channel the channel from which the data was received. + * @param channel the channel from which the data was received. * @param readObject the data read. - * @param key the key on which read event occurred. + * @param key the key on which read event occurred. */ void handleChannelRead(AbstractNioChannel channel, Object readObject, SelectionKey key); } diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/Dispatcher.java b/reactor/src/main/java/com/iluwatar/reactor/framework/Dispatcher.java index 583c1241bcf9..403b58c90627 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/Dispatcher.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/Dispatcher.java @@ -30,8 +30,9 @@ * Represents the event dispatching strategy. When {@link NioReactor} senses any event on the * registered {@link AbstractNioChannel}s then it de-multiplexes the event type, read or write or * connect, and then calls the {@link Dispatcher} to dispatch the read events. This decouples the - * I/O processing from application specific processing.
    Dispatcher should call the {@link - * ChannelHandler} associated with the channel on which event occurred. + * I/O processing from application specific processing.
    + * Dispatcher should call the {@link ChannelHandler} associated with the channel on which event + * occurred. * *

    The application can customize the way in which event is dispatched such as using the reactor * thread to dispatch event to channels or use a worker pool to do the non I/O processing. @@ -47,9 +48,9 @@ public interface Dispatcher { * *

    The type of readObject depends on the channel on which data was received. * - * @param channel on which read event occurred + * @param channel on which read event occurred * @param readObject object read by channel - * @param key on which event occurred + * @param key on which event occurred */ void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key); diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java index bb582b893739..0272d3c90f59 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioDatagramChannel.java @@ -35,9 +35,7 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; -/** - * A wrapper over {@link DatagramChannel} which can read and write data on a DatagramChannel. - */ +/** A wrapper over {@link DatagramChannel} which can read and write data on a DatagramChannel. */ @Slf4j public class NioDatagramChannel extends AbstractNioChannel { @@ -50,7 +48,7 @@ public class NioDatagramChannel extends AbstractNioChannel { *

    Note the constructor does not bind the socket, {@link #bind()} method should be called for * binding the socket. * - * @param port the port to be bound to listen for incoming datagram requests. + * @param port the port to be bound to listen for incoming datagram requests. * @param handler the handler to be used for handling incoming requests on this channel. * @throws IOException if any I/O error occurs. */ @@ -130,16 +128,12 @@ public void write(Object data, SelectionKey key) { super.write(data, key); } - /** - * Container of data used for {@link NioDatagramChannel} to communicate with remote peer. - */ + /** Container of data used for {@link NioDatagramChannel} to communicate with remote peer. */ @Getter public static class DatagramPacket { private final ByteBuffer data; - @Setter - private SocketAddress sender; - @Setter - private SocketAddress receiver; + @Setter private SocketAddress sender; + @Setter private SocketAddress receiver; /** * Creates a container with underlying data. diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java index fb0ab5784acd..714b16d59929 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioReactor.java @@ -42,9 +42,8 @@ * synchronously de-multiplexes the event which can be any of read, write or accept, and dispatches * the event to the appropriate {@link ChannelHandler} using the {@link Dispatcher}. * - *

    Implementation: A NIO reactor runs in its own thread when it is started using {@link - * #start()} method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event - * De-multiplexing. + *

    Implementation: A NIO reactor runs in its own thread when it is started using {@link #start()} + * method. {@link NioReactor} uses {@link Selector} for realizing Synchronous Event De-multiplexing. * *

    NOTE: This is one of the ways to implement NIO reactor, and it does not take care of all * possible edge cases which are required in a real application. This implementation is meant to @@ -55,6 +54,7 @@ public class NioReactor { private final Selector selector; private final Dispatcher dispatcher; + /** * All the work of altering the SelectionKey operations and Selector operations are performed in * the context of main event loop of reactor. So when any channel needs to change its readability @@ -62,6 +62,7 @@ public class NioReactor { * the command and executes it in next iteration. */ private final Queue pendingCommands = new ConcurrentLinkedQueue<>(); + private final ExecutorService reactorMain = Executors.newSingleThreadExecutor(); /** @@ -76,25 +77,24 @@ public NioReactor(Dispatcher dispatcher) throws IOException { this.selector = Selector.open(); } - /** - * Starts the reactor event loop in a new thread. - */ + /** Starts the reactor event loop in a new thread. */ public void start() { - reactorMain.execute(() -> { - try { - LOGGER.info("Reactor started, waiting for events..."); - eventLoop(); - } catch (IOException e) { - LOGGER.error("exception in event loop", e); - } - }); + reactorMain.execute( + () -> { + try { + LOGGER.info("Reactor started, waiting for events..."); + eventLoop(); + } catch (IOException e) { + LOGGER.error("exception in event loop", e); + } + }); } /** * Stops the reactor and related resources such as dispatcher. * * @throws InterruptedException if interrupted while stopping the reactor. - * @throws IOException if any I/O error occurs. + * @throws IOException if any I/O error occurs. */ public void stop() throws InterruptedException, IOException { reactorMain.shutdown(); @@ -112,7 +112,7 @@ public void stop() throws InterruptedException, IOException { * AbstractNioChannel#getInterestedOps()} to know about the interested operation of this channel. * * @param channel a new channel on which reactor will wait for events. The channel must be bound - * prior to being registered. + * prior to being registered. * @return this * @throws IOException if any I/O error occurs. */ @@ -217,7 +217,7 @@ private void onChannelAcceptable(SelectionKey key) throws IOException { *

    This is a non-blocking method and does not guarantee that the operations have changed when * this method returns. * - * @param key the key for which operations have to be changed. + * @param key the key for which operations have to be changed. * @param interestedOps the new interest operations. */ public void changeOps(SelectionKey key, int interestedOps) { @@ -225,9 +225,7 @@ public void changeOps(SelectionKey key, int interestedOps) { selector.wakeup(); } - /** - * A command that changes the interested operations of the key provided. - */ + /** A command that changes the interested operations of the key provided. */ static class ChangeKeyOpsCommand implements Runnable { private final SelectionKey key; private final int interestedOps; diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/NioServerSocketChannel.java b/reactor/src/main/java/com/iluwatar/reactor/framework/NioServerSocketChannel.java index bc512f1c9b25..3e56b3fe6e37 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/NioServerSocketChannel.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/NioServerSocketChannel.java @@ -43,13 +43,13 @@ public class NioServerSocketChannel extends AbstractNioChannel { private final int port; /** - * Creates a {@link ServerSocketChannel} which will bind at provided port and use - * handler to handle incoming events on this channel. + * Creates a {@link ServerSocketChannel} which will bind at provided port and use handler + * to handle incoming events on this channel. * *

    Note the constructor does not bind the socket, {@link #bind()} method should be called for * binding the socket. * - * @param port the port on which channel will be bound to accept incoming connection requests. + * @param port the port on which channel will be bound to accept incoming connection requests. * @param handler the handler that will handle incoming requests on this channel. * @throws IOException if any I/O error occurs. */ @@ -58,7 +58,6 @@ public NioServerSocketChannel(int port, ChannelHandler handler) throws IOExcepti this.port = port; } - @Override public int getInterestedOps() { // being a server socket channel it is interested in accepting connection from remote peers. diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/SameThreadDispatcher.java b/reactor/src/main/java/com/iluwatar/reactor/framework/SameThreadDispatcher.java index b075c71b6f2b..b06fe8a66e99 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/SameThreadDispatcher.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/SameThreadDispatcher.java @@ -38,8 +38,9 @@ public class SameThreadDispatcher implements Dispatcher { /** - * Dispatches the read event in the context of caller thread.
    Note this is a blocking call. - * It returns only after the associated handler has handled the read event. + * Dispatches the read event in the context of caller thread.
    + * Note this is a blocking call. It returns only after the associated handler has handled the read + * event. */ @Override public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) { @@ -50,9 +51,7 @@ public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, Se channel.getHandler().handleChannelRead(channel, readObject, key); } - /** - * No resources to free. - */ + /** No resources to free. */ @Override public void stop() { // no-op diff --git a/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java b/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java index c6c479d35c07..b202c9003c2d 100644 --- a/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java +++ b/reactor/src/main/java/com/iluwatar/reactor/framework/ThreadPoolDispatcher.java @@ -49,8 +49,9 @@ public ThreadPoolDispatcher(int poolSize) { /** * Submits the work of dispatching the read event to worker pool, where it gets picked up by - * worker threads.
    Note that this is a non-blocking call and returns immediately. It is not - * guaranteed that the event has been handled by associated handler. + * worker threads.
    + * Note that this is a non-blocking call and returns immediately. It is not guaranteed that the + * event has been handled by associated handler. */ @Override public void onChannelReadEvent(AbstractNioChannel channel, Object readObject, SelectionKey key) { diff --git a/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java b/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java index cb9fd970d553..79d4ffa59f28 100644 --- a/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java +++ b/reactor/src/test/java/com/iluwatar/reactor/app/ReactorTest.java @@ -42,7 +42,7 @@ class ReactorTest { /** * Test the application using pooled thread dispatcher. * - * @throws IOException if any I/O error occurs. + * @throws IOException if any I/O error occurs. * @throws InterruptedException if interrupted while stopping the application. */ @Test @@ -74,7 +74,7 @@ void testAppUsingThreadPoolDispatcher() throws IOException, InterruptedException /** * Test the application using same thread dispatcher. * - * @throws IOException if any I/O error occurs. + * @throws IOException if any I/O error occurs. * @throws InterruptedException if interrupted while stopping the application. */ @Test diff --git a/registry/README.md b/registry/README.md index aa40bb80941d..82834ffe8b2f 100644 --- a/registry/README.md +++ b/registry/README.md @@ -38,6 +38,10 @@ wiki.c2.com says > A registry is a global association from keys to objects, allowing the objects to be reached from anywhere. It involves two methods: one that takes a key and an object and add objects to the registry and one that takes a key and returns the object for the key. It's similar to the MultitonPattern, but doesn't restrict instances to only those in the registry. +Flowchart + +![Registry flowchart](./etc/registry-flowchart.png) + ## Programmatic Example of Registry Pattern in Java The Registry design pattern is a well-known pattern used in software design where objects are stored and provide a global point of access to them. This pattern is particularly useful when you need to manage a global collection of objects, decouple the creation of objects from their usage, ensure a controlled lifecycle for objects, and avoid redundant creation of objects. diff --git a/registry/etc/registry-flowchart.png b/registry/etc/registry-flowchart.png new file mode 100644 index 000000000000..e77c429a702b Binary files /dev/null and b/registry/etc/registry-flowchart.png differ diff --git a/registry/pom.xml b/registry/pom.xml index ec6431452c2e..1063e53376da 100644 --- a/registry/pom.xml +++ b/registry/pom.xml @@ -34,6 +34,14 @@ registry + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/registry/src/main/java/com/iluwatar/registry/App.java b/registry/src/main/java/com/iluwatar/registry/App.java index 1a9c5234b81d..8e463a67cd43 100644 --- a/registry/src/main/java/com/iluwatar/registry/App.java +++ b/registry/src/main/java/com/iluwatar/registry/App.java @@ -28,11 +28,11 @@ import org.slf4j.LoggerFactory; /** - * In Registry pattern, objects of a single class are stored and provide a global point of access to them. - * Note that there is no restriction on the number of objects. - * - *

    The given example {@link CustomerRegistry} represents the registry used to store and - * access {@link Customer} objects.

    + * In Registry pattern, objects of a single class are stored and provide a global point of access to + * them. Note that there is no restriction on the number of objects. + * + *

    The given example {@link CustomerRegistry} represents the registry used to store and access + * {@link Customer} objects. */ public class App { @@ -54,5 +54,4 @@ public static void main(String[] args) { LOGGER.info("John {}", customerRegistry.getCustomer("1")); LOGGER.info("Julia {}", customerRegistry.getCustomer("2")); } - } diff --git a/registry/src/main/java/com/iluwatar/registry/Customer.java b/registry/src/main/java/com/iluwatar/registry/Customer.java index 75dd42a5f027..8998624ab326 100644 --- a/registry/src/main/java/com/iluwatar/registry/Customer.java +++ b/registry/src/main/java/com/iluwatar/registry/Customer.java @@ -24,16 +24,11 @@ */ package com.iluwatar.registry; -/** - * Customer entity used in registry pattern example. - */ +/** Customer entity used in registry pattern example. */ public record Customer(String id, String name) { @Override public String toString() { - return "Customer{" - + "id='" + id + '\'' - + ", name='" + name + '\'' - + '}'; + return "Customer{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } } diff --git a/registry/src/main/java/com/iluwatar/registry/CustomerRegistry.java b/registry/src/main/java/com/iluwatar/registry/CustomerRegistry.java index ff56099111d5..4a3c8b2d690b 100644 --- a/registry/src/main/java/com/iluwatar/registry/CustomerRegistry.java +++ b/registry/src/main/java/com/iluwatar/registry/CustomerRegistry.java @@ -28,13 +28,10 @@ import java.util.concurrent.ConcurrentHashMap; import lombok.Getter; -/** - * CustomerRegistry class used to store/access {@link Customer} objects. - */ +/** CustomerRegistry class used to store/access {@link Customer} objects. */ public final class CustomerRegistry { - @Getter - private static final CustomerRegistry instance = new CustomerRegistry(); + @Getter private static final CustomerRegistry instance = new CustomerRegistry(); private final Map customerMap; @@ -49,5 +46,4 @@ public Customer addCustomer(Customer customer) { public Customer getCustomer(String id) { return customerMap.get(id); } - } diff --git a/registry/src/test/java/com/iluwatar/registry/CustomerRegistryTest.java b/registry/src/test/java/com/iluwatar/registry/CustomerRegistryTest.java index af0697c458c9..48edaa6e26f4 100644 --- a/registry/src/test/java/com/iluwatar/registry/CustomerRegistryTest.java +++ b/registry/src/test/java/com/iluwatar/registry/CustomerRegistryTest.java @@ -24,13 +24,13 @@ */ package com.iluwatar.registry; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + class CustomerRegistryTest { private static CustomerRegistry customerRegistry; diff --git a/repository/README.md b/repository/README.md index e2be527a2bcb..f29d0c98e61b 100644 --- a/repository/README.md +++ b/repository/README.md @@ -29,6 +29,10 @@ In plain words > Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer. +Sequence diagram + +![Repository sequence diagram](./etc/repository-sequence-diagram.png) + ## Programmatic Example of Repository Pattern in Java Let's first look at the person entity that we need to persist. diff --git a/repository/etc/repository-sequence-diagram.png b/repository/etc/repository-sequence-diagram.png new file mode 100644 index 000000000000..45eae098780d Binary files /dev/null and b/repository/etc/repository-sequence-diagram.png differ diff --git a/repository/pom.xml b/repository/pom.xml index 6bd1b9a6ab0c..108c64a68b95 100644 --- a/repository/pom.xml +++ b/repository/pom.xml @@ -33,26 +33,15 @@ 1.26.0-SNAPSHOT repository - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.2.3 - import - - - org.hibernate - hibernate-core - 6.4.4.Final - - - + + org.springframework.boot + spring-boot-starter + org.springframework.data spring-data-jpa + 3.4.4 org.hibernate @@ -74,14 +63,22 @@ jakarta.xml.bind jakarta.xml.bind-api + 4.0.2 jakarta.annotation jakarta.annotation-api + 2.1.1 org.springframework.boot spring-boot-starter-test + test + + + org.hibernate + hibernate-core + 6.4.4.Final diff --git a/repository/src/main/java/com/iluwatar/repository/App.java b/repository/src/main/java/com/iluwatar/repository/App.java index d7aba4dee8e0..53d4b04cc685 100644 --- a/repository/src/main/java/com/iluwatar/repository/App.java +++ b/repository/src/main/java/com/iluwatar/repository/App.java @@ -101,6 +101,5 @@ public static void main(String[] args) { repository.deleteAll(); context.close(); - } } diff --git a/repository/src/main/java/com/iluwatar/repository/AppConfig.java b/repository/src/main/java/com/iluwatar/repository/AppConfig.java index 1ba9d5eaf7c6..121399fcdaf0 100644 --- a/repository/src/main/java/com/iluwatar/repository/AppConfig.java +++ b/repository/src/main/java/com/iluwatar/repository/AppConfig.java @@ -60,9 +60,7 @@ public DataSource dataSource() { return basicDataSource; } - /** - * Factory to create a specific instance of Entity Manager. - */ + /** Factory to create a specific instance of Entity Manager. */ @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { var entityManager = new LocalContainerEntityManagerFactoryBean(); @@ -73,9 +71,7 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory() { return entityManager; } - /** - * Properties for Jpa. - */ + /** Properties for Jpa. */ private static Properties jpaProperties() { var properties = new Properties(); properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); @@ -83,9 +79,7 @@ private static Properties jpaProperties() { return properties; } - /** - * Get transaction manager. - */ + /** Get transaction manager. */ @Bean public JpaTransactionManager transactionManager() { var transactionManager = new JpaTransactionManager(); @@ -145,7 +139,5 @@ public static void main(String[] args) { persons.stream().map(Person::toString).forEach(LOGGER::info); context.close(); - } - } diff --git a/repository/src/main/java/com/iluwatar/repository/Person.java b/repository/src/main/java/com/iluwatar/repository/Person.java index 923fd8f25fec..107b557a7e5f 100644 --- a/repository/src/main/java/com/iluwatar/repository/Person.java +++ b/repository/src/main/java/com/iluwatar/repository/Person.java @@ -33,9 +33,7 @@ import lombok.Setter; import lombok.ToString; -/** - * Person entity. - */ +/** Person entity. */ @ToString @EqualsAndHashCode @Setter @@ -44,20 +42,15 @@ @NoArgsConstructor public class Person { - @Id - @GeneratedValue - private Long id; + @Id @GeneratedValue private Long id; private String name; private String surname; private int age; - /** - * Constructor. - */ + /** Constructor. */ public Person(String name, String surname, int age) { this.name = name; this.surname = surname; this.age = age; } - } diff --git a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java index a82b14630d18..f67e618b6cba 100644 --- a/repository/src/main/java/com/iluwatar/repository/PersonRepository.java +++ b/repository/src/main/java/com/iluwatar/repository/PersonRepository.java @@ -29,9 +29,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; -/** - * Person repository. - */ +/** Person repository. */ @Repository public interface PersonRepository extends CrudRepository, JpaSpecificationExecutor { diff --git a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java index ae0d6e864c20..193409d87be4 100644 --- a/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java +++ b/repository/src/main/java/com/iluwatar/repository/PersonSpecifications.java @@ -30,14 +30,10 @@ import jakarta.persistence.criteria.Root; import org.springframework.data.jpa.domain.Specification; -/** - * Helper class, includes vary Specification as the abstraction of sql query criteria. - */ +/** Helper class, includes vary Specification as the abstraction of sql query criteria. */ public class PersonSpecifications { - /** - * Specifications stating the Between (From - To) Age Specification. - */ + /** Specifications stating the Between (From - To) Age Specification. */ public static class AgeBetweenSpec implements Specification { private final int from; @@ -53,12 +49,9 @@ public AgeBetweenSpec(int from, int to) { public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return cb.between(root.get("age"), from, to); } - } - /** - * Name specification. - */ + /** Name specification. */ public static class NameEqualSpec implements Specification { public final String name; @@ -67,12 +60,9 @@ public NameEqualSpec(String name) { this.name = name; } - /** - * Get predicate. - */ + /** Get predicate. */ public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return cb.equal(root.get("name"), this.name); } } - } diff --git a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java index 1843a42cc1fa..66d5d9916b3f 100644 --- a/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AnnotationBasedRepositoryTest.java @@ -28,8 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.List; import jakarta.annotation.Resource; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,8 +45,7 @@ @SpringBootTest(classes = {AppConfig.class}) class AnnotationBasedRepositoryTest { - @Resource - private PersonRepository repository; + @Resource private PersonRepository repository; private final Person peter = new Person("Peter", "Sagan", 17); private final Person nasta = new Person("Nasta", "Kuzminova", 25); @@ -55,9 +54,7 @@ class AnnotationBasedRepositoryTest { private final List persons = List.of(peter, nasta, john, terry); - /** - * Prepare data for test - */ + /** Prepare data for test */ @BeforeEach void setup() { repository.saveAll(persons); @@ -114,5 +111,4 @@ void testFindOneByNameEqualSpec() { void cleanup() { repository.deleteAll(); } - } diff --git a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java index 66c7adc0732c..27b18d9e0ef6 100644 --- a/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AppConfigTest.java @@ -36,27 +36,20 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.annotation.Transactional; -/** - * This case is Just for test the Annotation Based configuration - */ +/** This case is Just for test the Annotation Based configuration */ @ExtendWith(SpringExtension.class) @SpringBootTest(classes = {AppConfig.class}) class AppConfigTest { - @Autowired - DataSource dataSource; + @Autowired DataSource dataSource; - /** - * Test for bean instance - */ + /** Test for bean instance */ @Test void testDataSource() { assertNotNull(dataSource); } - /** - * Test for correct query execution - */ + /** Test for correct query execution */ @Test @Transactional void testQuery() throws SQLException { diff --git a/repository/src/test/java/com/iluwatar/repository/AppTest.java b/repository/src/test/java/com/iluwatar/repository/AppTest.java index e9687dd3866d..600085930762 100644 --- a/repository/src/test/java/com/iluwatar/repository/AppTest.java +++ b/repository/src/test/java/com/iluwatar/repository/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.repository; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Repository example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Repository example runs without errors. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java index 745fc9bc599b..226e3745c76d 100644 --- a/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java +++ b/repository/src/test/java/com/iluwatar/repository/RepositoryTest.java @@ -28,8 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.List; import jakarta.annotation.Resource; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,8 +45,7 @@ @SpringBootTest(properties = {"locations=classpath:applicationContext.xml"}) class RepositoryTest { - @Resource - private PersonRepository repository; + @Resource private PersonRepository repository; private final Person peter = new Person("Peter", "Sagan", 17); private final Person nasta = new Person("Nasta", "Kuzminova", 25); @@ -55,9 +54,7 @@ class RepositoryTest { private final List persons = List.of(peter, nasta, john, terry); - /** - * Prepare data for test - */ + /** Prepare data for test */ @BeforeEach void setup() { repository.saveAll(persons); @@ -114,5 +111,4 @@ void testFindOneByNameEqualSpec() { void cleanup() { repository.deleteAll(); } - } diff --git a/resource-acquisition-is-initialization/README.md b/resource-acquisition-is-initialization/README.md index 5c6105dce96c..9d140ebba41d 100644 --- a/resource-acquisition-is-initialization/README.md +++ b/resource-acquisition-is-initialization/README.md @@ -33,6 +33,10 @@ Wikipedia says > Resource acquisition is initialization (RAII) is a programming idiom used in several object-oriented, statically typed programming languages to describe a particular language behavior. Resource allocation (or acquisition) is done during object creation (specifically initialization), by the constructor, while resource deallocation (release) is done during object destruction (specifically finalization), by the destructor. +Sequence diagram + +![Resource Acquisition Is Initialization sequence diagram](./etc/raii-sequence-diagram.png) + ## Programmatic Example of RAII Pattern in Java The RAII pattern is a common idiom used in software design where the acquisition of a resource is done during object creation (initialization), and the release of the resource is done during object destruction. This pattern is particularly useful in dealing with resource leaks and is critical in writing exception-safe code in C++. In Java, RAII is achieved with try-with-resources statement and interfaces `java.io.Closeable` and `AutoCloseable`. diff --git a/resource-acquisition-is-initialization/etc/raii-sequence-diagram.png b/resource-acquisition-is-initialization/etc/raii-sequence-diagram.png new file mode 100644 index 000000000000..212ecdc9e596 Binary files /dev/null and b/resource-acquisition-is-initialization/etc/raii-sequence-diagram.png differ diff --git a/resource-acquisition-is-initialization/pom.xml b/resource-acquisition-is-initialization/pom.xml index c8061093a0e9..465f7316e30a 100644 --- a/resource-acquisition-is-initialization/pom.xml +++ b/resource-acquisition-is-initialization/pom.xml @@ -34,6 +34,14 @@ resource-acquisition-is-initialization + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java index 49b1d48d109b..d2bb59613452 100644 --- a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java +++ b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/App.java @@ -48,9 +48,7 @@ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { try (var ignored = new SlidingDoor()) { diff --git a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/SlidingDoor.java b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/SlidingDoor.java index f29bdefba554..8d4347df640c 100644 --- a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/SlidingDoor.java +++ b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/SlidingDoor.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * SlidingDoor resource. - */ +/** SlidingDoor resource. */ @Slf4j public class SlidingDoor implements AutoCloseable { diff --git a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/TreasureChest.java b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/TreasureChest.java index 4085ee6515ad..5c71261d28f8 100644 --- a/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/TreasureChest.java +++ b/resource-acquisition-is-initialization/src/main/java/com/iluwatar/resource/acquisition/is/initialization/TreasureChest.java @@ -27,9 +27,7 @@ import java.io.Closeable; import lombok.extern.slf4j.Slf4j; -/** - * TreasureChest resource. - */ +/** TreasureChest resource. */ @Slf4j public class TreasureChest implements Closeable { diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java index 06d8af8235ba..dbd0979a053f 100644 --- a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.resource.acquisition.is.initialization; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java index fae377358514..0b4c42949b08 100644 --- a/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java +++ b/resource-acquisition-is-initialization/src/test/java/com/iluwatar/resource/acquisition/is/initialization/ClosableTest.java @@ -36,10 +36,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * ClosableTest - * - */ +/** ClosableTest */ class ClosableTest { private InMemoryAppender appender; @@ -56,7 +53,8 @@ void tearDown() { @Test void testOpenClose() { - try (final var ignored = new SlidingDoor(); final var ignored1 = new TreasureChest()) { + try (final var ignored = new SlidingDoor(); + final var ignored1 = new TreasureChest()) { assertTrue(appender.logContains("Sliding door opens.")); assertTrue(appender.logContains("Treasure chest opens.")); } @@ -64,9 +62,7 @@ void testOpenClose() { assertTrue(appender.logContains("Sliding door closes.")); } - /** - * Logging Appender Implementation - */ + /** Logging Appender Implementation */ static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); diff --git a/retry/README.md b/retry/README.md index 193866e436b5..cb221f77c7f7 100644 --- a/retry/README.md +++ b/retry/README.md @@ -34,6 +34,10 @@ In plain words > Enable an application to handle transient failures when it tries to connect to a service or network resource, by transparently retrying a failed operation. This can improve the stability of the application. +Flowchart + +![Retry flowchart](./etc/retry-flowchart.png) + ## Programmatic Example of Retry Pattern in Java The Retry design pattern is a resilience pattern that allows an application to transparently attempt to execute operations multiple times in the expectation that it'll succeed. This pattern is particularly useful when the application is connecting to a network service or a remote resource, where temporary failures are common. diff --git a/retry/etc/retry-flowchart.png b/retry/etc/retry-flowchart.png new file mode 100644 index 000000000000..30b3262e6a73 Binary files /dev/null and b/retry/etc/retry-flowchart.png differ diff --git a/retry/pom.xml b/retry/pom.xml index 331feb316b2e..ba2248eeb7a6 100644 --- a/retry/pom.xml +++ b/retry/pom.xml @@ -35,6 +35,14 @@ retry jar + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -43,6 +51,7 @@ org.hamcrest hamcrest-core + 3.0 test diff --git a/retry/src/main/java/com/iluwatar/retry/App.java b/retry/src/main/java/com/iluwatar/retry/App.java index 0d64239dc801..2fdcbdc922f4 100644 --- a/retry/src/main/java/com/iluwatar/retry/App.java +++ b/retry/src/main/java/com/iluwatar/retry/App.java @@ -37,19 +37,19 @@ * operations that our application performs involving remote systems. The calling code should remain * decoupled from implementations. * - *

    {@link FindCustomer} is a business operation that looks up a customer's record and returns - * its ID. Imagine its job is performed by looking up the customer in our local database and - * returning its ID. We can pass {@link CustomerNotFoundException} as one of its {@link + *

    {@link FindCustomer} is a business operation that looks up a customer's record and returns its + * ID. Imagine its job is performed by looking up the customer in our local database and returning + * its ID. We can pass {@link CustomerNotFoundException} as one of its {@link * FindCustomer#FindCustomer(java.lang.String, com.iluwatar.retry.BusinessException...) constructor * parameters} in order to simulate not finding the customer. * *

    Imagine that, lately, this operation has experienced intermittent failures due to some weird * corruption and/or locking in the data. After retrying a few times the customer is found. The - * database is still, however, expected to always be available. While a definitive solution is - * found to the problem, our engineers advise us to retry the operation a set number of times with a - * set delay between retries, although not too many retries otherwise the end user will be left - * waiting for a long time, while delays that are too short will not allow the database to recover - * from the load. + * database is still, however, expected to always be available. While a definitive solution is found + * to the problem, our engineers advise us to retry the operation a set number of times with a set + * delay between retries, although not too many retries otherwise the end user will be left waiting + * for a long time, while delays that are too short will not allow the database to recover from the + * load. * *

    To keep the calling code as decoupled as possible from this workaround, we have implemented * the retry mechanism as a {@link BusinessOperation} named {@link Retry}. @@ -91,32 +91,34 @@ private static void errorNoRetry() throws Exception { } private static void errorWithRetry() throws Exception { - final var retry = new Retry<>( - new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), - 3, //3 attempts - 100, //100 ms delay between attempts - e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) - ); + final var retry = + new Retry<>( + new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), + 3, // 3 attempts + 100, // 100 ms delay between attempts + e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())); op = retry; final var customerId = op.perform(); - LOG.info(String.format( - "However, retrying the operation while ignoring a recoverable error will eventually yield " - + "the result %s after a number of attempts %s", customerId, retry.attempts() - )); + LOG.info( + String.format( + "However, retrying the operation while ignoring a recoverable error will eventually yield " + + "the result %s after a number of attempts %s", + customerId, retry.attempts())); } private static void errorWithRetryExponentialBackoff() throws Exception { - final var retry = new RetryExponentialBackoff<>( - new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), - 6, //6 attempts - 30000, //30 s max delay between attempts - e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass()) - ); + final var retry = + new RetryExponentialBackoff<>( + new FindCustomer("123", new CustomerNotFoundException(NOT_FOUND)), + 6, // 6 attempts + 30000, // 30 s max delay between attempts + e -> CustomerNotFoundException.class.isAssignableFrom(e.getClass())); op = retry; final var customerId = op.perform(); - LOG.info(String.format( - "However, retrying the operation while ignoring a recoverable error will eventually yield " - + "the result %s after a number of attempts %s", customerId, retry.attempts() - )); + LOG.info( + String.format( + "However, retrying the operation while ignoring a recoverable error will eventually yield " + + "the result %s after a number of attempts %s", + customerId, retry.attempts())); } } diff --git a/retry/src/main/java/com/iluwatar/retry/BusinessException.java b/retry/src/main/java/com/iluwatar/retry/BusinessException.java index c652d48cb802..e4912c69aefb 100644 --- a/retry/src/main/java/com/iluwatar/retry/BusinessException.java +++ b/retry/src/main/java/com/iluwatar/retry/BusinessException.java @@ -31,11 +31,9 @@ * occurred. Its use is reserved as a "catch-all" for cases where no other subtype captures the * specificity of the error condition in question. Calling code is not expected to be able to handle * this error and should be reported to the maintainers immediately. - * */ public class BusinessException extends Exception { - @Serial - private static final long serialVersionUID = 6235833142062144336L; + @Serial private static final long serialVersionUID = 6235833142062144336L; /** * Ctor. diff --git a/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java b/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java index f6a61ecde0be..1e7edb34515b 100644 --- a/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java +++ b/retry/src/main/java/com/iluwatar/retry/BusinessOperation.java @@ -37,7 +37,7 @@ public interface BusinessOperation { * * @return the return value * @throws BusinessException if the operation fails. Implementations are allowed to throw more - * specific subtypes depending on the error conditions + * specific subtypes depending on the error conditions */ T perform() throws BusinessException; } diff --git a/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java b/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java index b469d9e7cf33..222b251c3b9c 100644 --- a/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java +++ b/retry/src/main/java/com/iluwatar/retry/CustomerNotFoundException.java @@ -32,12 +32,10 @@ *

    The severity of this error is bounded by its context: was the search for the customer * triggered by an input from some end user, or were the search parameters pulled from your * database? - * */ public final class CustomerNotFoundException extends BusinessException { - @Serial - private static final long serialVersionUID = -6972888602621778664L; + @Serial private static final long serialVersionUID = -6972888602621778664L; /** * Ctor. diff --git a/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java b/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java index 16d75b74dce7..24a71a22df35 100644 --- a/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java +++ b/retry/src/main/java/com/iluwatar/retry/DatabaseNotAvailableException.java @@ -26,13 +26,9 @@ import java.io.Serial; -/** - * Catastrophic error indicating that we have lost connection to our database. - * - */ +/** Catastrophic error indicating that we have lost connection to our database. */ public final class DatabaseNotAvailableException extends BusinessException { - @Serial - private static final long serialVersionUID = -3750769625095997799L; + @Serial private static final long serialVersionUID = -3750769625095997799L; /** * Ctor. diff --git a/retry/src/main/java/com/iluwatar/retry/FindCustomer.java b/retry/src/main/java/com/iluwatar/retry/FindCustomer.java index 1dde9d5616e7..b827935dea7e 100644 --- a/retry/src/main/java/com/iluwatar/retry/FindCustomer.java +++ b/retry/src/main/java/com/iluwatar/retry/FindCustomer.java @@ -34,13 +34,13 @@ *

    This is an imaginary operation that, for some imagined input, returns the ID for a customer. * However, this is a "flaky" operation that is supposed to fail intermittently, but for the * purposes of this example it fails in a programmed way depending on the constructor parameters. - * */ - -public record FindCustomer(String customerId, Deque errors) implements BusinessOperation { +public record FindCustomer(String customerId, Deque errors) + implements BusinessOperation { public FindCustomer(String customerId, BusinessException... errors) { this(customerId, new ArrayDeque<>(List.of(errors))); } + @Override public String perform() throws BusinessException { if (!this.errors.isEmpty()) { diff --git a/retry/src/main/java/com/iluwatar/retry/Retry.java b/retry/src/main/java/com/iluwatar/retry/Retry.java index ad9580454993..fb0e6c6c8502 100644 --- a/retry/src/main/java/com/iluwatar/retry/Retry.java +++ b/retry/src/main/java/com/iluwatar/retry/Retry.java @@ -47,19 +47,15 @@ public final class Retry implements BusinessOperation { /** * Ctor. * - * @param op the {@link BusinessOperation} to retry + * @param op the {@link BusinessOperation} to retry * @param maxAttempts number of times to retry - * @param delay delay (in milliseconds) between attempts + * @param delay delay (in milliseconds) between attempts * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions - * will be ignored if no tests are given + * will be ignored if no tests are given */ @SafeVarargs public Retry( - BusinessOperation op, - int maxAttempts, - long delay, - Predicate... ignoreTests - ) { + BusinessOperation op, int maxAttempts, long delay, Predicate... ignoreTests) { this.op = op; this.maxAttempts = maxAttempts; this.delay = delay; @@ -101,7 +97,7 @@ public T perform() throws BusinessException { try { Thread.sleep(this.delay); } catch (InterruptedException f) { - //ignore + // ignore } } } while (true); diff --git a/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java b/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java index 1661095b7298..024097b6b5c7 100644 --- a/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java +++ b/retry/src/main/java/com/iluwatar/retry/RetryExponentialBackoff.java @@ -49,18 +49,17 @@ public final class RetryExponentialBackoff implements BusinessOperation { /** * Ctor. * - * @param op the {@link BusinessOperation} to retry + * @param op the {@link BusinessOperation} to retry * @param maxAttempts number of times to retry * @param ignoreTests tests to check whether the remote exception can be ignored. No exceptions - * will be ignored if no tests are given + * will be ignored if no tests are given */ @SafeVarargs public RetryExponentialBackoff( BusinessOperation op, int maxAttempts, long maxDelay, - Predicate... ignoreTests - ) { + Predicate... ignoreTests) { this.op = op; this.maxAttempts = maxAttempts; this.maxDelay = maxDelay; @@ -104,7 +103,7 @@ public T perform() throws BusinessException { var delay = Math.min(testDelay, this.maxDelay); Thread.sleep(delay); } catch (InterruptedException f) { - //ignore + // ignore } } } while (true); diff --git a/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java b/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java index 3abd4f4e306a..95ddba4bdd72 100644 --- a/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java +++ b/retry/src/test/java/com/iluwatar/retry/FindCustomerTest.java @@ -30,22 +30,15 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link FindCustomer}. - * - */ +/** Unit tests for {@link FindCustomer}. */ class FindCustomerTest { - /** - * Returns the given result with no exceptions. - */ + /** Returns the given result with no exceptions. */ @Test void noExceptions() throws Exception { assertThat(new FindCustomer("123").perform(), is("123")); } - /** - * Throws the given exception. - */ + /** Throws the given exception. */ @Test void oneException() { var findCustomer = new FindCustomer("123", new BusinessException("test")); @@ -59,20 +52,20 @@ void oneException() { */ @Test void resultAfterExceptions() throws Exception { - final var op = new FindCustomer( - "123", - new CustomerNotFoundException("not found"), - new DatabaseNotAvailableException("not available") - ); + final var op = + new FindCustomer( + "123", + new CustomerNotFoundException("not found"), + new DatabaseNotAvailableException("not available")); try { op.perform(); } catch (CustomerNotFoundException e) { - //ignore + // ignore } try { op.perform(); } catch (DatabaseNotAvailableException e) { - //ignore + // ignore } assertThat(op.perform(), is("123")); diff --git a/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java b/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java index 085a1bd447e1..ceec4f523b77 100644 --- a/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java +++ b/retry/src/test/java/com/iluwatar/retry/RetryExponentialBackoffTest.java @@ -30,28 +30,23 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link Retry}. - * - */ +/** Unit tests for {@link Retry}. */ class RetryExponentialBackoffTest { - /** - * Should contain all errors thrown. - */ + /** Should contain all errors thrown. */ @Test void errors() { final var e = new BusinessException("unhandled"); - final var retry = new RetryExponentialBackoff( - () -> { - throw e; - }, - 2, - 0 - ); + final var retry = + new RetryExponentialBackoff( + () -> { + throw e; + }, + 2, + 0); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.errors(), hasItem(e)); @@ -64,17 +59,17 @@ void errors() { @Test void attempts() { final var e = new BusinessException("unhandled"); - final var retry = new RetryExponentialBackoff( - () -> { - throw e; - }, - 2, - 0 - ); + final var retry = + new RetryExponentialBackoff( + () -> { + throw e; + }, + 2, + 0); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.attempts(), is(1)); @@ -87,18 +82,18 @@ void attempts() { @Test void ignore() { final var e = new CustomerNotFoundException("customer not found"); - final var retry = new RetryExponentialBackoff( - () -> { - throw e; - }, - 2, - 0, - ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass()) - ); + final var retry = + new RetryExponentialBackoff( + () -> { + throw e; + }, + 2, + 0, + ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass())); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.attempts(), is(2)); diff --git a/retry/src/test/java/com/iluwatar/retry/RetryTest.java b/retry/src/test/java/com/iluwatar/retry/RetryTest.java index b400ef5e04c2..c7f753ccbf31 100644 --- a/retry/src/test/java/com/iluwatar/retry/RetryTest.java +++ b/retry/src/test/java/com/iluwatar/retry/RetryTest.java @@ -30,29 +30,24 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for {@link Retry}. - * - */ +/** Unit tests for {@link Retry}. */ class RetryTest { - /** - * Should contain all errors thrown. - */ + /** Should contain all errors thrown. */ @Test void errors() { final var e = new BusinessException("unhandled"); - final var retry = new Retry( - () -> { - throw e; - }, - 2, - 0 - ); + final var retry = + new Retry( + () -> { + throw e; + }, + 2, + 0); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.errors(), hasItem(e)); @@ -65,17 +60,17 @@ void errors() { @Test void attempts() { final var e = new BusinessException("unhandled"); - final var retry = new Retry( - () -> { - throw e; - }, - 2, - 0 - ); + final var retry = + new Retry( + () -> { + throw e; + }, + 2, + 0); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.attempts(), is(1)); @@ -88,21 +83,20 @@ void attempts() { @Test void ignore() { final var e = new CustomerNotFoundException("customer not found"); - final var retry = new Retry( - () -> { - throw e; - }, - 2, - 0, - ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass()) - ); + final var retry = + new Retry( + () -> { + throw e; + }, + 2, + 0, + ex -> CustomerNotFoundException.class.isAssignableFrom(ex.getClass())); try { retry.perform(); } catch (BusinessException ex) { - //ignore + // ignore } assertThat(retry.attempts(), is(2)); } - -} \ No newline at end of file +} diff --git a/role-object/README.md b/role-object/README.md index 98b29bb284fb..b4d79cc1f6da 100644 --- a/role-object/README.md +++ b/role-object/README.md @@ -32,6 +32,10 @@ wiki.c2.com says > Adapt an object to different client’s needs through transparently attached role objects, each one representing a role the object has to play in that client’s context. The object manages its role set dynamically. By representing roles as individual objects, different contexts are kept separate and system configuration is simplified. +Sequence diagram + +![Role Object sequence diagram](./etc/role-object-sequence-diagram.png) + ## Programmatic Example of Role Object Pattern in Java The Role Object design pattern is a pattern that suggests modeling context-specific views of an object as separate role objects. These role objects are dynamically attached to and removed from the core object. The resulting composite object structure, consisting of the core and its role objects, is called a subject. A subject often plays several roles and the same role is likely to be played by different subjects. diff --git a/role-object/etc/role-object-sequence-diagram.png b/role-object/etc/role-object-sequence-diagram.png new file mode 100644 index 000000000000..a63319d80114 Binary files /dev/null and b/role-object/etc/role-object-sequence-diagram.png differ diff --git a/role-object/pom.xml b/role-object/pom.xml index 066c1bfb67cf..3c75482e8e9a 100644 --- a/role-object/pom.xml +++ b/role-object/pom.xml @@ -34,6 +34,14 @@ role-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java b/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java index 178ac9e6b044..125fc24af6c8 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/ApplicationRoleObject.java @@ -39,22 +39,22 @@ * customer-specific roles is provided by {@link CustomerRole}, which also supports the {@link * Customer} interface. * - *

    The {@link CustomerRole} class is abstract and not meant to be instantiated. - * Concrete subclasses of {@link CustomerRole}, for example {@link BorrowerRole} or {@link - * InvestorRole}, define and implement the interface for specific roles. It is only these subclasses - * which are instantiated at runtime. The {@link BorrowerRole} class defines the context-specific - * view of {@link Customer} objects as needed by the loan department. It defines additional - * operations to manage the customer’s credits and securities. Similarly, the {@link InvestorRole} - * class adds operations specific to the investment department’s view of customers. A client like - * the loan application may either work with objects of the {@link CustomerRole} class, using the - * interface class {@link Customer}, or with objects of concrete {@link CustomerRole} subclasses. - * Suppose the loan application knows a particular {@link Customer} instance through its {@link - * Customer} interface. The loan application may want to check whether the {@link Customer} object - * plays the role of Borrower. To this end it calls {@link Customer#hasRole(Role)} with a suitable - * role specification. For the purpose of our example, let’s assume we can name roles with enum. If - * the {@link Customer} object can play the role named “Borrower,” the loan application will ask it - * to return a reference to the corresponding object. The loan application may now use this - * reference to call Borrower-specific operations. + *

    The {@link CustomerRole} class is abstract and not meant to be instantiated. Concrete + * subclasses of {@link CustomerRole}, for example {@link BorrowerRole} or {@link InvestorRole}, + * define and implement the interface for specific roles. It is only these subclasses which are + * instantiated at runtime. The {@link BorrowerRole} class defines the context-specific view of + * {@link Customer} objects as needed by the loan department. It defines additional operations to + * manage the customer’s credits and securities. Similarly, the {@link InvestorRole} class adds + * operations specific to the investment department’s view of customers. A client like the loan + * application may either work with objects of the {@link CustomerRole} class, using the interface + * class {@link Customer}, or with objects of concrete {@link CustomerRole} subclasses. Suppose the + * loan application knows a particular {@link Customer} instance through its {@link Customer} + * interface. The loan application may want to check whether the {@link Customer} object plays the + * role of Borrower. To this end it calls {@link Customer#hasRole(Role)} with a suitable role + * specification. For the purpose of our example, let’s assume we can name roles with enum. If the + * {@link Customer} object can play the role named “Borrower,” the loan application will ask it to + * return a reference to the corresponding object. The loan application may now use this reference + * to call Borrower-specific operations. */ @Slf4j public class ApplicationRoleObject { @@ -74,20 +74,23 @@ public static void main(String[] args) { var hasInvestorRole = customer.hasRole(INVESTOR); LOGGER.info("Customer has an investor role - {}", hasInvestorRole); - customer.getRole(INVESTOR, InvestorRole.class) - .ifPresent(inv -> { - inv.setAmountToInvest(1000); - inv.setName("Billy"); - }); - customer.getRole(BORROWER, BorrowerRole.class) - .ifPresent(inv -> inv.setName("Johny")); + customer + .getRole(INVESTOR, InvestorRole.class) + .ifPresent( + inv -> { + inv.setAmountToInvest(1000); + inv.setName("Billy"); + }); + customer.getRole(BORROWER, BorrowerRole.class).ifPresent(inv -> inv.setName("Johny")); - customer.getRole(INVESTOR, InvestorRole.class) + customer + .getRole(INVESTOR, InvestorRole.class) .map(InvestorRole::invest) .ifPresent(LOGGER::info); - customer.getRole(BORROWER, BorrowerRole.class) + customer + .getRole(BORROWER, BorrowerRole.class) .map(BorrowerRole::borrow) .ifPresent(LOGGER::info); } -} \ No newline at end of file +} diff --git a/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java b/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java index 5d0b8111cbde..172e9d489388 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/BorrowerRole.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.Setter; -/** - * Borrower role. - */ +/** Borrower role. */ @Getter @Setter public class BorrowerRole extends CustomerRole { @@ -39,5 +37,4 @@ public class BorrowerRole extends CustomerRole { public String borrow() { return String.format("Borrower %s wants to get some money.", name); } - } diff --git a/role-object/src/main/java/com/iluwatar/roleobject/Customer.java b/role-object/src/main/java/com/iluwatar/roleobject/Customer.java index 573604cbc3ef..1b5d15592229 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/Customer.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/Customer.java @@ -27,9 +27,7 @@ import java.util.Arrays; import java.util.Optional; -/** - * The main abstraction to work with Customer. - */ +/** The main abstraction to work with Customer. */ public abstract class Customer { /** @@ -46,7 +44,6 @@ public abstract class Customer { * @param role to check * @return true if the role exists otherwise false */ - public abstract boolean hasRole(Role role); /** @@ -60,13 +57,12 @@ public abstract class Customer { /** * Get specific instance associated with this role @see {@link Role}. * - * @param role to get + * @param role to get * @param expectedRole instance class expected to get * @return optional with value if the instance exists and corresponds expected class */ public abstract Optional getRole(Role role, Class expectedRole); - public static Customer newCustomer() { return new CustomerCore(); } @@ -82,5 +78,4 @@ public static Customer newCustomer(Role... role) { Arrays.stream(role).forEach(customer::addRole); return customer; } - } diff --git a/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java b/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java index 04652039feff..079f6cb5f34b 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/CustomerCore.java @@ -45,12 +45,12 @@ public CustomerCore() { @Override public boolean addRole(Role role) { - return role - .instance() - .map(inst -> { - roles.put(role, inst); - return true; - }) + return role.instance() + .map( + inst -> { + roles.put(role, inst); + return true; + }) .orElse(false); } @@ -66,8 +66,7 @@ public boolean remRole(Role role) { @Override public Optional getRole(Role role, Class expectedRole) { - return Optional - .ofNullable(roles.get(role)) + return Optional.ofNullable(roles.get(role)) .filter(expectedRole::isInstance) .map(expectedRole::cast); } diff --git a/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java b/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java index cb8ad7bb8496..044c80cf8a3a 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/CustomerRole.java @@ -24,8 +24,5 @@ */ package com.iluwatar.roleobject; -/** - * Key abstraction for segregated roles. - */ -public abstract class CustomerRole extends CustomerCore { -} +/** Key abstraction for segregated roles. */ +public abstract class CustomerRole extends CustomerCore {} diff --git a/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java index 1390d72f6076..ef8cb73ae3e8 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/InvestorRole.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.Setter; -/** - * Investor role. - */ +/** Investor role. */ @Getter @Setter public class InvestorRole extends CustomerRole { diff --git a/role-object/src/main/java/com/iluwatar/roleobject/Role.java b/role-object/src/main/java/com/iluwatar/roleobject/Role.java index 7a681f02a406..98ef516b3ec1 100644 --- a/role-object/src/main/java/com/iluwatar/roleobject/Role.java +++ b/role-object/src/main/java/com/iluwatar/roleobject/Role.java @@ -29,12 +29,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Possible roles. - */ +/** Possible roles. */ public enum Role { - - BORROWER(BorrowerRole.class), INVESTOR(InvestorRole.class); + BORROWER(BorrowerRole.class), + INVESTOR(InvestorRole.class); private final Class typeCst; @@ -44,18 +42,18 @@ public enum Role { private static final Logger logger = LoggerFactory.getLogger(Role.class); - /** - * Get instance. - */ + /** Get instance. */ @SuppressWarnings("unchecked") public Optional instance() { var typeCst = this.typeCst; try { return (Optional) Optional.of(typeCst.getDeclaredConstructor().newInstance()); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + } catch (InstantiationException + | IllegalAccessException + | NoSuchMethodException + | InvocationTargetException e) { logger.error("error creating an object", e); } return Optional.empty(); } - -} \ No newline at end of file +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java b/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java index 7dab58c7e2e8..aa5be60bb4dc 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/ApplicationRoleObjectTest.java @@ -32,6 +32,6 @@ class ApplicationRoleObjectTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> ApplicationRoleObject.main(new String[]{})); + assertDoesNotThrow(() -> ApplicationRoleObject.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java b/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java index 95e6c6a4e190..df366db16f1b 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/BorrowerRoleTest.java @@ -36,4 +36,4 @@ void borrowTest() { borrowerRole.setName("test"); assertEquals("Borrower test wants to get some money.", borrowerRole.borrow()); } -} \ No newline at end of file +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java b/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java index 9cb10b8e1519..cd90277096bb 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/CustomerCoreTest.java @@ -88,5 +88,4 @@ void toStringTest() { core = new CustomerCore(); assertEquals("Customer{roles=[]}", core.toString()); } - -} \ No newline at end of file +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java b/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java index 14609c6f8283..79b26ced83e7 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/InvestorRoleTest.java @@ -37,4 +37,4 @@ void investTest() { investorRole.setAmountToInvest(10); assertEquals("Investor test has invested 10 dollars", investorRole.invest()); } -} \ No newline at end of file +} diff --git a/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java b/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java index 503685180260..825dc6fea095 100644 --- a/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java +++ b/role-object/src/test/java/com/iluwatar/roleobject/RoleTest.java @@ -37,4 +37,4 @@ void instanceTest() { assertTrue(instance.isPresent()); assertEquals(instance.get().getClass(), BorrowerRole.class); } -} \ No newline at end of file +} diff --git a/saga/README.md b/saga/README.md index 47709dd68ba4..d715f026af67 100644 --- a/saga/README.md +++ b/saga/README.md @@ -31,6 +31,10 @@ Wikipedia says > Long-running transactions (also known as the saga interaction pattern) are computer database transactions that avoid locks on non-local resources, use compensation to handle failures, potentially aggregate smaller ACID transactions (also referred to as atomic transactions), and typically use a coordinator to complete or abort the transaction. In contrast to rollback in ACID transactions, compensation restores the original state, or an equivalent, and is business-specific. For example, the compensating action for making a hotel reservation is canceling that reservation. +Flowchart + +![Saga flowchart](./etc/saga-flowchart.png) + ## Programmatic Example of Saga Pattern in Java The Saga design pattern is a sequence of local transactions where each transaction updates data within a single service. It's particularly useful in a microservices architecture where each service has its own database. The Saga pattern ensures data consistency and fault tolerance across services. Here are the key components of the Saga pattern: diff --git a/saga/etc/saga-flowchart.png b/saga/etc/saga-flowchart.png new file mode 100644 index 000000000000..5bbffc8f2d0b Binary files /dev/null and b/saga/etc/saga-flowchart.png differ diff --git a/saga/pom.xml b/saga/pom.xml index 6ca621c868cf..7697d9fbc53b 100644 --- a/saga/pom.xml +++ b/saga/pom.xml @@ -34,6 +34,14 @@ saga + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java b/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java index 5839773a4a21..8f46ff63248a 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/ChoreographyChapter.java @@ -24,7 +24,6 @@ */ package com.iluwatar.saga.choreography; - /** * ChoreographyChapter is an interface representing a contract for an external service. In that * case, a service needs to make a decision what to do further hence the server needs to get all @@ -62,6 +61,4 @@ public interface ChoreographyChapter { * @return result {@link Saga} */ Saga rollback(Saga saga); - - } diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java b/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java index 9140cd0527fa..491b4a590672 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/FlyBookingService.java @@ -24,10 +24,7 @@ */ package com.iluwatar.saga.choreography; - -/** - * Class representing a service to book a fly. - */ +/** Class representing a service to book a fly. */ public class FlyBookingService extends Service { public FlyBookingService(ServiceDiscoveryService service) { super(service); diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java b/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java index 280c0f05b597..3f44fbd3ea76 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/HotelBookingService.java @@ -24,10 +24,7 @@ */ package com.iluwatar.saga.choreography; - -/** - * Class representing a service to book a hotel. - */ +/** Class representing a service to book a hotel. */ public class HotelBookingService extends Service { public HotelBookingService(ServiceDiscoveryService service) { super(service); @@ -37,6 +34,4 @@ public HotelBookingService(ServiceDiscoveryService service) { public String getName() { return "booking a Hotel"; } - - } diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java b/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java index 55778bb1d048..36b8717c32c6 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/OrderService.java @@ -24,10 +24,7 @@ */ package com.iluwatar.saga.choreography; - -/** - * Class representing a service to init a new order. - */ +/** Class representing a service to init a new order. */ public class OrderService extends Service { public OrderService(ServiceDiscoveryService service) { diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java index c04a63dfdd35..4b690c3c2067 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/Saga.java @@ -41,7 +41,6 @@ public class Saga { private boolean forward; private boolean finished; - public static Saga create() { return new Saga(); } @@ -53,9 +52,7 @@ public static Saga create() { */ public SagaResult getResult() { if (finished) { - return forward - ? SagaResult.FINISHED - : SagaResult.ROLLBACKED; + return forward ? SagaResult.FINISHED : SagaResult.ROLLBACKED; } return SagaResult.PROGRESS; @@ -130,7 +127,6 @@ int back() { return --pos; } - private Saga() { this.chapters = new ArrayList<>(); this.pos = 0; @@ -142,7 +138,6 @@ Chapter getCurrent() { return chapters.get(pos); } - boolean isPresent() { return pos >= 0 && pos < chapters.size(); } @@ -156,13 +151,9 @@ boolean isCurrentSuccess() { * outcoming parameter). */ public static class Chapter { - @Getter - private final String name; - @Setter - private ChapterResult result; - @Getter - @Setter - private Object inValue; + @Getter private final String name; + @Setter private ChapterResult result; + @Getter @Setter private Object inValue; public Chapter(String name) { this.name = name; @@ -179,19 +170,18 @@ public boolean isSuccess() { } } - - /** - * result for chapter. - */ + /** result for chapter. */ public enum ChapterResult { - INIT, SUCCESS, ROLLBACK + INIT, + SUCCESS, + ROLLBACK } - /** - * result for saga. - */ + /** result for saga. */ public enum SagaResult { - PROGRESS, FINISHED, ROLLBACKED + PROGRESS, + FINISHED, + ROLLBACKED } @Override diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java b/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java index 86758da00067..09d68f4292d8 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/SagaApplication.java @@ -31,12 +31,12 @@ * an analog of transaction in a database but in terms of microservices architecture this is * executed in a distributed environment * - *

    A saga is a sequence of local transactions in a certain context. - * If one transaction fails for some reason, the saga executes compensating transactions(rollbacks) - * to undo the impact of the preceding transactions. + *

    A saga is a sequence of local transactions in a certain context. If one transaction fails for + * some reason, the saga executes compensating transactions(rollbacks) to undo the impact of the + * preceding transactions. * - *

    In this approach, there are no mediators or orchestrators services. - * All chapters are handled and moved by services manually. + *

    In this approach, there are no mediators or orchestrators services. All chapters are handled + * and moved by services manually. * *

    The major difference with choreography saga is an ability to handle crashed services * (otherwise in choreography services very hard to prevent a saga if one of them has been crashed) @@ -47,24 +47,22 @@ @Slf4j public class SagaApplication { - /** - * main method. - */ + /** main method. */ public static void main(String[] args) { var sd = serviceDiscovery(); var service = sd.findAny(); var goodOrderSaga = service.execute(newSaga("good_order")); var badOrderSaga = service.execute(newSaga("bad_order")); - LOGGER.info("orders: goodOrder is {}, badOrder is {}", - goodOrderSaga.getResult(), badOrderSaga.getResult()); - + LOGGER.info( + "orders: goodOrder is {}, badOrder is {}", + goodOrderSaga.getResult(), + badOrderSaga.getResult()); } - private static Saga newSaga(Object value) { - return Saga - .create() - .chapter("init an order").setInValue(value) + return Saga.create() + .chapter("init an order") + .setInValue(value) .chapter("booking a Fly") .chapter("booking a Hotel") .chapter("withdrawing Money"); @@ -72,8 +70,7 @@ private static Saga newSaga(Object value) { private static ServiceDiscoveryService serviceDiscovery() { var sd = new ServiceDiscoveryService(); - return sd - .discover(new OrderService(sd)) + return sd.discover(new OrderService(sd)) .discover(new FlyBookingService(sd)) .discover(new HotelBookingService(sd)) .discover(new WithdrawMoneyService(sd)); diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/Service.java b/saga/src/main/java/com/iluwatar/saga/choreography/Service.java index 3cd7b84ff2b2..6510b830c8fd 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/Service.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/Service.java @@ -28,7 +28,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** * Common abstraction class representing services. implementing a general contract @see {@link * ChoreographyChapter} @@ -70,21 +69,24 @@ public Saga execute(Saga saga) { } var finalNextSaga = nextSaga; - return sd.find(chapterName).map(ch -> ch.execute(finalNextSaga)) + return sd.find(chapterName) + .map(ch -> ch.execute(finalNextSaga)) .orElseThrow(serviceNotFoundException(chapterName)); } private Supplier serviceNotFoundException(String chServiceName) { - return () -> new RuntimeException( - String.format("the service %s has not been found", chServiceName)); + return () -> + new RuntimeException(String.format("the service %s has not been found", chServiceName)); } @Override public Saga process(Saga saga) { var inValue = saga.getCurrentValue(); - LOGGER.info("The chapter '{}' has been started. " + LOGGER.info( + "The chapter '{}' has been started. " + "The data {} has been stored or calculated successfully", - getName(), inValue); + getName(), + inValue); saga.setCurrentStatus(Saga.ChapterResult.SUCCESS); saga.setCurrentValue(inValue); return saga; @@ -93,9 +95,11 @@ public Saga process(Saga saga) { @Override public Saga rollback(Saga saga) { var inValue = saga.getCurrentValue(); - LOGGER.info("The Rollback for a chapter '{}' has been started. " + LOGGER.info( + "The Rollback for a chapter '{}' has been started. " + "The data {} has been rollbacked successfully", - getName(), inValue); + getName(), + inValue); saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); saga.setCurrentValue(inValue); @@ -110,5 +114,4 @@ private boolean isSagaFinished(Saga saga) { } return false; } - } diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java index d159ea453893..49cc7c92fbf2 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/ServiceDiscoveryService.java @@ -29,9 +29,7 @@ import java.util.NoSuchElementException; import java.util.Optional; -/** - * The class representing a service discovery pattern. - */ +/** The class representing a service discovery pattern. */ public class ServiceDiscoveryService { private final Map services; @@ -57,6 +55,4 @@ public ServiceDiscoveryService discover(ChoreographyChapter chapterService) { public ServiceDiscoveryService() { this.services = new HashMap<>(); } - - } diff --git a/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java b/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java index c9b370898016..095170fb800d 100644 --- a/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java +++ b/saga/src/main/java/com/iluwatar/saga/choreography/WithdrawMoneyService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.saga.choreography; -/** - * Class representing a service to withdraw a money. - */ +/** Class representing a service to withdraw a money. */ public class WithdrawMoneyService extends Service { public WithdrawMoneyService(ServiceDiscoveryService service) { @@ -43,7 +41,8 @@ public Saga process(Saga saga) { var inValue = saga.getCurrentValue(); if (inValue.equals("bad_order")) { - LOGGER.info("The chapter '{}' has been started. But the exception has been raised." + LOGGER.info( + "The chapter '{}' has been started. But the exception has been raised." + "The rollback is about to start", getName()); saga.setCurrentStatus(Saga.ChapterResult.ROLLBACK); diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java index a348b3a721ab..9790373f6275 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ChapterResult.java @@ -32,8 +32,7 @@ * @param incoming value */ public class ChapterResult { - @Getter - private final K value; + @Getter private final K value; private final State state; ChapterResult(K value, State state) { @@ -53,10 +52,9 @@ public static ChapterResult failure(K val) { return new ChapterResult<>(val, State.FAILURE); } - /** - * state for chapter. - */ + /** state for chapter. */ public enum State { - SUCCESS, FAILURE + SUCCESS, + FAILURE } } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java index 1540b4afba3b..19a0fc1f8d5b 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/FlyBookingService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.saga.orchestration; -/** - * Class representing a service to book a fly. - */ +/** Class representing a service to book a fly. */ public class FlyBookingService extends Service { @Override public String getName() { diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java index aa3a9266e957..ebd602032c83 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/HotelBookingService.java @@ -24,29 +24,30 @@ */ package com.iluwatar.saga.orchestration; -/** - * Class representing a service to book a hotel. - */ +/** Class representing a service to book a hotel. */ public class HotelBookingService extends Service { @Override public String getName() { return "booking a Hotel"; } - @Override public ChapterResult rollback(String value) { if (value.equals("crashed_order")) { - LOGGER.info("The Rollback for a chapter '{}' has been started. " + LOGGER.info( + "The Rollback for a chapter '{}' has been started. " + "The data {} has been failed.The saga has been crashed.", - getName(), value); + getName(), + value); return ChapterResult.failure(value); } - LOGGER.info("The Rollback for a chapter '{}' has been started. " + LOGGER.info( + "The Rollback for a chapter '{}' has been started. " + "The data {} has been rollbacked successfully", - getName(), value); + getName(), + value); return super.rollback(value); } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java b/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java index 1197de110587..537e5af14e0d 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/OrchestrationChapter.java @@ -53,5 +53,4 @@ public interface OrchestrationChapter { * @return result {@link ChapterResult} */ ChapterResult rollback(K value); - } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java index 67d0f9089d68..2f1432d39ed3 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/OrderService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.saga.orchestration; -/** - * Class representing a service to init a new order. - */ +/** Class representing a service to init a new order. */ public class OrderService extends Service { @Override public String getName() { diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java index 9057a23e38a4..b89886c4aef0 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Saga.java @@ -37,18 +37,15 @@ public class Saga { private final List chapters; - private Saga() { this.chapters = new ArrayList<>(); } - public Saga chapter(String name) { this.chapters.add(new Chapter(name)); return this; } - public Chapter get(int idx) { return chapters.get(idx); } @@ -57,21 +54,18 @@ public boolean isPresent(int idx) { return idx >= 0 && idx < chapters.size(); } - public static Saga create() { return new Saga(); } - /** - * result for saga. - */ + /** result for saga. */ public enum Result { - FINISHED, ROLLBACK, CRASHED + FINISHED, + ROLLBACK, + CRASHED } - /** - * class represents chapter name. - */ + /** class represents chapter name. */ @AllArgsConstructor @Getter public static class Chapter { diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java index 2d5a748d94f4..200a73b8af4d 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaApplication.java @@ -31,15 +31,14 @@ * an analog of transaction in a database but in terms of microservices architecture this is * executed in a distributed environment * - *

    A saga is a sequence of local transactions in a certain context. - * If one transaction fails for some reason, the saga executes compensating transactions(rollbacks) - * to undo the impact of the preceding transactions. + *

    A saga is a sequence of local transactions in a certain context. If one transaction fails for + * some reason, the saga executes compensating transactions(rollbacks) to undo the impact of the + * preceding transactions. * - *

    In this approach, there is an orchestrator @see {@link SagaOrchestrator} - * that manages all the transactions and directs the participant services to execute local - * transactions based on events. The major difference with choreography saga is an ability to handle - * crashed services (otherwise in choreography services very hard to prevent a saga if one of them - * has been crashed) + *

    In this approach, there is an orchestrator @see {@link SagaOrchestrator} that manages all the + * transactions and directs the participant services to execute local transactions based on events. + * The major difference with choreography saga is an ability to handle crashed services (otherwise + * in choreography services very hard to prevent a saga if one of them has been crashed) * * @see Saga * @see SagaOrchestrator @@ -48,9 +47,7 @@ @Slf4j public class SagaApplication { - /** - * method to show common saga logic. - */ + /** method to show common saga logic. */ public static void main(String[] args) { var sagaOrchestrator = new SagaOrchestrator(newSaga(), serviceDiscovery()); @@ -58,14 +55,15 @@ public static void main(String[] args) { Saga.Result badOrder = sagaOrchestrator.execute("bad_order"); Saga.Result crashedOrder = sagaOrchestrator.execute("crashed_order"); - LOGGER.info("orders: goodOrder is {}, badOrder is {},crashedOrder is {}", - goodOrder, badOrder, crashedOrder); + LOGGER.info( + "orders: goodOrder is {}, badOrder is {},crashedOrder is {}", + goodOrder, + badOrder, + crashedOrder); } - private static Saga newSaga() { - return Saga - .create() + return Saga.create() .chapter("init an order") .chapter("booking a Fly") .chapter("booking a Hotel") diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java index 88128879ad76..2cfe2acc0f80 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/SagaOrchestrator.java @@ -31,7 +31,6 @@ import lombok.extern.slf4j.Slf4j; - /** * The orchestrator that manages all the transactions and directs the participant services to * execute local transactions based on events. @@ -42,12 +41,11 @@ public class SagaOrchestrator { private final ServiceDiscoveryService sd; private final CurrentState state; - /** * Create a new service to orchetrate sagas. * * @param saga saga to process - * @param sd service discovery @see {@link ServiceDiscoveryService} + * @param sd service discovery @see {@link ServiceDiscoveryService} */ public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) { this.saga = saga; @@ -59,7 +57,7 @@ public SagaOrchestrator(Saga saga, ServiceDiscoveryService sd) { * pipeline to execute saga process/story. * * @param value incoming value - * @param type for incoming value + * @param type for incoming value * @return result @see {@link Result} */ @SuppressWarnings("unchecked") @@ -101,15 +99,12 @@ public Result execute(K value) { } } - if (!saga.isPresent(next)) { return state.isForward() ? FINISHED : result == CRASHED ? CRASHED : ROLLBACK; } } - } - private static class CurrentState { int currentNumber; boolean isForward; @@ -124,7 +119,6 @@ void cleanUp() { this.isForward = true; } - boolean isForward() { return isForward; } @@ -145,5 +139,4 @@ int current() { return currentNumber; } } - } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java b/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java index 6fc74d1943cf..00ec6d18e036 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/Service.java @@ -39,22 +39,23 @@ public abstract class Service implements OrchestrationChapter { @Override public abstract String getName(); - @Override public ChapterResult process(K value) { - LOGGER.info("The chapter '{}' has been started. " + LOGGER.info( + "The chapter '{}' has been started. " + "The data {} has been stored or calculated successfully", - getName(), value); + getName(), + value); return ChapterResult.success(value); } @Override public ChapterResult rollback(K value) { - LOGGER.info("The Rollback for a chapter '{}' has been started. " + LOGGER.info( + "The Rollback for a chapter '{}' has been started. " + "The data {} has been rollbacked successfully", - getName(), value); + getName(), + value); return ChapterResult.success(value); } - - } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java index 19b85fd92304..face085fb451 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/ServiceDiscoveryService.java @@ -28,9 +28,7 @@ import java.util.Map; import java.util.Optional; -/** - * The class representing a service discovery pattern. - */ +/** The class representing a service discovery pattern. */ public class ServiceDiscoveryService { private final Map> services; @@ -46,6 +44,4 @@ public ServiceDiscoveryService discover(OrchestrationChapter orchestrationCha public ServiceDiscoveryService() { this.services = new HashMap<>(); } - - } diff --git a/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java index fe443cbd14b9..b6fb2a03cb54 100644 --- a/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java +++ b/saga/src/main/java/com/iluwatar/saga/orchestration/WithdrawMoneyService.java @@ -24,9 +24,7 @@ */ package com.iluwatar.saga.orchestration; -/** - * Class representing a service to withdraw a money. - */ +/** Class representing a service to withdraw a money. */ public class WithdrawMoneyService extends Service { @Override public String getName() { @@ -36,7 +34,8 @@ public String getName() { @Override public ChapterResult process(String value) { if (value.equals("bad_order") || value.equals("crashed_order")) { - LOGGER.info("The chapter '{}' has been started. But the exception has been raised." + LOGGER.info( + "The chapter '{}' has been started. But the exception has been raised." + "The rollback is about to start", getName()); return ChapterResult.failure(value); diff --git a/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java index 29f835684b46..a3d5b136e69d 100644 --- a/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java +++ b/saga/src/test/java/com/iluwatar/saga/choreography/SagaApplicationTest.java @@ -35,6 +35,6 @@ class SagaApplicationTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> SagaApplication.main(new String[]{})); + assertDoesNotThrow(() -> SagaApplication.main(new String[] {})); } } diff --git a/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java b/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java index 8154f368a8fc..14e6658f447c 100644 --- a/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java +++ b/saga/src/test/java/com/iluwatar/saga/choreography/SagaChoreographyTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.saga.choreography; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * test to check choreography saga - */ +import org.junit.jupiter.api.Test; + +/** test to check choreography saga */ class SagaChoreographyTest { @Test @@ -45,9 +43,9 @@ void executeTest() { } private static Saga newSaga(Object value) { - return Saga - .create() - .chapter("init an order").setInValue(value) + return Saga.create() + .chapter("init an order") + .setInValue(value) .chapter("booking a Fly") .chapter("booking a Hotel") .chapter("withdrawing Money"); @@ -55,8 +53,7 @@ private static Saga newSaga(Object value) { private static ServiceDiscoveryService serviceDiscovery() { var sd = new ServiceDiscoveryService(); - return sd - .discover(new OrderService(sd)) + return sd.discover(new OrderService(sd)) .discover(new FlyBookingService(sd)) .discover(new HotelBookingService(sd)) .discover(new WithdrawMoneyService(sd)); diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java index b06cb65a2ce0..a428a109e6b1 100644 --- a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaApplicationTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Test if the application starts without throwing an exception. - */ +/** Test if the application starts without throwing an exception. */ class SagaApplicationTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> SagaApplication.main(new String[]{})); + assertDoesNotThrow(() -> SagaApplication.main(new String[] {})); } } diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java index a9ff4db05edb..1270e652595e 100644 --- a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorInternallyTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.saga.orchestration; -import org.junit.jupiter.api.Test; - import static com.iluwatar.saga.orchestration.Saga.Result; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Test; -/** - * test to test orchestration logic - */ +/** test to test orchestration logic */ class SagaOrchestratorInternallyTest { private final List records = new ArrayList<>(); @@ -46,16 +43,12 @@ void executeTest() { var result = sagaOrchestrator.execute(1); assertEquals(Result.ROLLBACK, result); assertArrayEquals( - new String[]{"+1", "+2", "+3", "+4", "-4", "-3", "-2", "-1"}, - records.toArray(new String[]{})); + new String[] {"+1", "+2", "+3", "+4", "-4", "-3", "-2", "-1"}, + records.toArray(new String[] {})); } private static Saga newSaga() { - return Saga.create() - .chapter("1") - .chapter("2") - .chapter("3") - .chapter("4"); + return Saga.create().chapter("1").chapter("2").chapter("3").chapter("4"); } private ServiceDiscoveryService serviceDiscovery() { @@ -145,4 +138,4 @@ public ChapterResult rollback(Integer value) { return ChapterResult.success(value); } } -} \ No newline at end of file +} diff --git a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java index 687d772977b7..a136f90a927d 100644 --- a/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java +++ b/saga/src/test/java/com/iluwatar/saga/orchestration/SagaOrchestratorTest.java @@ -24,13 +24,11 @@ */ package com.iluwatar.saga.orchestration; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; -/** - * test to check general logic - */ +import org.junit.jupiter.api.Test; + +/** test to check general logic */ class SagaOrchestratorTest { @Test @@ -44,8 +42,7 @@ void execute() { } private static Saga newSaga() { - return Saga - .create() + return Saga.create() .chapter("init an order") .chapter("booking a Fly") .chapter("booking a Hotel") @@ -53,11 +50,10 @@ private static Saga newSaga() { } private static ServiceDiscoveryService serviceDiscovery() { - return - new ServiceDiscoveryService() - .discover(new OrderService()) - .discover(new FlyBookingService()) - .discover(new HotelBookingService()) - .discover(new WithdrawMoneyService()); + return new ServiceDiscoveryService() + .discover(new OrderService()) + .discover(new FlyBookingService()) + .discover(new HotelBookingService()) + .discover(new WithdrawMoneyService()); } -} \ No newline at end of file +} diff --git a/separated-interface/README.md b/separated-interface/README.md index 1b26f40aa255..4497e360c618 100644 --- a/separated-interface/README.md +++ b/separated-interface/README.md @@ -31,6 +31,10 @@ In plain words > Defines a client interface separate from its implementation to allow for flexible and interchangeable components. +Sequence diagram + +![Separated Interface sequence diagram](./etc/separated-interface-sequence-diagram.png) + ## Programmatic Example of Separated Interface Pattern in Java The Java Separated Interface design pattern is a crucial software architecture strategy that promotes separating the interface definition from its implementation, crucial for enhancing system flexibility and scalability. This allows the client to be completely unaware of the implementation, promoting loose coupling and enhancing flexibility. diff --git a/separated-interface/etc/separated-interface-sequence-diagram.png b/separated-interface/etc/separated-interface-sequence-diagram.png new file mode 100644 index 000000000000..162690d41176 Binary files /dev/null and b/separated-interface/etc/separated-interface-sequence-diagram.png differ diff --git a/separated-interface/pom.xml b/separated-interface/pom.xml index 1684ea1240fd..9dd5816e0414 100644 --- a/separated-interface/pom.xml +++ b/separated-interface/pom.xml @@ -34,6 +34,14 @@ separated-interface + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java index c50a5eb6cfd2..0f4cb00f845b 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/App.java @@ -30,13 +30,13 @@ import lombok.extern.slf4j.Slf4j; /** - *

    The Separated Interface pattern encourages to separate the interface definition and + * The Separated Interface pattern encourages to separate the interface definition and * implementation in different packages. This allows the client to be completely unaware of the - * implementation.

    + * implementation. * *

    In this class the {@link InvoiceGenerator} class is injected with different instances of * {@link com.iluwatar.separatedinterface.invoice.TaxCalculator} implementations located in separate - * packages, to receive different responses for both of the implementations.

    + * packages, to receive different responses for both of the implementations. */ @Slf4j public class App { @@ -49,12 +49,12 @@ public class App { * @param args command line args */ public static void main(String[] args) { - //Create the invoice generator with product cost as 50 and foreign product tax - var internationalProductInvoice = new InvoiceGenerator(PRODUCT_COST, - new ForeignTaxCalculator()); + // Create the invoice generator with product cost as 50 and foreign product tax + var internationalProductInvoice = + new InvoiceGenerator(PRODUCT_COST, new ForeignTaxCalculator()); LOGGER.info("Foreign Tax applied: {}", "" + internationalProductInvoice.getAmountWithTax()); - //Create the invoice generator with product cost as 50 and domestic product tax + // Create the invoice generator with product cost as 50 and domestic product tax var domesticProductInvoice = new InvoiceGenerator(PRODUCT_COST, new DomesticTaxCalculator()); LOGGER.info("Domestic Tax applied: {}", "" + domesticProductInvoice.getAmountWithTax()); } diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java index cdb2b6c1d18d..a3e2c8396a6d 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/InvoiceGenerator.java @@ -27,13 +27,11 @@ /** * InvoiceGenerator class generates an invoice, accepting the product cost and calculating the total * price payable inclusive tax (calculated by {@link TaxCalculator}). - * */ public record InvoiceGenerator(double amount, TaxCalculator taxCalculator) { - /** TaxCalculator description: - * The TaxCalculator interface to calculate the payable tax. - * Amount description: - * The base product amount without tax. + /** + * TaxCalculator description: The TaxCalculator interface to calculate the payable tax. Amount + * description: The base product amount without tax. */ public double getAmountWithTax() { return amount + taxCalculator.calculate(amount); diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java index 85b6e6786cfd..6132d0c48bd4 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/invoice/TaxCalculator.java @@ -24,11 +24,8 @@ */ package com.iluwatar.separatedinterface.invoice; -/** - * TaxCalculator interface to demonstrate The Separated Interface pattern. - */ +/** TaxCalculator interface to demonstrate The Separated Interface pattern. */ public interface TaxCalculator { double calculate(double amount); - } diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java index cbf9146582c8..0b372a0e110d 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculator.java @@ -26,9 +26,7 @@ import com.iluwatar.separatedinterface.invoice.TaxCalculator; -/** - * TaxCalculator for Domestic goods with 20% tax. - */ +/** TaxCalculator for Domestic goods with 20% tax. */ public class DomesticTaxCalculator implements TaxCalculator { public static final double TAX_PERCENTAGE = 20; @@ -37,5 +35,4 @@ public class DomesticTaxCalculator implements TaxCalculator { public double calculate(double amount) { return amount * TAX_PERCENTAGE / 100.0; } - } diff --git a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java index b4265a5d3792..0378c010837a 100644 --- a/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java +++ b/separated-interface/src/main/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculator.java @@ -26,9 +26,7 @@ import com.iluwatar.separatedinterface.invoice.TaxCalculator; -/** - * TaxCalculator for foreign goods with 60% tax. - */ +/** TaxCalculator for foreign goods with 60% tax. */ public class ForeignTaxCalculator implements TaxCalculator { public static final double TAX_PERCENTAGE = 60; @@ -37,5 +35,4 @@ public class ForeignTaxCalculator implements TaxCalculator { public double calculate(double amount) { return amount * TAX_PERCENTAGE / 100.0; } - } diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java index 995aabf1946d..245077b1b461 100644 --- a/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/AppTest.java @@ -24,18 +24,15 @@ */ package com.iluwatar.separatedinterface; -import org.junit.jupiter.api.Test; -import com.iluwatar.separatedinterface.App; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Application test. - */ +import org.junit.jupiter.api.Test; + +/** Application test. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java index 7772fc942b63..c0223ad258a8 100644 --- a/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/invoice/InvoiceGeneratorTest.java @@ -48,5 +48,4 @@ void testGenerateTax() { Assertions.assertEquals(target.getAmountWithTax(), productCost + tax); verify(taxCalculatorMock, times(1)).calculate(productCost); } - } diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java index be7261a2f98a..13974dc1dec9 100644 --- a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/DomesticTaxCalculatorTest.java @@ -38,5 +38,4 @@ void testTaxCalculation() { var tax = target.calculate(100.0); Assertions.assertEquals(tax, 20.0); } - } diff --git a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java index 53ba5064d0d2..1e1441f22c34 100644 --- a/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java +++ b/separated-interface/src/test/java/com/iluwatar/separatedinterface/taxes/ForeignTaxCalculatorTest.java @@ -38,5 +38,4 @@ void testTaxCalculation() { var tax = target.calculate(100.0); Assertions.assertEquals(tax, 60.0); } - } diff --git a/serialized-entity/README.md b/serialized-entity/README.md index f7306dcdfa50..70a700bb358b 100644 --- a/serialized-entity/README.md +++ b/serialized-entity/README.md @@ -32,6 +32,10 @@ Wikipedia says > In computing, serialization is the process of translating a data structure or object state into a format that can be stored (e.g. files in secondary storage devices, data buffers in primary storage devices) or transmitted (e.g. data streams over computer networks) and reconstructed later (possibly in a different computer environment). When the resulting series of bits is reread according to the serialization format, it can be used to create a semantically identical clone of the original object. For many complex objects, such as those that make extensive use of references, this process is not straightforward. Serialization of objects does not include any of their associated methods with which they were previously linked. +Flowchart + +![Serialized Entity flowchart](./etc/serialized-entity-flowchart.png) + ## Programmatic Example of Serialized Entity Pattern in Java The Serialized Entity design pattern is a way to easily persist Java objects to the database. It uses the `Serializable` interface and the DAO (Data Access Object) pattern. The pattern first uses `Serializable` to convert a Java object into a set of bytes, then it uses the DAO pattern to store this set of bytes as a BLOB (Binary Large OBject) in the database. diff --git a/serialized-entity/etc/serialized-entity-flowchart.png b/serialized-entity/etc/serialized-entity-flowchart.png new file mode 100644 index 000000000000..0f382155e9ab Binary files /dev/null and b/serialized-entity/etc/serialized-entity-flowchart.png differ diff --git a/serialized-entity/pom.xml b/serialized-entity/pom.xml index aea122f5a0ac..928e28097d73 100644 --- a/serialized-entity/pom.xml +++ b/serialized-entity/pom.xml @@ -34,6 +34,14 @@ serialized-entity + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java index 538675576e9e..c4cab51dacb1 100644 --- a/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/App.java @@ -30,21 +30,22 @@ import lombok.extern.slf4j.Slf4j; import org.h2.jdbcx.JdbcDataSource; - /** * Serialized Entity Pattern. * - *

    Serialized Entity Pattern allow us to easily persist Java objects to the database. It uses Serializable interface - * and DAO pattern. Serialized Entity Pattern will first use Serializable to convert a Java object into a set of bytes, - * then it will using DAO pattern to store this set of bytes as BLOB to database.

    - * - *

    In this example, we first initialize two Java objects (Country) "China" and "UnitedArabEmirates", then we - * initialize "serializedChina" with "China" object and "serializedUnitedArabEmirates" with "UnitedArabEmirates", - * then we use method "serializedChina.insertCountry()" and "serializedUnitedArabEmirates.insertCountry()" to serialize - * "China" and "UnitedArabEmirates" and persist them to database. - * Last, with "serializedChina.selectCountry()" and "serializedUnitedArabEmirates.selectCountry()" we could read "China" - * and "UnitedArabEmirates" from database as sets of bytes, then deserialize them back to Java object (Country).

    + *

    Serialized Entity Pattern allow us to easily persist Java objects to the database. It uses + * Serializable interface and DAO pattern. Serialized Entity Pattern will first use Serializable to + * convert a Java object into a set of bytes, then it will using DAO pattern to store this set of + * bytes as BLOB to database. * + *

    In this example, we first initialize two Java objects (Country) "China" and + * "UnitedArabEmirates", then we initialize "serializedChina" with "China" object and + * "serializedUnitedArabEmirates" with "UnitedArabEmirates", then we use method + * "serializedChina.insertCountry()" and "serializedUnitedArabEmirates.insertCountry()" to serialize + * "China" and "UnitedArabEmirates" and persist them to database. Last, with + * "serializedChina.selectCountry()" and "serializedUnitedArabEmirates.selectCountry()" we could + * read "China" and "UnitedArabEmirates" from database as sets of bytes, then deserialize them back + * to Java object (Country). */ @Slf4j public class App { @@ -55,6 +56,7 @@ private App() {} /** * Program entry point. + * * @param args command line args. * @throws IOException if any * @throws ClassNotFoundException if any @@ -66,20 +68,10 @@ public static void main(String[] args) throws IOException, ClassNotFoundExceptio createSchema(dataSource); // Initializing Country Object China - final var China = new Country( - 86, - "China", - "Asia", - "Chinese" - ); + final var China = new Country(86, "China", "Asia", "Chinese"); // Initializing Country Object UnitedArabEmirates - final var UnitedArabEmirates = new Country( - 971, - "United Arab Emirates", - "Asia", - "Arabic" - ); + final var UnitedArabEmirates = new Country(971, "United Arab Emirates", "Asia", "Arabic"); // Initializing CountrySchemaSql Object with parameter "China" and "dataSource" final var serializedChina = new CountrySchemaSql(China, dataSource); @@ -105,7 +97,7 @@ public static void main(String[] args) throws IOException, ClassNotFoundExceptio private static void deleteSchema(DataSource dataSource) { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CountrySchemaSql.DELETE_SCHEMA_SQL); } catch (SQLException e) { LOGGER.info("Exception thrown " + e.getMessage()); @@ -114,7 +106,7 @@ private static void deleteSchema(DataSource dataSource) { private static void createSchema(DataSource dataSource) { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(CountrySchemaSql.CREATE_SCHEMA_SQL); } catch (SQLException e) { LOGGER.info("Exception thrown " + e.getMessage()); @@ -126,4 +118,4 @@ private static DataSource createDataSource() { dataSource.setURL(DB_URL); return dataSource; } -} \ No newline at end of file +} diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java index f7430ef252ac..b406ab62f88b 100644 --- a/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/Country.java @@ -23,6 +23,7 @@ * THE SOFTWARE. */ package com.iluwatar.serializedentity; + import java.io.Serial; import java.io.Serializable; import lombok.AllArgsConstructor; @@ -31,9 +32,7 @@ import lombok.Setter; import lombok.ToString; -/** - * A Country POJO that represents the data that will serialize and store in database. - */ +/** A Country POJO that represents the data that will serialize and store in database. */ @Getter @Setter @EqualsAndHashCode @@ -45,7 +44,5 @@ public class Country implements Serializable { private String name; private String continents; private String language; - @Serial - private static final long serialVersionUID = 7149851; - + @Serial private static final long serialVersionUID = 7149851; } diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java index 26d912c00bd9..beffe1f4c6db 100644 --- a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountryDao.java @@ -42,10 +42,9 @@ import java.io.IOException; -/** - * DAO interface for Country transactions. - */ +/** DAO interface for Country transactions. */ public interface CountryDao { int insertCountry() throws IOException; + int selectCountry() throws IOException, ClassNotFoundException; } diff --git a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java index 971f875c6b81..2de487fa0308 100644 --- a/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java +++ b/serialized-entity/src/main/java/com/iluwatar/serializedentity/CountrySchemaSql.java @@ -35,12 +35,11 @@ import javax.sql.DataSource; import lombok.extern.slf4j.Slf4j; -/** - * Country Schema SQL Class. - */ +/** Country Schema SQL Class. */ @Slf4j public class CountrySchemaSql implements CountryDao { - public static final String CREATE_SCHEMA_SQL = "CREATE TABLE IF NOT EXISTS WORLD (ID INT PRIMARY KEY, COUNTRY BLOB)"; + public static final String CREATE_SCHEMA_SQL = + "CREATE TABLE IF NOT EXISTS WORLD (ID INT PRIMARY KEY, COUNTRY BLOB)"; public static final String DELETE_SCHEMA_SQL = "DROP TABLE WORLD IF EXISTS"; @@ -54,27 +53,26 @@ public class CountrySchemaSql implements CountryDao { * @param country country */ public CountrySchemaSql(Country country, DataSource dataSource) { - this.country = new Country( - country.getCode(), - country.getName(), - country.getContinents(), - country.getLanguage() - ); + this.country = + new Country( + country.getCode(), country.getName(), country.getContinents(), country.getLanguage()); this.dataSource = dataSource; } /** * This method will serialize a Country object and store it to database. - * @return int type, if successfully insert a serialized object to database then return country code, else return -1. + * + * @return int type, if successfully insert a serialized object to database then return country + * code, else return -1. * @throws IOException if any. */ @Override public int insertCountry() throws IOException { var sql = "INSERT INTO WORLD (ID, COUNTRY) VALUES (?, ?)"; try (var connection = dataSource.getConnection(); - var preparedStatement = connection.prepareStatement(sql); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oss = new ObjectOutputStream(baos)) { + var preparedStatement = connection.prepareStatement(sql); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oss = new ObjectOutputStream(baos)) { oss.writeObject(country); oss.flush(); @@ -91,8 +89,9 @@ public int insertCountry() throws IOException { /** * This method will select a data item from database and deserialize it. - * @return int type, if successfully select and deserialized object from database then return country code, - * else return -1. + * + * @return int type, if successfully select and deserialized object from database then return + * country code, else return -1. * @throws IOException if any. * @throws ClassNotFoundException if any. */ @@ -100,14 +99,15 @@ public int insertCountry() throws IOException { public int selectCountry() throws IOException, ClassNotFoundException { var sql = "SELECT ID, COUNTRY FROM WORLD WHERE ID = ?"; try (var connection = dataSource.getConnection(); - var preparedStatement = connection.prepareStatement(sql)) { + var preparedStatement = connection.prepareStatement(sql)) { preparedStatement.setInt(1, country.getCode()); try (ResultSet rs = preparedStatement.executeQuery()) { if (rs.next()) { Blob countryBlob = rs.getBlob("country"); - ByteArrayInputStream baos = new ByteArrayInputStream(countryBlob.getBytes(1, (int) countryBlob.length())); + ByteArrayInputStream baos = + new ByteArrayInputStream(countryBlob.getBytes(1, (int) countryBlob.length())); ObjectInputStream ois = new ObjectInputStream(baos); country = (Country) ois.readObject(); LOGGER.info("Country: " + country); @@ -119,5 +119,4 @@ public int selectCountry() throws IOException, ClassNotFoundException { } return -1; } - } diff --git a/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java b/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java index fde211a36284..ad6a9d6d18fe 100644 --- a/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java +++ b/serialized-entity/src/test/java/com/iluwatar/serializedentity/AppTest.java @@ -24,23 +24,19 @@ */ package com.iluwatar.serializedentity; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Serialized Entity example runs without errors. - */ -class AppTest { +import org.junit.jupiter.api.Test; - /** - * 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#main(String[])} - * throws an exception. - */ +/** Tests that Serialized Entity example runs without errors. */ +class AppTest { - @Test - void shouldExecuteSerializedEntityWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); - } + /** + * 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#main(String[])} throws an exception. + */ + @Test + void shouldExecuteSerializedEntityWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } } diff --git a/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java b/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java index 37ba92660b22..36d7baceb081 100644 --- a/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java +++ b/serialized-entity/src/test/java/com/iluwatar/serializedentity/CountryTest.java @@ -23,86 +23,70 @@ * THE SOFTWARE. */ package com.iluwatar.serializedentity; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; -import java.io.*; - -import static org.junit.jupiter.api.Assertions.*; - @Slf4j public class CountryTest { @Test void testGetMethod() { - Country China = new Country( - 86, - "China", - "Asia", - "Chinese" - ); + Country China = new Country(86, "China", "Asia", "Chinese"); - assertEquals(86, China.getCode()); - assertEquals("China", China.getName()); - assertEquals("Asia", China.getContinents()); - assertEquals("Chinese", China.getLanguage()); + assertEquals(86, China.getCode()); + assertEquals("China", China.getName()); + assertEquals("Asia", China.getContinents()); + assertEquals("Chinese", China.getLanguage()); } @Test void testSetMethod() { - Country country = new Country( - 86, - "China", - "Asia", - "Chinese" - ); + Country country = new Country(86, "China", "Asia", "Chinese"); - country.setCode(971); - country.setName("UAE"); - country.setContinents("West-Asia"); - country.setLanguage("Arabic"); + country.setCode(971); + country.setName("UAE"); + country.setContinents("West-Asia"); + country.setLanguage("Arabic"); - assertEquals(971, country.getCode()); - assertEquals("UAE", country.getName()); - assertEquals("West-Asia", country.getContinents()); - assertEquals("Arabic", country.getLanguage()); + assertEquals(971, country.getCode()); + assertEquals("UAE", country.getName()); + assertEquals("West-Asia", country.getContinents()); + assertEquals("Arabic", country.getLanguage()); } @Test - void testSerializable(){ - // Serializing Country - try { - Country country = new Country( - 86, - "China", - "Asia", - "Chinese"); - ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("output.txt")); - objectOutputStream.writeObject(country); - objectOutputStream.close(); - } catch (IOException e) { - LOGGER.error("Error occurred: ", e); - } + void testSerializable() { + // Serializing Country + try { + Country country = new Country(86, "China", "Asia", "Chinese"); + ObjectOutputStream objectOutputStream = + new ObjectOutputStream(new FileOutputStream("output.txt")); + objectOutputStream.writeObject(country); + objectOutputStream.close(); + } catch (IOException e) { + LOGGER.error("Error occurred: ", e); + } - // De-serialize Country - try { - ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("output.txt")); - Country country = (Country) objectInputStream.readObject(); - objectInputStream.close(); - System.out.println(country); + // De-serialize Country + try { + ObjectInputStream objectInputStream = + new ObjectInputStream(new FileInputStream("output.txt")); + Country country = (Country) objectInputStream.readObject(); + objectInputStream.close(); + System.out.println(country); - Country China = new Country( - 86, - "China", - "Asia", - "Chinese"); + Country China = new Country(86, "China", "Asia", "Chinese"); - assertEquals(China, country); - } catch (Exception e) { - LOGGER.error("Error occurred: ", e); - } + assertEquals(China, country); + } catch (Exception e) { + LOGGER.error("Error occurred: ", e); + } try { Files.deleteIfExists(Paths.get("output.txt")); } catch (IOException e) { diff --git a/serialized-lob/README.md b/serialized-lob/README.md index 06eea05f4b08..79b26ae2d184 100644 --- a/serialized-lob/README.md +++ b/serialized-lob/README.md @@ -30,6 +30,10 @@ In plain words > The Serialized LOB design pattern manages the storage of large objects, such as files or multimedia, by serializing and storing them directly within a database. +Flowchart + +![Serialized LOB flowchart](./etc/serialized-lob-flowchart.png) + ## Programmatic Example of Serialized LOB Pattern in Java The Serialized Large Object (LOB) design pattern is a way to handle large objects in a database. It involves serializing an object graph into a single large object (a BLOB or CLOB, for Binary Large Object or Character Large Object, respectively) and storing it in the database. When the object graph needs to be retrieved, it is read from the database and deserialized back into the original object graph. diff --git a/serialized-lob/etc/serialized-lob-flowchart.png b/serialized-lob/etc/serialized-lob-flowchart.png new file mode 100644 index 000000000000..075c7e238cf7 Binary files /dev/null and b/serialized-lob/etc/serialized-lob-flowchart.png differ diff --git a/serialized-lob/pom.xml b/serialized-lob/pom.xml index 60f98e98f94b..f705a848ad32 100644 --- a/serialized-lob/pom.xml +++ b/serialized-lob/pom.xml @@ -58,6 +58,14 @@ + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + junit-jupiter-engine org.junit.jupiter diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/App.java b/serialized-lob/src/main/java/com/iluwatar/slob/App.java index 743cc8293f1f..25006726ea0c 100644 --- a/serialized-lob/src/main/java/com/iluwatar/slob/App.java +++ b/serialized-lob/src/main/java/com/iluwatar/slob/App.java @@ -41,9 +41,7 @@ import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; -/** - * SLOB Application using {@link LobSerializer} and H2 DB. - */ +/** SLOB Application using {@link LobSerializer} and H2 DB. */ public class App { public static final String CLOB = "CLOB"; @@ -51,20 +49,20 @@ public class App { /** * Main entry point to program. + * *

    In the SLOB pattern, the object graph is serialized into a single large object (a BLOB or * CLOB, for Binary Large Object or Character Large Object, respectively) and stored in the * database. When the object graph needs to be retrieved, it is read from the database and - * deserialized back into the original object graph.

    + * deserialized back into the original object graph. * *

    A Forest is created using {@link #createForest()} with Animals and Plants along with their - * respective relationships.

    + * respective relationships. * - *

    Creates a {@link LobSerializer} using the method - * {@link #createLobSerializer(String[])}.

    + *

    Creates a {@link LobSerializer} using the method {@link #createLobSerializer(String[])}. * - *

    Once created the serializer is passed to the - * {@link #executeSerializer(Forest, LobSerializer)} which handles the serialization, - * deserialization and persisting and loading from DB.

    + *

    Once created the serializer is passed to the {@link #executeSerializer(Forest, + * LobSerializer)} which handles the serialization, deserialization and persisting and loading + * from DB. * * @param args if first arg is CLOB then ClobSerializer is used else BlobSerializer is used. */ @@ -75,12 +73,13 @@ public static void main(String[] args) throws SQLException { } /** - *

    Creates a {@link LobSerializer} on the basis of input args.

    - *

    If input args are not empty and the value equals {@link App#CLOB} then a - * {@link ClobSerializer} is created else a {@link BlobSerializer} is created.

    + * Creates a {@link LobSerializer} on the basis of input args. + * + *

    If input args are not empty and the value equals {@link App#CLOB} then a {@link + * ClobSerializer} is created else a {@link BlobSerializer} is created. * * @param args if first arg is {@link App#CLOB} then ClobSerializer is instantiated else - * BlobSerializer is instantiated. + * BlobSerializer is instantiated. */ private static LobSerializer createLobSerializer(String[] args) throws SQLException { LobSerializer serializer; @@ -96,14 +95,14 @@ private static LobSerializer createLobSerializer(String[] args) throws SQLExcept * Creates a Forest with {@link Animal} and {@link Plant} along with their respective * relationships. * - *

    The method creates a {@link Forest} with 2 Plants Grass and Oak of type Herb and tree - * respectively.

    + *

    The method creates a {@link Forest} with 2 Plants Grass and Oak of type Herb and tree + * respectively. * - *

    It also creates 3 animals Zebra and Buffalo which eat the plant grass. Lion consumes the - * Zebra and the Buffalo.

    + *

    It also creates 3 animals Zebra and Buffalo which eat the plant grass. Lion consumes the + * Zebra and the Buffalo. * - *

    With the above animals and plants and their relationships a forest - * object is created which represents the Object Graph.

    + *

    With the above animals and plants and their relationships a forest object is created which + * represents the Object Graph. * * @return Forest Object */ @@ -122,7 +121,7 @@ private static Forest createForest() { * Serialize the input object using the input serializer and persist to DB. After this it loads * the same object back from DB and deserializes using the same serializer. * - * @param forest Object to Serialize and Persist + * @param forest Object to Serialize and Persist * @param lobSerializer Serializer to Serialize and Deserialize Object */ private static void executeSerializer(Forest forest, LobSerializer lobSerializer) { @@ -135,9 +134,12 @@ private static void executeSerializer(Forest forest, LobSerializer lobSerializer Forest forestFromDb = serializer.deSerialize(fromDb); LOGGER.info(forestFromDb.toString()); - } catch (SQLException | IOException | TransformerException | ParserConfigurationException - | SAXException - | ClassNotFoundException e) { + } catch (SQLException + | IOException + | TransformerException + | ParserConfigurationException + | SAXException + | ClassNotFoundException e) { throw new RuntimeException(e); } } diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java b/serialized-lob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java index ea54592c99d1..10f3b262d6b2 100644 --- a/serialized-lob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java +++ b/serialized-lob/src/main/java/com/iluwatar/slob/dbservice/DatabaseService.java @@ -30,9 +30,7 @@ import lombok.extern.slf4j.Slf4j; import org.h2.jdbcx.JdbcDataSource; -/** - * Service to handle database operations. - */ +/** Service to handle database operations. */ @Slf4j public class DatabaseService { @@ -73,8 +71,7 @@ private static DataSource createDataSource() { * * @throws SQLException if any issue occurs while executing DROP Query */ - public void shutDownService() - throws SQLException { + public void shutDownService() throws SQLException { try (var connection = dataSource.getConnection(); var statement = connection.createStatement()) { statement.execute(DELETE_SCHEMA_SQL); @@ -82,17 +79,16 @@ public void shutDownService() } /** - * Initaites startup sequence and executes the query - * {@link DatabaseService#CREATE_BINARY_SCHEMA_DDL} if {@link DatabaseService#dataTypeDb} is - * binary else will execute the query {@link DatabaseService#CREATE_TEXT_SCHEMA_DDL}. + * Initaites startup sequence and executes the query {@link + * DatabaseService#CREATE_BINARY_SCHEMA_DDL} if {@link DatabaseService#dataTypeDb} is binary else + * will execute the query {@link DatabaseService#CREATE_TEXT_SCHEMA_DDL}. * * @throws SQLException if there are any issues during DDL execution */ - public void startupService() - throws SQLException { + public void startupService() throws SQLException { try (var connection = dataSource.getConnection(); var statement = connection.createStatement()) { - if (dataTypeDb.equals("BINARY")) { + if (dataTypeDb.equals(BINARY_DATA)) { statement.execute(CREATE_BINARY_SCHEMA_DDL); } else { statement.execute(CREATE_TEXT_SCHEMA_DDL); @@ -103,14 +99,13 @@ public void startupService() /** * Executes the insert query {@link DatabaseService#INSERT}. * - * @param id with which row is to be inserted + * @param id with which row is to be inserted * @param name name to be added in the row * @param data object data to be saved in the row - * @throws SQLException if there are any issues in executing insert query - * {@link DatabaseService#INSERT} + * @throws SQLException if there are any issues in executing insert query {@link + * DatabaseService#INSERT} */ - public void insert(int id, String name, Object data) - throws SQLException { + public void insert(int id, String name, Object data) throws SQLException { try (var connection = dataSource.getConnection(); var insert = connection.prepareStatement(INSERT)) { insert.setInt(1, id); @@ -121,22 +116,20 @@ public void insert(int id, String name, Object data) } /** - * Runs the select query {@link DatabaseService#SELECT} form the result set returns an - * {@link java.io.InputStream} if {@link DatabaseService#dataTypeDb} is 'binary' else will return - * the object as a {@link String}. + * Runs the select query {@link DatabaseService#SELECT} form the result set returns an {@link + * java.io.InputStream} if {@link DatabaseService#dataTypeDb} is 'binary' else will return the + * object as a {@link String}. * - * @param id with which row is to be selected + * @param id with which row is to be selected * @param columnsName column in which the object is stored * @return object found from DB - * @throws SQLException if there are any issues in executing insert query * - * {@link DatabaseService#SELECT} + * @throws SQLException if there are any issues in executing insert query * {@link + * DatabaseService#SELECT} */ public Object select(final long id, String columnsName) throws SQLException { ResultSet resultSet = null; try (var connection = dataSource.getConnection(); - var preparedStatement = - connection.prepareStatement(SELECT) - ) { + var preparedStatement = connection.prepareStatement(SELECT)) { Object result = null; preparedStatement.setLong(1, id); resultSet = preparedStatement.executeQuery(); diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/lob/Animal.java b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Animal.java index 770543cc5cf3..17caa41e96aa 100644 --- a/serialized-lob/src/main/java/com/iluwatar/slob/lob/Animal.java +++ b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Animal.java @@ -35,9 +35,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; -/** - * Creates an object Animal with a list of animals and/or plants it consumes. - */ +/** Creates an object Animal with a list of animals and/or plants it consumes. */ @Data @AllArgsConstructor @NoArgsConstructor @@ -51,12 +49,12 @@ public class Animal implements Serializable { * Iterates over the input nodes recursively and adds new plants to {@link Animal#plantsEaten} or * animals to {@link Animal#animalsEaten} found to input sets respectively. * - * @param childNodes contains the XML Node containing the Forest + * @param childNodes contains the XML Node containing the Forest * @param animalsEaten set of Animals eaten - * @param plantsEaten set of Plants eaten + * @param plantsEaten set of Plants eaten */ - protected static void iterateXmlForAnimalAndPlants(NodeList childNodes, Set animalsEaten, - Set plantsEaten) { + protected static void iterateXmlForAnimalAndPlants( + NodeList childNodes, Set animalsEaten, Set plantsEaten) { for (int i = 0; i < childNodes.getLength(); i++) { Node child = childNodes.item(i); if (child.getNodeType() == Node.ELEMENT_NODE) { diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/lob/Forest.java b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Forest.java index 1c14d55debd8..844e3c6bd84f 100644 --- a/serialized-lob/src/main/java/com/iluwatar/slob/lob/Forest.java +++ b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Forest.java @@ -46,7 +46,7 @@ @NoArgsConstructor @AllArgsConstructor public class Forest implements Serializable { - + public static final String HORIZONTAL_DIVIDER = "\n--------------------------\n"; private String name; private Set animals = new HashSet<>(); private Set plants = new HashSet<>(); @@ -105,16 +105,16 @@ public String toString() { sb.append("Forest Name = ").append(name).append("\n"); sb.append("Animals found in the ").append(name).append(" Forest: \n"); for (Animal animal : animals) { - sb.append("\n--------------------------\n"); + sb.append(HORIZONTAL_DIVIDER); sb.append(animal.toString()); - sb.append("\n--------------------------\n"); + sb.append(HORIZONTAL_DIVIDER); } sb.append("\n"); sb.append("Plants in the ").append(name).append(" Forest: \n"); for (Plant plant : plants) { - sb.append("\n--------------------------\n"); + sb.append(HORIZONTAL_DIVIDER); sb.append(plant.toString()); - sb.append("\n--------------------------\n"); + sb.append(HORIZONTAL_DIVIDER); } return sb.toString(); } diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/lob/Plant.java b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Plant.java index 20aff543eacb..f41a8b67c525 100644 --- a/serialized-lob/src/main/java/com/iluwatar/slob/lob/Plant.java +++ b/serialized-lob/src/main/java/com/iluwatar/slob/lob/Plant.java @@ -34,9 +34,7 @@ import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; -/** - * Creates an object Plant which contains its name and type. - */ +/** Creates an object Plant which contains its name and type. */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java index c3858a84a9b6..f9fe3a7a7e70 100644 --- a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java +++ b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/BlobSerializer.java @@ -69,7 +69,7 @@ public Object serialize(Forest toSerialize) throws IOException { * @param toDeserialize Input Object to De-serialize * @return Deserialized Object * @throws ClassNotFoundException {@inheritDoc} - * @throws IOException {@inheritDoc} + * @throws IOException {@inheritDoc} */ @Override public Forest deSerialize(Object toDeserialize) throws IOException, ClassNotFoundException { diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java index 1734447194d0..827477c301b7 100644 --- a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java +++ b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/ClobSerializer.java @@ -76,7 +76,7 @@ private static String elementToXmlString(Element node) throws TransformerExcepti * @param forest Object which is to be serialized * @return Serialized object * @throws ParserConfigurationException If any issues occur in parsing input object - * @throws TransformerException If any issues occur in Transformation from Node to XML + * @throws TransformerException If any issues occur in Transformation from Node to XML */ @Override public Object serialize(Forest forest) throws ParserConfigurationException, TransformerException { @@ -90,14 +90,14 @@ public Object serialize(Forest forest) throws ParserConfigurationException, Tran * @param toDeserialize Input Object to De-serialize * @return Deserialized Object * @throws ParserConfigurationException If any issues occur in parsing input object - * @throws IOException if any issues occur during reading object - * @throws SAXException If any issues occur in Transformation from Node to XML + * @throws IOException if any issues occur during reading object + * @throws SAXException If any issues occur in Transformation from Node to XML */ @Override public Forest deSerialize(Object toDeserialize) throws ParserConfigurationException, IOException, SAXException { - DocumentBuilder documentBuilder = DocumentBuilderFactory.newDefaultInstance() - .newDocumentBuilder(); + DocumentBuilder documentBuilder = + DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder(); var stream = new ByteArrayInputStream(toDeserialize.toString().getBytes()); Document parsed = documentBuilder.parse(stream); Forest forest = new Forest(); diff --git a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java index 54e9c8ba52ec..c97246f332e4 100644 --- a/serialized-lob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java +++ b/serialized-lob/src/main/java/com/iluwatar/slob/serializers/LobSerializer.java @@ -60,8 +60,8 @@ protected LobSerializer(String dataTypeDb) throws SQLException { * @param toSerialize Input Object to serialize * @return Serialized Object * @throws ParserConfigurationException if any issue occurs during parsing of input object - * @throws TransformerException if any issue occurs during Transformation - * @throws IOException if any issues occur during reading object + * @throws TransformerException if any issue occurs during Transformation + * @throws IOException if any issues occur during reading object */ public abstract Object serialize(Forest toSerialize) throws ParserConfigurationException, TransformerException, IOException; @@ -69,8 +69,8 @@ public abstract Object serialize(Forest toSerialize) /** * Saves the object to DB with the provided ID. * - * @param id key to be sent to DB service - * @param name Object name to store in DB + * @param id key to be sent to DB service + * @param name Object name to store in DB * @param object Object to store in DB * @return ID with which the object is stored in DB * @throws SQLException if any issue occurs while saving to DB @@ -83,7 +83,7 @@ public int persistToDb(int id, String name, Object object) throws SQLException { /** * Loads the object from db using the ID and column name. * - * @param id to query the DB + * @param id to query the DB * @param columnName column from which object is to be extracted * @return Object from DB * @throws SQLException if any issue occurs while loading from DB @@ -98,8 +98,8 @@ public Object loadFromDb(int id, String columnName) throws SQLException { * @param toDeserialize object to deserialize * @return Deserialized Object * @throws ParserConfigurationException If issue occurs during parsing of input object - * @throws IOException if any issues occur during reading object - * @throws SAXException if any issues occur during reading object for XML parsing + * @throws IOException if any issues occur during reading object + * @throws SAXException if any issues occur during reading object for XML parsing */ public abstract Forest deSerialize(Object toDeserialize) throws ParserConfigurationException, IOException, SAXException, ClassNotFoundException; diff --git a/serialized-lob/src/test/java/com/iluwatar/slob/AppTest.java b/serialized-lob/src/test/java/com/iluwatar/slob/AppTest.java index f8332a128020..8d87608a27e0 100644 --- a/serialized-lob/src/test/java/com/iluwatar/slob/AppTest.java +++ b/serialized-lob/src/test/java/com/iluwatar/slob/AppTest.java @@ -43,20 +43,20 @@ import org.junit.jupiter.api.Test; import org.xml.sax.SAXException; -/** - * SLOB Application test - */ +/** SLOB Application test */ @Slf4j class AppTest { /** * Creates a Forest with Animals and Plants along with their respective relationships. - *

    The method creates a forest with 2 Plants Grass and Oak of type Herb and tree - * respectively.

    - *

    It also creates 3 animals Zebra and Buffalo which eat the plant grass. Lion consumes the - * Zebra and the Buffalo.

    - *

    With the above animals and plants and their relationships a forest - * object is created which represents the Object Graph.

    + * + *

    The method creates a forest with 2 Plants Grass and Oak of type Herb and tree respectively. + * + *

    It also creates 3 animals Zebra and Buffalo which eat the plant grass. Lion consumes the + * Zebra and the Buffalo. + * + *

    With the above animals and plants and their relationships a forest object is created which + * represents the Object Graph. * * @return Forest Object */ @@ -72,28 +72,30 @@ private static Forest createForest() { } /** - * Tests the {@link App} without passing any argument in the args to test the - * {@link ClobSerializer}. + * Tests the {@link App} without passing any argument in the args to test the {@link + * ClobSerializer}. */ @Test void shouldExecuteWithoutExceptionClob() { - assertDoesNotThrow(() -> App.main(new String[]{"CLOB"})); + assertDoesNotThrow(() -> App.main(new String[] {"CLOB"})); } /** - * Tests the {@link App} without passing any argument in the args to test the - * {@link BlobSerializer}. + * Tests the {@link App} without passing any argument in the args to test the {@link + * BlobSerializer}. */ @Test void shouldExecuteWithoutExceptionBlob() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } /** * Tests the serialization of the input object using the {@link ClobSerializer} and persists the * serialized object to DB, then load the object back from DB and deserializes it using the - * provided {@link ClobSerializer}.

    After loading the object back from DB the test matches the - * hash of the input object with the hash of the object that was loaded from DB and deserialized. + * provided {@link ClobSerializer}. + * + *

    After loading the object back from DB the test matches the hash of the input object with the + * hash of the object that was loaded from DB and deserialized. */ @Test void clobSerializerTest() { @@ -106,11 +108,16 @@ void clobSerializerTest() { Object fromDb = serializer.loadFromDb(id, Forest.class.getSimpleName()); Forest forestFromDb = serializer.deSerialize(fromDb); - Assertions.assertEquals(forest.hashCode(), forestFromDb.hashCode(), + Assertions.assertEquals( + forest.hashCode(), + forestFromDb.hashCode(), "Hashes of objects after Serializing and Deserializing are the same"); - } catch (SQLException | IOException | TransformerException | ParserConfigurationException | - SAXException | - ClassNotFoundException e) { + } catch (SQLException + | IOException + | TransformerException + | ParserConfigurationException + | SAXException + | ClassNotFoundException e) { throw new RuntimeException(e); } } @@ -118,8 +125,10 @@ void clobSerializerTest() { /** * Tests the serialization of the input object using the {@link BlobSerializer} and persists the * serialized object to DB, then loads the object back from DB and deserializes it using the - * {@link BlobSerializer}.

    After loading the object back from DB the test matches the hash of - * the input object with the hash of the object that was loaded from DB and deserialized. + * {@link BlobSerializer}. + * + *

    After loading the object back from DB the test matches the hash of the input object with the + * hash of the object that was loaded from DB and deserialized. */ @Test void blobSerializerTest() { @@ -132,11 +141,16 @@ void blobSerializerTest() { Object fromDb = serializer.loadFromDb(id, Forest.class.getSimpleName()); Forest forestFromDb = serializer.deSerialize(fromDb); - Assertions.assertEquals(forest.hashCode(), forestFromDb.hashCode(), + Assertions.assertEquals( + forest.hashCode(), + forestFromDb.hashCode(), "Hashes of objects after Serializing and Deserializing are the same"); - } catch (SQLException | IOException | TransformerException | ParserConfigurationException | - SAXException | - ClassNotFoundException e) { + } catch (SQLException + | IOException + | TransformerException + | ParserConfigurationException + | SAXException + | ClassNotFoundException e) { throw new RuntimeException(e); } } diff --git a/servant/README.md b/servant/README.md index 67fb496abd15..a5e4643b325a 100644 --- a/servant/README.md +++ b/servant/README.md @@ -34,6 +34,10 @@ Wikipedia says > In software engineering, the servant pattern defines an object used to offer some functionality to a group of classes without defining that functionality in each of them. A Servant is a class whose instance (or even just class) provides methods that take care of a desired service, while objects for which (or with whom) the servant does something, are taken as parameters. +Sequence diagram + +![Servant sequence diagram](./etc/servant-sequence-diagram.png) + ## Programmatic Example of Servant Pattern in Java The Servant design pattern is a behavioral design pattern that defines a class that provides some sort of service to a group of classes. This pattern is particularly useful when these classes lack some common functionality that can't be added to the superclass. The Servant class brings this common functionality to a group of classes. diff --git a/servant/etc/servant-sequence-diagram.png b/servant/etc/servant-sequence-diagram.png new file mode 100644 index 000000000000..1039676ec495 Binary files /dev/null and b/servant/etc/servant-sequence-diagram.png differ diff --git a/servant/pom.xml b/servant/pom.xml index 65650364bd6b..9917acddb984 100644 --- a/servant/pom.xml +++ b/servant/pom.xml @@ -35,6 +35,14 @@ servant + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/servant/src/main/java/com/iluwatar/servant/App.java b/servant/src/main/java/com/iluwatar/servant/App.java index 9583823c8f29..122a4f0a4ce0 100644 --- a/servant/src/main/java/com/iluwatar/servant/App.java +++ b/servant/src/main/java/com/iluwatar/servant/App.java @@ -27,7 +27,6 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; - /** * Servant offers some functionality to a group of classes without defining that functionality in * each of them. A Servant is a class whose instance provides methods that take care of a desired @@ -41,17 +40,13 @@ public class App { private static final Servant jenkins = new Servant("Jenkins"); private static final Servant travis = new Servant("Travis"); - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { scenario(jenkins, 1); scenario(travis, 0); } - /** - * Can add a List with enum Actions for variable scenarios. - */ + /** Can add a List with enum Actions for variable scenarios. */ public static void scenario(Servant servant, int compliment) { var k = new King(); var q = new Queen(); diff --git a/servant/src/main/java/com/iluwatar/servant/King.java b/servant/src/main/java/com/iluwatar/servant/King.java index a2fb35e1a5b2..85150edf28d3 100644 --- a/servant/src/main/java/com/iluwatar/servant/King.java +++ b/servant/src/main/java/com/iluwatar/servant/King.java @@ -24,9 +24,7 @@ */ package com.iluwatar.servant; -/** - * King. - */ +/** King. */ public class King implements Royalty { private boolean isDrunk; diff --git a/servant/src/main/java/com/iluwatar/servant/Queen.java b/servant/src/main/java/com/iluwatar/servant/Queen.java index 50b2f97a7e31..e0de35938792 100644 --- a/servant/src/main/java/com/iluwatar/servant/Queen.java +++ b/servant/src/main/java/com/iluwatar/servant/Queen.java @@ -24,9 +24,7 @@ */ package com.iluwatar.servant; -/** - * Queen. - */ +/** Queen. */ public class Queen implements Royalty { private boolean isDrunk = true; @@ -64,5 +62,4 @@ public boolean getMood() { public void setFlirtiness(boolean f) { this.isFlirty = f; } - } diff --git a/servant/src/main/java/com/iluwatar/servant/Royalty.java b/servant/src/main/java/com/iluwatar/servant/Royalty.java index ae19d4e6b463..befc71246f7f 100644 --- a/servant/src/main/java/com/iluwatar/servant/Royalty.java +++ b/servant/src/main/java/com/iluwatar/servant/Royalty.java @@ -24,9 +24,7 @@ */ package com.iluwatar.servant; -/** - * Royalty. - */ +/** Royalty. */ interface Royalty { void getFed(); diff --git a/servant/src/main/java/com/iluwatar/servant/Servant.java b/servant/src/main/java/com/iluwatar/servant/Servant.java index e6d279b2f3eb..f572b5d4ab86 100644 --- a/servant/src/main/java/com/iluwatar/servant/Servant.java +++ b/servant/src/main/java/com/iluwatar/servant/Servant.java @@ -26,16 +26,12 @@ import java.util.List; -/** - * Servant. - */ +/** Servant. */ public class Servant { public String name; - /** - * Constructor. - */ + /** Constructor. */ public Servant(String name) { this.name = name; } @@ -52,9 +48,7 @@ public void giveCompliments(Royalty r) { r.receiveCompliments(); } - /** - * Check if we will be hanged. - */ + /** Check if we will be hanged. */ public boolean checkIfYouWillBeHanged(List tableGuests) { return tableGuests.stream().allMatch(Royalty::getMood); } diff --git a/servant/src/test/java/com/iluwatar/servant/AppTest.java b/servant/src/test/java/com/iluwatar/servant/AppTest.java index 63322030e91d..dea0950738da 100644 --- a/servant/src/test/java/com/iluwatar/servant/AppTest.java +++ b/servant/src/test/java/com/iluwatar/servant/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.servant; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/servant/src/test/java/com/iluwatar/servant/KingTest.java b/servant/src/test/java/com/iluwatar/servant/KingTest.java index cb14220e10d9..31d03ca1b3d0 100644 --- a/servant/src/test/java/com/iluwatar/servant/KingTest.java +++ b/servant/src/test/java/com/iluwatar/servant/KingTest.java @@ -29,10 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * KingTest - * - */ +/** KingTest */ class KingTest { @Test @@ -102,5 +99,4 @@ void testHungryDrunkComplimentedKing() { king.changeMood(); assertFalse(king.getMood()); } - -} \ No newline at end of file +} diff --git a/servant/src/test/java/com/iluwatar/servant/QueenTest.java b/servant/src/test/java/com/iluwatar/servant/QueenTest.java index 9b7fe36ce2b5..251e9934b75e 100644 --- a/servant/src/test/java/com/iluwatar/servant/QueenTest.java +++ b/servant/src/test/java/com/iluwatar/servant/QueenTest.java @@ -24,16 +24,12 @@ */ package com.iluwatar.servant; - import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * QueenTest - * - */ +/** QueenTest */ class QueenTest { @Test @@ -67,5 +63,4 @@ void testFlirtyComplemented() { queen.changeMood(); assertTrue(queen.getMood()); } - -} \ No newline at end of file +} diff --git a/servant/src/test/java/com/iluwatar/servant/ServantTest.java b/servant/src/test/java/com/iluwatar/servant/ServantTest.java index 3f698d2adb4c..f0f476835325 100644 --- a/servant/src/test/java/com/iluwatar/servant/ServantTest.java +++ b/servant/src/test/java/com/iluwatar/servant/ServantTest.java @@ -33,10 +33,7 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * ServantTest - * - */ +/** ServantTest */ class ServantTest { @Test @@ -80,7 +77,5 @@ void testCheckIfYouWillBeHanged() { assertTrue(new Servant("test").checkIfYouWillBeHanged(goodCompany)); assertTrue(new Servant("test").checkIfYouWillBeHanged(badCompany)); - } - -} \ No newline at end of file +} diff --git a/server-session/README.md b/server-session/README.md index 4ce452b53f7d..a6054889e4d9 100644 --- a/server-session/README.md +++ b/server-session/README.md @@ -34,6 +34,10 @@ Wikipedia says > A session token is a unique identifier that is generated and sent from a server to a client to identify the current interaction session. The client usually stores and sends the token as an HTTP cookie and/or sends it as a parameter in GET or POST queries. The reason to use session tokens is that the client only has to handle the identifier—all session data is stored on the server (usually in a database, to which the client does not have direct access) linked to that identifier. +Sequence diagram + +![Server Session sequence diagram](./etc/server-session-sequence-diagram.png) + ## Programmatic Example of Server Session Pattern in Java The Server Session design pattern is a behavioral design pattern that assigns the responsibility of storing session data on the server side. This pattern is particularly useful in the context of stateless protocols like HTTP where all requests are isolated events independent of previous requests. diff --git a/server-session/etc/server-session-sequence-diagram.png b/server-session/etc/server-session-sequence-diagram.png new file mode 100644 index 000000000000..034196da7566 Binary files /dev/null and b/server-session/etc/server-session-sequence-diagram.png differ diff --git a/server-session/pom.xml b/server-session/pom.xml index e7cdcf82c201..50b52b405158 100644 --- a/server-session/pom.xml +++ b/server-session/pom.xml @@ -38,9 +38,17 @@ server-session + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter - junit-jupiter-api + junit-jupiter-engine test diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/App.java b/server-session/src/main/java/com/iluwatar/sessionserver/App.java index a3c66d3ff634..512447b8a2d9 100644 --- a/server-session/src/main/java/com/iluwatar/sessionserver/App.java +++ b/server-session/src/main/java/com/iluwatar/sessionserver/App.java @@ -34,22 +34,21 @@ import lombok.extern.slf4j.Slf4j; /** - * The server session pattern is a behavioral design pattern concerned with assigning the responsibility - * of storing session data on the server side. Within the context of stateless protocols like HTTP all - * requests are isolated events independent of previous requests. In order to create sessions during - * user-access for a particular web application various methods can be used, such as cookies. Cookies - * are a small piece of data that can be sent between client and server on every request and response - * so that the server can "remember" the previous requests. In general cookies can either store the session - * data or the cookie can store a session identifier and be used to access appropriate data from a persistent - * storage. In the latter case the session data is stored on the server-side and appropriate data is - * identified by the cookie sent from a client's request. - * This project demonstrates the latter case. - * In the following example the ({@link App}) class starts a server and assigns ({@link LoginHandler}) - * class to handle login request. When a user logs in a session identifier is created and stored for future - * requests in a list. When a user logs out the session identifier is deleted from the list along with - * the appropriate user session data, which is handle by the ({@link LogoutHandler}) class. + * The server session pattern is a behavioral design pattern concerned with assigning the + * responsibility of storing session data on the server side. Within the context of stateless + * protocols like HTTP all requests are isolated events independent of previous requests. In order + * to create sessions during user-access for a particular web application various methods can be + * used, such as cookies. Cookies are a small piece of data that can be sent between client and + * server on every request and response so that the server can "remember" the previous requests. In + * general cookies can either store the session data or the cookie can store a session identifier + * and be used to access appropriate data from a persistent storage. In the latter case the session + * data is stored on the server-side and appropriate data is identified by the cookie sent from a + * client's request. This project demonstrates the latter case. In the following example the ({@link + * App}) class starts a server and assigns ({@link LoginHandler}) class to handle login request. + * When a user logs in a session identifier is created and stored for future requests in a list. + * When a user logs out the session identifier is deleted from the list along with the appropriate + * user session data, which is handle by the ({@link LogoutHandler}) class. */ - @Slf4j public class App { @@ -60,6 +59,7 @@ public class App { /** * Main entry point. + * * @param args arguments * @throws IOException ex */ @@ -81,31 +81,36 @@ public static void main(String[] args) throws IOException { } private static void sessionExpirationTask() { - new Thread(() -> { - while (true) { - try { - LOGGER.info("Session expiration checker started..."); - Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time - Instant currentTime = Instant.now(); - synchronized (sessions) { - synchronized (sessionCreationTimes) { - Iterator> iterator = - sessionCreationTimes.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - if (entry.getValue().plusMillis(SESSION_EXPIRATION_TIME).isBefore(currentTime)) { - sessions.remove(entry.getKey()); - iterator.remove(); + new Thread( + () -> { + while (true) { + try { + LOGGER.info("Session expiration checker started..."); + Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time + Instant currentTime = Instant.now(); + synchronized (sessions) { + synchronized (sessionCreationTimes) { + Iterator> iterator = + sessionCreationTimes.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry + .getValue() + .plusMillis(SESSION_EXPIRATION_TIME) + .isBefore(currentTime)) { + sessions.remove(entry.getKey()); + iterator.remove(); + } + } + } + } + LOGGER.info("Session expiration checker finished!"); + } catch (InterruptedException e) { + LOGGER.error("An error occurred: ", e); + Thread.currentThread().interrupt(); } } - } - } - LOGGER.info("Session expiration checker finished!"); - } catch (InterruptedException e) { - LOGGER.error("An error occurred: ", e); - Thread.currentThread().interrupt(); - } - } - }).start(); + }) + .start(); } -} \ No newline at end of file +} diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java b/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java index 1e36ac052570..fd19aa5b9e5f 100644 --- a/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java +++ b/server-session/src/main/java/com/iluwatar/sessionserver/LoginHandler.java @@ -33,9 +33,7 @@ import java.util.UUID; import lombok.extern.slf4j.Slf4j; -/** - * LoginHandler. - */ +/** LoginHandler. */ @Slf4j public class LoginHandler implements HttpHandler { diff --git a/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java b/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java index 5bea06f2f866..3d98a7b604bd 100644 --- a/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java +++ b/server-session/src/main/java/com/iluwatar/sessionserver/LogoutHandler.java @@ -32,9 +32,7 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; -/** - * LogoutHandler. - */ +/** LogoutHandler. */ @Slf4j public class LogoutHandler implements HttpHandler { @@ -61,7 +59,7 @@ public void handle(HttpExchange exchange) { response = "Logout successful!\n" + "Session ID: " + currentSessionId; } - //Remove session + // Remove session if (currentSessionId != null) { LOGGER.info("User " + sessions.get(currentSessionId) + " deleted!"); } else { diff --git a/server-session/src/test/java/com.iluwatar.sessionserver/LoginHandlerTest.java b/server-session/src/test/java/com/iluwatar/sessionserver/LoginHandlerTest.java similarity index 85% rename from server-session/src/test/java/com.iluwatar.sessionserver/LoginHandlerTest.java rename to server-session/src/test/java/com/iluwatar/sessionserver/LoginHandlerTest.java index db5445f88483..1da0f3ab51f9 100644 --- a/server-session/src/test/java/com.iluwatar.sessionserver/LoginHandlerTest.java +++ b/server-session/src/test/java/com/iluwatar/sessionserver/LoginHandlerTest.java @@ -38,22 +38,17 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * LoginHandlerTest. - */ +/** LoginHandlerTest. */ public class LoginHandlerTest { private LoginHandler loginHandler; - //private Headers headers; + // private Headers headers; private Map sessions; private Map sessionCreationTimes; - @Mock - private HttpExchange exchange; + @Mock private HttpExchange exchange; - /** - * Setup tests. - */ + /** Setup tests. */ @BeforeEach public void setUp() { MockitoAnnotations.initMocks(this); @@ -65,18 +60,20 @@ public void setUp() { @Test public void testHandle() { - //assemble + // assemble ByteArrayOutputStream outputStream = - new ByteArrayOutputStream(); //Exchange object is mocked so OutputStream must be manually created - when(exchange.getResponseHeaders()).thenReturn( - new Headers()); //Exchange object is mocked so Header object must be manually created + new ByteArrayOutputStream(); // Exchange object is mocked so OutputStream must be manually + // created + when(exchange.getResponseHeaders()) + .thenReturn( + new Headers()); // Exchange object is mocked so Header object must be manually created when(exchange.getResponseBody()).thenReturn(outputStream); - //act + // act loginHandler.handle(exchange); - //assert + // assert String[] response = outputStream.toString().split("Session ID: "); assertEquals(sessions.entrySet().toArray()[0].toString().split("=1")[0], response[1]); } -} \ No newline at end of file +} diff --git a/server-session/src/test/java/com.iluwatar.sessionserver/LogoutHandlerTest.java b/server-session/src/test/java/com/iluwatar/sessionserver/LogoutHandlerTest.java similarity index 85% rename from server-session/src/test/java/com.iluwatar.sessionserver/LogoutHandlerTest.java rename to server-session/src/test/java/com/iluwatar/sessionserver/LogoutHandlerTest.java index 1b9d817b0114..3929aeb4bc7f 100644 --- a/server-session/src/test/java/com.iluwatar.sessionserver/LogoutHandlerTest.java +++ b/server-session/src/test/java/com/iluwatar/sessionserver/LogoutHandlerTest.java @@ -38,9 +38,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -/** - * LogoutHandlerTest. - */ +/** LogoutHandlerTest. */ public class LogoutHandlerTest { private LogoutHandler logoutHandler; @@ -48,12 +46,9 @@ public class LogoutHandlerTest { private Map sessions; private Map sessionCreationTimes; - @Mock - private HttpExchange exchange; + @Mock private HttpExchange exchange; - /** - * Setup tests. - */ + /** Setup tests. */ @BeforeEach public void setUp() { MockitoAnnotations.initMocks(this); @@ -61,25 +56,27 @@ public void setUp() { sessionCreationTimes = new HashMap<>(); logoutHandler = new LogoutHandler(sessions, sessionCreationTimes); headers = new Headers(); - headers.add("Cookie", - "sessionID=1234"); //Exchange object methods return Header Object but Exchange is mocked so Headers must be manually created + headers.add( + "Cookie", + "sessionID=1234"); // Exchange object methods return Header Object but Exchange is mocked so + // Headers must be manually created } @Test public void testHandler_SessionNotExpired() { - //assemble - sessions.put("1234", 1); //Fake login details since LoginHandler isn't called - sessionCreationTimes.put("1234", - Instant.now()); //Fake login details since LoginHandler isn't called + // assemble + sessions.put("1234", 1); // Fake login details since LoginHandler isn't called + sessionCreationTimes.put( + "1234", Instant.now()); // Fake login details since LoginHandler isn't called when(exchange.getRequestHeaders()).thenReturn(headers); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); when(exchange.getResponseBody()).thenReturn(outputStream); - //act + // act logoutHandler.handle(exchange); - //assert + // assert String[] response = outputStream.toString().split("Session ID: "); Assertions.assertEquals("1234", response[1]); Assertions.assertFalse(sessions.containsKey(response[1])); @@ -89,15 +86,15 @@ public void testHandler_SessionNotExpired() { @Test public void testHandler_SessionExpired() { - //assemble + // assemble ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); when(exchange.getRequestHeaders()).thenReturn(headers); when(exchange.getResponseBody()).thenReturn(outputStream); - //act + // act logoutHandler.handle(exchange); - //assert + // assert String[] response = outputStream.toString().split("Session ID: "); Assertions.assertEquals("Session has already expired!", response[0]); } diff --git a/service-layer/README.md b/service-layer/README.md index 4e50542c7e81..f2dc54115884 100644 --- a/service-layer/README.md +++ b/service-layer/README.md @@ -36,6 +36,10 @@ Wikipedia says > Service layer is an architectural pattern, applied within the service-orientation design paradigm, which aims to organize the services, within a service inventory, into a set of logical layers. Services that are categorized into a particular layer share functionality. This helps to reduce the conceptual overhead related to managing the service inventory, as the services belonging to the same layer address a smaller set of activities. +Architecture diagram + +![Service Layer Architecture Diagram](./etc/service-layer-architecture-diagram.png) + ## Programmatic Example of Service Layer Pattern in Java Our Java implementation uses the Service Layer pattern to streamline interactions between data access objects (DAOs) and the business logic, ensuring a clean separation of concerns. @@ -353,10 +357,6 @@ INFO [2024-05-27 09:16:40,681] com.iluwatar.servicelayer.app.App: Find wizards INFO [2024-05-27 09:16:40,683] com.iluwatar.servicelayer.app.App: Aderlard Boud has 'Fireball' ``` -## Detailed Explanation of Service Layer Pattern with Real-World Examples - -![Service Layer](./etc/service-layer.png "Service Layer") - ## When to Use the Service Layer Pattern in Java * Use when you need to separate business logic from presentation logic. diff --git a/service-layer/etc/service-layer-architecture-diagram.png b/service-layer/etc/service-layer-architecture-diagram.png new file mode 100644 index 000000000000..f7e6438d0ad5 Binary files /dev/null and b/service-layer/etc/service-layer-architecture-diagram.png differ diff --git a/service-layer/pom.xml b/service-layer/pom.xml index 19e8fd377ecb..37795dae301a 100644 --- a/service-layer/pom.xml +++ b/service-layer/pom.xml @@ -34,9 +34,18 @@ service-layer + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.hibernate hibernate-core + 6.6.11.Final com.h2database @@ -45,10 +54,17 @@ org.glassfish.jaxb jaxb-runtime + 4.0.5 javax.xml.bind jaxb-api + 2.4.0-b180830.0359 + + + jakarta.persistence + jakarta.persistence-api + 3.2.0 org.junit.jupiter diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java b/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java index 947d1ea2ee06..9c37968e5b62 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/app/App.java @@ -34,22 +34,21 @@ import com.iluwatar.servicelayer.wizard.WizardDaoImpl; import lombok.extern.slf4j.Slf4j; - /** * Service layer defines an application's boundary with a layer of services that establishes a set * of available operations and coordinates the application's response in each operation. * - *

    Enterprise applications typically require different kinds of interfaces to the data they - * store and the logic they implement: data loaders, user interfaces, integration gateways, and - * others. Despite their different purposes, these interfaces often need common interactions with - * the application to access and manipulate its data and invoke its business logic. The interactions - * may be complex, involving transactions across multiple resources and the coordination of several + *

    Enterprise applications typically require different kinds of interfaces to the data they store + * and the logic they implement: data loaders, user interfaces, integration gateways, and others. + * Despite their different purposes, these interfaces often need common interactions with the + * application to access and manipulate its data and invoke its business logic. The interactions may + * be complex, involving transactions across multiple resources and the coordination of several * responses to an action. Encoding the logic of the interactions separately in each interface * causes a lot of duplication. * - *

    The example application demonstrates interactions between a client ({@link App}) and a - * service ({@link MagicService}). The service is implemented with 3-layer architecture (entity, - * dao, service). For persistence the example uses in-memory H2 database which is populated on each + *

    The example application demonstrates interactions between a client ({@link App}) and a service + * ({@link MagicService}). The service is implemented with 3-layer architecture (entity, dao, + * service). For persistence the example uses in-memory H2 database which is populated on each * application startup. */ @Slf4j @@ -68,9 +67,7 @@ public static void main(String[] args) { queryData(); } - /** - * Initialize data. - */ + /** Initialize data. */ public static void initData() { // spells var spell1 = new Spell("Ice dart"); @@ -173,9 +170,7 @@ public static void initData() { wizardDao.merge(wizard4); } - /** - * Query the data. - */ + /** Query the data. */ public static void queryData() { var wizardDao = new WizardDaoImpl(); var spellbookDao = new SpellbookDaoImpl(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java index 6f3f662aa929..89b614d1b8b1 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/BaseEntity.java @@ -24,15 +24,10 @@ */ package com.iluwatar.servicelayer.common; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.MappedSuperclass; +import jakarta.persistence.MappedSuperclass; -/** - * Base class for entities. - */ +/** Base class for entities. */ @MappedSuperclass -@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class BaseEntity { /** @@ -62,5 +57,4 @@ public abstract class BaseEntity { * @param name The new name */ public abstract void setName(final String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java index 9fc0b63927db..e842d7b542b0 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/common/DaoBaseImpl.java @@ -25,11 +25,11 @@ package com.iluwatar.servicelayer.common; import com.iluwatar.servicelayer.hibernate.HibernateUtil; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import java.lang.reflect.ParameterizedType; import java.util.List; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.query.Query; @@ -42,8 +42,9 @@ public abstract class DaoBaseImpl implements Dao { @SuppressWarnings("unchecked") - protected Class persistentClass = (Class) ((ParameterizedType) getClass() - .getGenericSuperclass()).getActualTypeArguments()[0]; + protected Class persistentClass = + (Class) + ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; /* * Making this getSessionFactory() instead of getSession() so that it is the responsibility diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java b/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java index b1ef9f77f92d..b68ebc06b0ac 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/hibernate/HibernateUtil.java @@ -31,19 +31,14 @@ import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; -/** - * Produces the Hibernate {@link SessionFactory}. - */ +/** Produces the Hibernate {@link SessionFactory}. */ @Slf4j public final class HibernateUtil { - /** - * The cached session factory. - */ + /** The cached session factory. */ private static volatile SessionFactory sessionFactory; - private HibernateUtil() { - } + private HibernateUtil() {} /** * Create the current session factory instance, create a new one when there is none yet. @@ -53,15 +48,17 @@ private HibernateUtil() { public static synchronized SessionFactory getSessionFactory() { if (sessionFactory == null) { try { - sessionFactory = new Configuration() - .addAnnotatedClass(Wizard.class) - .addAnnotatedClass(Spellbook.class) - .addAnnotatedClass(Spell.class) - .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") - .setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1") - .setProperty("hibernate.current_session_context_class", "thread") - .setProperty("hibernate.show_sql", "false") - .setProperty("hibernate.hbm2ddl.auto", "create-drop").buildSessionFactory(); + sessionFactory = + new Configuration() + .addAnnotatedClass(Wizard.class) + .addAnnotatedClass(Spellbook.class) + .addAnnotatedClass(Spell.class) + .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") + .setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1") + .setProperty("hibernate.current_session_context_class", "thread") + .setProperty("hibernate.show_sql", "false") + .setProperty("hibernate.hbm2ddl.auto", "create-drop") + .buildSessionFactory(); } catch (Throwable ex) { LOGGER.error("Initial SessionFactory creation failed.", ex); throw new ExceptionInInitializerError(ex); @@ -78,5 +75,4 @@ public static void dropSession() { getSessionFactory().close(); sessionFactory = null; } - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicService.java b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicService.java index a5132286604e..33657f562d59 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicService.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicService.java @@ -29,10 +29,7 @@ import com.iluwatar.servicelayer.wizard.Wizard; import java.util.List; - -/** - * Service interface. - */ +/** Service interface. */ public interface MagicService { List findAllWizards(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java index af527963ad11..0c0c3bd1fd67 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/magic/MagicServiceImpl.java @@ -33,18 +33,14 @@ import java.util.ArrayList; import java.util.List; -/** - * Service implementation. - */ +/** Service implementation. */ public class MagicServiceImpl implements MagicService { private final WizardDao wizardDao; private final SpellbookDao spellbookDao; private final SpellDao spellDao; - /** - * Constructor. - */ + /** Constructor. */ public MagicServiceImpl(WizardDao wizardDao, SpellbookDao spellbookDao, SpellDao spellDao) { this.wizardDao = wizardDao; this.spellbookDao = spellbookDao; diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java index f272151660c1..c806530dc028 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/Spell.java @@ -26,23 +26,24 @@ import com.iluwatar.servicelayer.common.BaseEntity; import com.iluwatar.servicelayer.spellbook.Spellbook; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import lombok.Getter; import lombok.Setter; -/** - * Spell entity. - */ +/** Spell entity. */ @Entity @Table(name = "SPELL") @Getter @Setter +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Spell extends BaseEntity { private String name; @@ -56,8 +57,7 @@ public class Spell extends BaseEntity { @JoinColumn(name = "SPELLBOOK_ID_FK", referencedColumnName = "SPELLBOOK_ID") private Spellbook spellbook; - public Spell() { - } + public Spell() {} public Spell(String name) { this(); diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java index 08dda10486a2..675db02adddc 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDao.java @@ -26,11 +26,8 @@ import com.iluwatar.servicelayer.common.Dao; -/** - * SpellDao interface. - */ +/** SpellDao interface. */ public interface SpellDao extends Dao { Spell findByName(String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java index 5ee39f4be733..0e238ccf8319 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spell/SpellDaoImpl.java @@ -25,16 +25,13 @@ package com.iluwatar.servicelayer.spell; import com.iluwatar.servicelayer.common.DaoBaseImpl; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.hibernate.Transaction; import org.hibernate.query.Query; - -/** - * SpellDao implementation. - */ +/** SpellDao implementation. */ public class SpellDaoImpl extends DaoBaseImpl implements SpellDao { @Override diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java index d81fc878902c..8a893724c63a 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/Spellbook.java @@ -27,27 +27,28 @@ import com.iluwatar.servicelayer.common.BaseEntity; import com.iluwatar.servicelayer.spell.Spell; import com.iluwatar.servicelayer.wizard.Wizard; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; import java.util.HashSet; import java.util.Set; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.ManyToMany; -import javax.persistence.OneToMany; -import javax.persistence.Table; import lombok.Getter; import lombok.Setter; -/** - * Spellbook entity. - */ +/** Spellbook entity. */ @Entity @Table(name = "SPELLBOOK") @Getter @Setter +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Spellbook extends BaseEntity { @Id diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java index 84fd5dfb756f..c1c27ddf5b1a 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDao.java @@ -26,11 +26,8 @@ import com.iluwatar.servicelayer.common.Dao; -/** - * SpellbookDao interface. - */ +/** SpellbookDao interface. */ public interface SpellbookDao extends Dao { Spellbook findByName(String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java index 463b06cdd369..9169f6ca80b5 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImpl.java @@ -25,16 +25,13 @@ package com.iluwatar.servicelayer.spellbook; import com.iluwatar.servicelayer.common.DaoBaseImpl; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.hibernate.Transaction; import org.hibernate.query.Query; - -/** - * SpellbookDao implementation. - */ +/** SpellbookDao implementation. */ public class SpellbookDaoImpl extends DaoBaseImpl implements SpellbookDao { @Override @@ -58,5 +55,4 @@ public Spellbook findByName(String name) { } return result; } - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java index 8b607d76e7b1..44c3c4b4ad07 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/Wizard.java @@ -26,25 +26,26 @@ import com.iluwatar.servicelayer.common.BaseEntity; import com.iluwatar.servicelayer.spellbook.Spellbook; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; import java.util.HashSet; import java.util.Set; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.ManyToMany; -import javax.persistence.Table; import lombok.Getter; import lombok.Setter; -/** - * Wizard entity. - */ +/** Wizard entity. */ @Entity @Table(name = "WIZARD") @Getter @Setter +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Wizard extends BaseEntity { @Id diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java index 33dd3d3432fb..e8ed2229f62b 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDao.java @@ -26,11 +26,8 @@ import com.iluwatar.servicelayer.common.Dao; -/** - * WizardDao interface. - */ +/** WizardDao interface. */ public interface WizardDao extends Dao { Wizard findByName(String name); - } diff --git a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java index d35c9b0ca21b..e1dd3069d5fc 100644 --- a/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java +++ b/service-layer/src/main/java/com/iluwatar/servicelayer/wizard/WizardDaoImpl.java @@ -25,15 +25,13 @@ package com.iluwatar.servicelayer.wizard; import com.iluwatar.servicelayer.common.DaoBaseImpl; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Root; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; import org.hibernate.Transaction; import org.hibernate.query.Query; -/** - * WizardDao implementation. - */ +/** WizardDao implementation. */ public class WizardDaoImpl extends DaoBaseImpl implements WizardDao { @Override diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java index c7d0b9139d99..11af9138d6f0 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/app/AppTest.java @@ -24,25 +24,22 @@ */ package com.iluwatar.servicelayer.app; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.iluwatar.servicelayer.hibernate.HibernateUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } @AfterEach void tearDown() { HibernateUtil.dropSession(); } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java index c563ead81924..1bfeeb79aff3 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/common/BaseDaoTest.java @@ -43,31 +43,23 @@ */ public abstract class BaseDaoTest> { - /** - * The number of entities stored before each test - */ + /** The number of entities stored before each test */ private static final int INITIAL_COUNT = 5; - /** - * The unique id generator, shared between all entities - */ + /** The unique id generator, shared between all entities */ private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); - /** - * Factory, used to create new entity instances with the given name - */ + /** Factory, used to create new entity instances with the given name */ private final Function factory; - /** - * The tested data access object - */ + /** The tested data access object */ private final D dao; /** * Create a new test using the given factory and dao * * @param factory The factory, used to create new entity instances with the given name - * @param dao The tested data access object + * @param dao The tested data access object */ public BaseDaoTest(final Function factory, final D dao) { this.factory = factory; @@ -141,5 +133,4 @@ void testSetName() { assertEquals(expectedName, entity.getName()); assertEquals(expectedName, entity.toString()); } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java index 7badd9da339a..22794ea8d727 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/magic/MagicServiceImplTest.java @@ -41,10 +41,7 @@ import java.util.Set; import org.junit.jupiter.api.Test; -/** - * MagicServiceImplTest - * - */ +/** MagicServiceImplTest */ class MagicServiceImplTest { @Test @@ -93,11 +90,7 @@ void testFindAllSpells() { void testFindWizardsWithSpellbook() { final var bookname = "bookname"; final var spellbook = mock(Spellbook.class); - final var wizards = Set.of( - mock(Wizard.class), - mock(Wizard.class), - mock(Wizard.class) - ); + final var wizards = Set.of(mock(Wizard.class), mock(Wizard.class), mock(Wizard.class)); when(spellbook.getWizards()).thenReturn(wizards); final var spellbookDao = mock(SpellbookDao.class); @@ -106,7 +99,6 @@ void testFindWizardsWithSpellbook() { final var wizardDao = mock(WizardDao.class); final var spellDao = mock(SpellDao.class); - final var service = new MagicServiceImpl(wizardDao, spellbookDao, spellDao); verifyNoInteractions(wizardDao, spellbookDao, spellDao, spellbook); @@ -122,11 +114,7 @@ void testFindWizardsWithSpellbook() { @Test void testFindWizardsWithSpell() { - final var wizards = Set.of( - mock(Wizard.class), - mock(Wizard.class), - mock(Wizard.class) - ); + final var wizards = Set.of(mock(Wizard.class), mock(Wizard.class), mock(Wizard.class)); final var spellbook = mock(Spellbook.class); when(spellbook.getWizards()).thenReturn(wizards); @@ -152,5 +140,4 @@ void testFindWizardsWithSpell() { verifyNoMoreInteractions(wizardDao, spellbookDao, spellDao); } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java index 9b75f531924f..616d80c12e15 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spell/SpellDaoImplTest.java @@ -30,10 +30,7 @@ import com.iluwatar.servicelayer.common.BaseDaoTest; import org.junit.jupiter.api.Test; -/** - * SpellDaoImplTest - * - */ +/** SpellDaoImplTest */ class SpellDaoImplTest extends BaseDaoTest { public SpellDaoImplTest() { @@ -51,5 +48,4 @@ void testFindByName() { assertEquals(spell.getName(), spellByName.getName()); } } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java index 55f3b8b96329..5d7b208601e5 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/spellbook/SpellbookDaoImplTest.java @@ -30,10 +30,7 @@ import com.iluwatar.servicelayer.common.BaseDaoTest; import org.junit.jupiter.api.Test; -/** - * SpellbookDaoImplTest - * - */ +/** SpellbookDaoImplTest */ class SpellbookDaoImplTest extends BaseDaoTest { public SpellbookDaoImplTest() { @@ -51,5 +48,4 @@ void testFindByName() { assertEquals(book.getName(), spellByName.getName()); } } - } diff --git a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java index 22a7c6d08e36..f21380f50f82 100644 --- a/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java +++ b/service-layer/src/test/java/com/iluwatar/servicelayer/wizard/WizardDaoImplTest.java @@ -30,10 +30,7 @@ import com.iluwatar.servicelayer.common.BaseDaoTest; import org.junit.jupiter.api.Test; -/** - * WizardDaoImplTest - * - */ +/** WizardDaoImplTest */ class WizardDaoImplTest extends BaseDaoTest { public WizardDaoImplTest() { @@ -51,5 +48,4 @@ void testFindByName() { assertEquals(spell.getName(), byName.getName()); } } - } diff --git a/service-locator/README.md b/service-locator/README.md index 68c803e70a14..7e8e25d10031 100644 --- a/service-locator/README.md +++ b/service-locator/README.md @@ -33,6 +33,10 @@ Wikipedia says > The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the "service locator", which on request returns the information necessary to perform a certain task. Proponents of the pattern say the approach simplifies component-based applications where all dependencies are cleanly listed at the beginning of the whole application design, consequently making traditional dependency injection a more complex way of connecting objects. Critics of the pattern argue that it is an antipattern which obscures dependencies and makes software harder to test. +Sequence diagram + +![Service Locator sequence diagram](./etc/service-locator-sequence-diagram.png) + ## Programmatic Example of Service Locator Pattern in Java The Service Locator design pattern is used to abstract the processes involved in obtaining a service. It uses a central registry, the "service locator", which returns the necessary information to perform a task upon request. This Java design pattern is particularly useful in enterprise Java applications where services need centralized management. diff --git a/service-locator/etc/service-locator-sequence-diagram.png b/service-locator/etc/service-locator-sequence-diagram.png new file mode 100644 index 000000000000..3987b03927cf Binary files /dev/null and b/service-locator/etc/service-locator-sequence-diagram.png differ diff --git a/service-locator/pom.xml b/service-locator/pom.xml index 515f5eff7356..52fb0b932ba6 100644 --- a/service-locator/pom.xml +++ b/service-locator/pom.xml @@ -34,6 +34,14 @@ service-locator + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/App.java b/service-locator/src/main/java/com/iluwatar/servicelocator/App.java index f8467e0c4915..061cee97bdba 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/App.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/App.java @@ -31,9 +31,7 @@ * necessary to perform a certain task. * *

    In this example we use the Service locator pattern to lookup JNDI-services and cache them for - * subsequent requests. - *
    - * + * subsequent requests.
    */ public class App { diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/InitContext.java b/service-locator/src/main/java/com/iluwatar/servicelocator/InitContext.java index 7b3b2374240e..2082a066b124 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/InitContext.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/InitContext.java @@ -29,7 +29,6 @@ /** * For JNDI lookup of services from the web.xml. Will match name of the service name that is being * requested and return a newly created service object with the name - * */ @Slf4j public class InitContext { diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/Service.java b/service-locator/src/main/java/com/iluwatar/servicelocator/Service.java index 4c830fe503c2..876a6f727f01 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/Service.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/Service.java @@ -26,9 +26,13 @@ /** * This is going to be the parent service interface which we will use to create our services. All - * services will have a

    • service name
    • unique id
    • execution work - * flow
    + * services will have a * + *
      + *
    • service name + *
    • unique id + *
    • execution work flow + *
    */ public interface Service { diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceCache.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceCache.java index b8bcbcfc94c8..112abe8d5f20 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceCache.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceCache.java @@ -33,7 +33,6 @@ * the cache will be empty and thus any service that is being requested, will be created fresh and * then placed into the cache map. On next hit, if same service name will be requested, it will be * returned from the cache - * */ @Slf4j public class ServiceCache { diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java index d2cc2bf51911..12b6359018ae 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceImpl.java @@ -30,7 +30,6 @@ * This is a single service implementation of a sample service. This is the actual service that will * process the request. The reference for this service is to be looked upon in the JNDI server that * can be set in the web.xml deployment descriptor - * */ @Slf4j public class ServiceImpl implements Service { @@ -38,9 +37,7 @@ public class ServiceImpl implements Service { private final String serviceName; private final int id; - /** - * Constructor. - */ + /** Constructor. */ public ServiceImpl(String serviceName) { // set the service name this.serviceName = serviceName; diff --git a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java index 8ce2d27e9632..53da47e89b2a 100644 --- a/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java +++ b/service-locator/src/main/java/com/iluwatar/servicelocator/ServiceLocator.java @@ -27,14 +27,12 @@ /** * The service locator module. Will fetch service from cache, otherwise creates a fresh service and * update cache - * */ public final class ServiceLocator { private static final ServiceCache serviceCache = new ServiceCache(); - private ServiceLocator() { - } + private ServiceLocator() {} /** * Fetch the service with the name param from the cache first, if no service is found, lookup the diff --git a/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java b/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java index 3622e4117208..282ad2e410a6 100644 --- a/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java +++ b/service-locator/src/test/java/com/iluwatar/servicelocator/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.servicelocator; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java b/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java index cf56bd01ec45..90d6966b987b 100644 --- a/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java +++ b/service-locator/src/test/java/com/iluwatar/servicelocator/ServiceLocatorTest.java @@ -33,24 +33,17 @@ import java.util.List; import org.junit.jupiter.api.Test; -/** - * ServiceLocatorTest - * - */ +/** ServiceLocatorTest */ class ServiceLocatorTest { - /** - * Verify if we just receive 'null' when requesting a non-existing service - */ + /** Verify if we just receive 'null' when requesting a non-existing service */ @Test void testGetNonExistentService() { assertNull(ServiceLocator.getService("fantastic/unicorn/service")); assertNull(ServiceLocator.getService("another/fantastic/unicorn/service")); } - /** - * Verify if we get the same cached instance when requesting the same service twice - */ + /** Verify if we get the same cached instance when requesting the same service twice */ @Test void testServiceCache() { final var serviceNames = List.of("jndi/serviceA", "jndi/serviceB"); @@ -62,7 +55,5 @@ void testServiceCache() { assertTrue(service.getId() > 0); // The id is generated randomly, but the minimum value is '1' assertSame(service, ServiceLocator.getService(serviceName)); } - } - -} \ No newline at end of file +} diff --git a/service-stub/README.md b/service-stub/README.md new file mode 100644 index 000000000000..5fa4533587ea --- /dev/null +++ b/service-stub/README.md @@ -0,0 +1,170 @@ +--- +title: "Service Stub Pattern in Java: Simplifying Testing with Stub Implementations" +shortTitle: Service Stub +description: "Explore the Service Stub design pattern in Java using a Sentiment Analysis example. Learn how stub implementations provide dummy services to facilitate testing and development." +category: Testing +language: en +tag: + - API design + - Decoupling + - Integration + - Microservices + - Testing +--- + +## Also known as + +* Service Mock +* Test Double + +## Intent of Service Stub Pattern + +Provide a lightweight, simplified implementation of a remote or external service to facilitate testing in isolation. + +## Detailed Explanation of Service Stub Pattern with Real-World Example + +Real-world example + +> A real-world analogy for the Service Stub pattern could be a flight simulator used to train pilots. Instead of learning to fly directly using a real airplane—which would be costly, dangerous, and often impractical—pilots initially practice within a simulator. This simulator provides predefined, realistic scenarios and reactions, enabling pilots to train safely, repeatedly, and predictably without the complexities and risks associated with actual flight operations. Similarly, a Service Stub provides controlled, predictable responses for external services during testing, simplifying and accelerating software development and testing processes. + +In plain words + +> Use a fake service to return predictable results without relying on external systems. + +Wikipedia says + +> A test stub is a dummy component used during testing to isolate behavior. + +Sequence diagram + +![Service Stub sequence diagram](./etc/service-stub-sequence-diagram.png) + +## Programmatic Example of Service Stub Pattern in Java + +We demonstrate the Service Stub pattern using a simple sentiment analysis example. To illustrate this clearly, we define a common interface `SentimentAnalysisServer` and create two separate implementations: + +**RealSentimentAnalysisServer**: Represents a slow, realistic sentiment analysis service, returning random sentiment results to simulate external complexity and latency. + +**StubSentimentAnalysisServer**: Provides fast, deterministic results based on simple keyword matching, suitable for isolated testing without external dependencies. + +### Step-by-step Example Implementation + +First, define a common interface that both implementations will use: + +```java +public interface SentimentAnalysisServer { + String analyzeSentiment(String text); +} +``` + +Next, we create a realistic implementation that simulates a slow, external service. It introduces a delay of 5 seconds and returns random sentiment results (`Positive`, `Negative`, or `Neutral`). For flexibility and easier testing, it allows injecting a custom sentiment supplier: + +```java +public class RealSentimentAnalysisServer implements SentimentAnalysisServer { + + private final Supplier sentimentSupplier; + + public RealSentimentAnalysisServer(Supplier sentimentSupplier) { + this.sentimentSupplier = sentimentSupplier; + } + + public RealSentimentAnalysisServer() { + this(() -> new Random().nextInt(3)); + } + + @Override + public String analyzeSentiment(String text) { + int sentiment = sentimentSupplier.get(); + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return sentiment == 0 ? "Positive" : sentiment == 1 ? "Negative" : "Neutral"; + } +} +``` + +Then, we provide a simplified stub implementation designed specifically for testing purposes. It returns immediate and predictable results based on simple keyword detection. This enables tests to run quickly and consistently without relying on external factors: + +```java +public class StubSentimentAnalysisServer implements SentimentAnalysisServer { + + @Override + public String analyzeSentiment(String text) { + if (text.toLowerCase().contains("good")) { + return "Positive"; + } + else if (text.toLowerCase().contains("bad")) { + return "Negative"; + } + else { + return "Neutral"; + } + } +} +``` + +Finally, here's the main application logic illustrating how to use both implementations in practice. Notice the significant performance difference between the real and stub implementations: + +```java +@Slf4j + public static void main(String[] args) { + LOGGER.info("Setting up the real sentiment analysis server."); + RealSentimentAnalysisServer realSentimentAnalysisServer = new RealSentimentAnalysisServer(); + String text = "This movie is soso"; + LOGGER.info("Analyzing input: {}", text); + String sentiment = realSentimentAnalysisServer.analyzeSentiment(text); + LOGGER.info("The sentiment is: {}", sentiment); + + LOGGER.info("Setting up the stub sentiment analysis server."); + StubSentimentAnalysisServer stubSentimentAnalysisServer = new StubSentimentAnalysisServer(); + text = "This movie is so bad"; + LOGGER.info("Analyzing input: {}", text); + sentiment = stubSentimentAnalysisServer.analyzeSentiment(text); + LOGGER.info("The sentiment is: {}", sentiment); + } +``` + +In summary, implementing a Service Stub involves creating a simplified substitute (`StubSentimentAnalysisServer`) for an actual external service (`RealSentimentAnalysisServer`). This approach allows your tests to run quickly and consistently by isolating them from external complexity and unpredictability. + +## When to Use the Service Stub Pattern in Java + +* When testing systems with external or third-party service dependencies. +* In integration tests to isolate the service being tested from network or external dependencies. +* During development when the actual services are unavailable or unreliable. +* To speed up tests by avoiding calls to slower external systems. + +## Real-World Applications of Service Stub Pattern in Java + +* WireMock: Widely used in Java testing to stub HTTP-based external services. +* Mockito: Allows creating lightweight stubs for dependencies in unit testing. +* Spring Cloud Contract: Provides contracts and stub servers for services in microservices architectures. + +## Benefits and Trade-offs of Service Stub Pattern + +Benefits: + +* Simplifies testing by eliminating dependencies on external systems. +* Speeds up testing processes by removing latency from external network calls. +* Allows consistent, repeatable, and predictable testing scenarios. +* Enables parallel test execution, improving overall development productivity. + +Trade-offs: + +* Stubs need to be regularly updated to reflect changes in the actual external services. +* May introduce false confidence if stubs do not accurately represent external system behavior. +* Can lead to additional overhead and maintenance of stub configurations. + +## Related Java Design Patterns + +* [Adapter](https://java-design-patterns.com/patterns/adapter/): Service Stub may sometimes implement Adapter interfaces to mimic external dependencies in a test environment. +* Mock Object: Similar to Service Stub, but Mock Objects usually verify interactions explicitly, while Service Stubs primarily provide predefined responses without verification. +* [Proxy](https://java-design-patterns.com/patterns/proxy/): Both Service Stub and Proxy introduce intermediate objects to control access or communication with actual components, though Proxy typically manages access control and communication, while Service Stub specifically aims to isolate for testing. + +## References and Credits + +* [Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation](https://amzn.to/4bjhTSK) +* [Growing Object-Oriented Software, Guided by Tests](https://amzn.to/4dGfIuk) +* [Mocks Aren't Stubs (Martin Fowler)](https://martinfowler.com/articles/mocksArentStubs.html) +* [xUnit Test Patterns: Refactoring Test Code](https://amzn.to/4dHGDpm) diff --git a/service-stub/etc/service-stub-sequence-diagram.png b/service-stub/etc/service-stub-sequence-diagram.png new file mode 100644 index 000000000000..de83741a52d8 Binary files /dev/null and b/service-stub/etc/service-stub-sequence-diagram.png differ diff --git a/service-stub/pom.xml b/service-stub/pom.xml new file mode 100644 index 000000000000..1289578678c6 --- /dev/null +++ b/service-stub/pom.xml @@ -0,0 +1,56 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + service-stub + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + + \ No newline at end of file diff --git a/service-stub/src/main/java/com/iluwatar/servicestub/App.java b/service-stub/src/main/java/com/iluwatar/servicestub/App.java new file mode 100644 index 000000000000..34227bc54614 --- /dev/null +++ b/service-stub/src/main/java/com/iluwatar/servicestub/App.java @@ -0,0 +1,66 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +import lombok.extern.slf4j.Slf4j; + +/** + * A Service Stub is a dummy implementation of an external service used during development or + * testing. The purpose is to provide a lightweight "stub" when the real service may not always be + * available (or too slow to use during testing). + * + *

    This implementation simulates a simple sentiment analysis program, where a text is analyzed to + * deduce whether it is a positive, negative or neutral sentiment. The stub returns a response based + * on whether the analyzed text contains the words "good" or "bad", not accounting for stopwords or + * the underlying semantic of the text. + * + *

    The "real" sentiment analysis class simulates the processing time for the request by pausing + * the execution of the thread for 5 seconds. In the stub sentiment analysis class the response is + * immediate. In addition, the stub returns a deterministic output with regard to the input. This is + * extra useful for testing purposes. + */ +@Slf4j +public class App { + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + LOGGER.info("Setting up the real sentiment analysis server."); + RealSentimentAnalysisServer realSentimentAnalysisServer = new RealSentimentAnalysisServer(); + String text = "This movie is soso"; + LOGGER.info("Analyzing input: {}", text); + String sentiment = realSentimentAnalysisServer.analyzeSentiment(text); + LOGGER.info("The sentiment is: {}", sentiment); + + LOGGER.info("Setting up the stub sentiment analysis server."); + StubSentimentAnalysisServer stubSentimentAnalysisServer = new StubSentimentAnalysisServer(); + text = "This movie is so bad"; + LOGGER.info("Analyzing input: {}", text); + sentiment = stubSentimentAnalysisServer.analyzeSentiment(text); + LOGGER.info("The sentiment is: {}", sentiment); + } +} diff --git a/service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java b/service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java new file mode 100644 index 000000000000..c65f6d172151 --- /dev/null +++ b/service-stub/src/main/java/com/iluwatar/servicestub/RealSentimentAnalysisServer.java @@ -0,0 +1,69 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +import java.util.Random; +import java.util.function.Supplier; + +/** + * Real implementation of SentimentAnalysisServer. Simulates random sentiment classification with + * processing delay. + */ +public class RealSentimentAnalysisServer implements SentimentAnalysisServer { + /** + * A real sentiment analysis implementation would analyze the input string using, e.g., NLP and + * determine whether the sentiment is positive, negative or neutral. Here we simply choose a + * random number to simulate this. The "model" may take some time to process the input and we + * simulate this by delaying the execution 5 seconds. Analyzes the sentiment of the given input + * string and returns the classification result (Positive, Negative, or Neutral). + */ + private final Supplier sentimentSupplier; + + // Constructor + public RealSentimentAnalysisServer(Supplier sentimentSupplier) { + this.sentimentSupplier = sentimentSupplier; + } + + @SuppressWarnings("java:S2245") // Safe use: Randomness is for simulation/testing only + public RealSentimentAnalysisServer() { + this(() -> new Random().nextInt(3)); + } + + @Override + public String analyzeSentiment(String text) { + int sentiment = sentimentSupplier.get(); + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + return switch (sentiment) { + case 0 -> "Positive"; + case 1 -> "Negative"; + default -> "Neutral"; + }; + } +} diff --git a/service-stub/src/main/java/com/iluwatar/servicestub/SentimentAnalysisServer.java b/service-stub/src/main/java/com/iluwatar/servicestub/SentimentAnalysisServer.java new file mode 100644 index 000000000000..51bd6394f30f --- /dev/null +++ b/service-stub/src/main/java/com/iluwatar/servicestub/SentimentAnalysisServer.java @@ -0,0 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +/** Sentiment analysis server interface to be implemented by sentiment analysis services. */ +public interface SentimentAnalysisServer { + /** + * Analyzes the sentiment of the input text and returns the result. + * + * @param text the input text to analyze + * @return sentiment classification result + */ + String analyzeSentiment(String text); +} diff --git a/service-stub/src/main/java/com/iluwatar/servicestub/StubSentimentAnalysisServer.java b/service-stub/src/main/java/com/iluwatar/servicestub/StubSentimentAnalysisServer.java new file mode 100644 index 000000000000..95949c74fff9 --- /dev/null +++ b/service-stub/src/main/java/com/iluwatar/servicestub/StubSentimentAnalysisServer.java @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +/** + * Stub implementation of SentimentAnalysisServer. Returns deterministic sentiment based on input + * keywords. + */ +public class StubSentimentAnalysisServer implements SentimentAnalysisServer { + + /** + * Fake sentiment analyzer, always returns "Positive" if input string contains the word "good", + * "Negative" if the string contains "bad" and "Neutral" otherwise. + * + * @param text the input string to analyze + * @return sentiment classification result (Positive, Negative, or Neutral) + */ + @Override + public String analyzeSentiment(String text) { + if (text.toLowerCase().contains("good")) { + return "Positive"; + } else if (text.toLowerCase().contains("bad")) { + return "Negative"; + } else { + return "Neutral"; + } + } +} diff --git a/service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java b/service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java new file mode 100644 index 000000000000..a0acda718b00 --- /dev/null +++ b/service-stub/src/test/java/com/iluwatar/servicestub/AppTest.java @@ -0,0 +1,36 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +class AppTest { + @Test + void shouldExecuteWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/service-stub/src/test/java/com/iluwatar/servicestub/RealSentimentAnalysisServerTest.java b/service-stub/src/test/java/com/iluwatar/servicestub/RealSentimentAnalysisServerTest.java new file mode 100644 index 000000000000..0ae02182634c --- /dev/null +++ b/service-stub/src/test/java/com/iluwatar/servicestub/RealSentimentAnalysisServerTest.java @@ -0,0 +1,50 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class RealSentimentAnalysisServerTest { + + @Test + void testPositiveSentiment() { + RealSentimentAnalysisServer server = new RealSentimentAnalysisServer(() -> 0); + assertEquals("Positive", server.analyzeSentiment("Test")); + } + + @Test + void testNegativeSentiment() { + RealSentimentAnalysisServer server = new RealSentimentAnalysisServer(() -> 1); + assertEquals("Negative", server.analyzeSentiment("Test")); + } + + @Test + void testNeutralSentiment() { + RealSentimentAnalysisServer server = new RealSentimentAnalysisServer(() -> 2); + assertEquals("Neutral", server.analyzeSentiment("Test")); + } +} diff --git a/service-stub/src/test/java/com/iluwatar/servicestub/StubSentimentAnalysisServerTest.java b/service-stub/src/test/java/com/iluwatar/servicestub/StubSentimentAnalysisServerTest.java new file mode 100644 index 000000000000..5a136633d656 --- /dev/null +++ b/service-stub/src/test/java/com/iluwatar/servicestub/StubSentimentAnalysisServerTest.java @@ -0,0 +1,52 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.servicestub; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class StubSentimentAnalysisServerTest { + + private final StubSentimentAnalysisServer stub = new StubSentimentAnalysisServer(); + + @Test + void testPositiveSentiment() { + String result = stub.analyzeSentiment("This is a good product"); + assertEquals("Positive", result); + } + + @Test + void testNegativeSentiment() { + String result = stub.analyzeSentiment("This is a bad product"); + assertEquals("Negative", result); + } + + @Test + void testNeutralSentiment() { + String result = stub.analyzeSentiment("This product is average"); + assertEquals("Neutral", result); + } +} diff --git a/service-to-worker/README.md b/service-to-worker/README.md index fb234342c79f..0c1c7bd4ba4d 100644 --- a/service-to-worker/README.md +++ b/service-to-worker/README.md @@ -26,6 +26,10 @@ In plain words > Separates the processing logic from the view in web applications to improve maintainability and scalability. +Sequence diagram + +![Service to Worker sequence diagram](./etc/service-to-worker-sequence-diagram.png) + ## Programmatic Example of Service to Worker Pattern in Java The Service to Worker design pattern separates the processing logic from the view in web applications to improve maintainability and scalability. It combines the Dispatcher View and Service Locator patterns to facilitate the separation of processing, control flow, and view management in web applications. diff --git a/service-to-worker/etc/service-to-worker-sequence-diagram.png b/service-to-worker/etc/service-to-worker-sequence-diagram.png new file mode 100644 index 000000000000..e33a04be8b2f Binary files /dev/null and b/service-to-worker/etc/service-to-worker-sequence-diagram.png differ diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Command.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Command.java index df6d80eb422a..3c033e8f8466 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Command.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Command.java @@ -29,12 +29,10 @@ import com.iluwatar.model.view.controller.Nourishment; /** - * The type Command. - * Instantiates a new Command. + * The type Command. Instantiates a new Command. * - * @param fatigue the fatigue - * @param health the health + * @param fatigue the fatigue + * @param health the health * @param nourishment the nourishment */ -public record Command(Fatigue fatigue, Health health, Nourishment nourishment) { -} +public record Command(Fatigue fatigue, Health health, Nourishment nourishment) {} diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Dispatcher.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Dispatcher.java index 784784478af2..3d5d957e89c5 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Dispatcher.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/Dispatcher.java @@ -34,8 +34,7 @@ */ public class Dispatcher { - @Getter - private final GiantView giantView; + @Getter private final GiantView giantView; private final List actions; /** @@ -60,7 +59,7 @@ void addAction(Action action) { /** * Perform an action. * - * @param s the s + * @param s the s * @param actionIndex the action index */ public void performAction(Command s, int actionIndex) { @@ -75,5 +74,4 @@ public void performAction(Command s, int actionIndex) { public void updateView(GiantModel giantModel) { giantView.displayGiant(giantModel); } - } diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantController.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantController.java index 0be3ee5adff0..78add64f8616 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantController.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantController.java @@ -44,7 +44,7 @@ public GiantController(Dispatcher dispatcher) { /** * Sets command to control the dispatcher. * - * @param s the s + * @param s the s * @param index the index */ public void setCommand(Command s, int index) { diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantModel.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantModel.java index 202c25e5650c..69e4b834a56a 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantModel.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantModel.java @@ -29,27 +29,23 @@ import com.iluwatar.model.view.controller.Nourishment; import lombok.Getter; -/** - * GiantModel contains the giant data. - */ +/** GiantModel contains the giant data. */ public class GiantModel { private final com.iluwatar.model.view.controller.GiantModel model; - @Getter - private final String name; + @Getter private final String name; /** * Instantiates a new Giant model. * - * @param name the name - * @param health the health - * @param fatigue the fatigue + * @param name the name + * @param health the health + * @param fatigue the fatigue * @param nourishment the nourishment */ GiantModel(String name, Health health, Fatigue fatigue, Nourishment nourishment) { this.name = name; - this.model = new com.iluwatar.model.view.controller.GiantModel(health, fatigue, - nourishment); + this.model = new com.iluwatar.model.view.controller.GiantModel(health, fatigue, nourishment); } /** @@ -103,8 +99,8 @@ void setNourishment(Nourishment nourishment) { @Override public String toString() { - return String - .format("Giant %s, The giant looks %s, %s and %s.", name, - model.getHealth(), model.getFatigue(), model.getNourishment()); + return String.format( + "Giant %s, The giant looks %s, %s and %s.", + name, model.getHealth(), model.getFatigue(), model.getNourishment()); } } diff --git a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantView.java b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantView.java index 44d256cdbf13..667f4c889dbf 100644 --- a/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantView.java +++ b/service-to-worker/src/main/java/com/iluwatar/servicetoworker/GiantView.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * GiantView displays the giant. - */ +/** GiantView displays the giant. */ @Slf4j public class GiantView { diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/ActionTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/ActionTest.java index 400010dd0759..66bfc625113b 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/ActionTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/ActionTest.java @@ -31,18 +31,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; -/** - * The type Action test. - */ +/** The type Action test. */ class ActionTest { - /** - * Verify if the health value is set properly though the constructor and setter - */ + /** Verify if the health value is set properly though the constructor and setter */ @Test void testSetHealth() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); assertEquals(Health.HEALTHY, model.getHealth()); var messageFormat = "Giant giant1, The giant looks %s, alert and saturated."; @@ -53,13 +49,11 @@ void testSetHealth() { } } - /** - * Verify if the fatigue level is set properly though the constructor and setter - */ + /** Verify if the fatigue level is set properly though the constructor and setter */ @Test void testSetFatigue() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); assertEquals(Fatigue.ALERT, model.getFatigue()); var messageFormat = "Giant giant1, The giant looks healthy, %s and saturated."; @@ -70,13 +64,11 @@ void testSetFatigue() { } } - /** - * Verify if the nourishment level is set properly though the constructor and setter - */ + /** Verify if the nourishment level is set properly though the constructor and setter */ @Test void testSetNourishment() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); assertEquals(Nourishment.SATURATED, model.getNourishment()); var messageFormat = "Giant giant1, The giant looks healthy, alert and %s."; @@ -87,13 +79,11 @@ void testSetNourishment() { } } - /** - * Test update model. - */ + /** Test update model. */ @Test void testUpdateModel() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); assertEquals(Nourishment.SATURATED, model.getNourishment()); for (final var nourishment : Nourishment.values()) { diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/AppTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/AppTest.java index 0dce37fca1f3..93b39ec28da4 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/AppTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.servicetoworker; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/DispatcherTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/DispatcherTest.java index 0551f9b96973..dafb12f12512 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/DispatcherTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/DispatcherTest.java @@ -32,18 +32,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; -/** - * The type Dispatcher test. - */ +/** The type Dispatcher test. */ class DispatcherTest { - /** - * Test perform action. - */ + /** Test perform action. */ @Test void testPerformAction() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); GiantView giantView = new GiantView(); Dispatcher dispatcher = new Dispatcher(giantView); @@ -64,13 +60,10 @@ void testPerformAction() { @Test void testUpdateView() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); GiantView giantView = new GiantView(); Dispatcher dispatcher = new Dispatcher(giantView); assertDoesNotThrow(() -> dispatcher.updateView(model)); } } - - - diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantControllerTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantControllerTest.java index ce7334b97b16..a4b9c35eb1e0 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantControllerTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantControllerTest.java @@ -32,19 +32,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; - -/** - * The type Giant controller test. - */ +/** The type Giant controller test. */ class GiantControllerTest { - /** - * Test set command. - */ + /** Test set command. */ @Test void testSetCommand() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); Action action = new Action(model); GiantView giantView = new GiantView(); Dispatcher dispatcher = new Dispatcher(giantView); @@ -57,17 +52,14 @@ void testSetCommand() { assertEquals(Nourishment.HUNGRY, model.getNourishment()); } - /** - * Test update view. - */ + /** Test update view. */ @Test void testUpdateView() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); GiantView giantView = new GiantView(); Dispatcher dispatcher = new Dispatcher(giantView); GiantController giantController = new GiantController(dispatcher); assertDoesNotThrow(() -> giantController.updateView(model)); } - -} \ No newline at end of file +} diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantModelTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantModelTest.java index b45ba99d4922..c6053e608166 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantModelTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantModelTest.java @@ -31,18 +31,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; -/** - * The type Giant model test. - */ +/** The type Giant model test. */ class GiantModelTest { - /** - * Verify if the health value is set properly though the constructor and setter - */ + /** Verify if the health value is set properly though the constructor and setter */ @Test void testSetHealth() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); assertEquals(Health.HEALTHY, model.getHealth()); var messageFormat = "Giant giant1, The giant looks %s, alert and saturated."; for (final var health : Health.values()) { @@ -52,13 +48,11 @@ void testSetHealth() { } } - /** - * Verify if the fatigue level is set properly though the constructor and setter - */ + /** Verify if the fatigue level is set properly though the constructor and setter */ @Test void testSetFatigue() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); assertEquals(Fatigue.ALERT, model.getFatigue()); var messageFormat = "Giant giant1, The giant looks healthy, %s and saturated."; for (final var fatigue : Fatigue.values()) { @@ -68,13 +62,11 @@ void testSetFatigue() { } } - /** - * Verify if the nourishment level is set properly though the constructor and setter - */ + /** Verify if the nourishment level is set properly though the constructor and setter */ @Test void testSetNourishment() { - final var model = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + final var model = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); assertEquals(Nourishment.SATURATED, model.getNourishment()); var messageFormat = "Giant giant1, The giant looks healthy, alert and %s."; for (final var nourishment : Nourishment.values()) { diff --git a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantViewTest.java b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantViewTest.java index 8f5f3bb359e9..fd4f39be9e4c 100644 --- a/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantViewTest.java +++ b/service-to-worker/src/test/java/com/iluwatar/servicetoworker/GiantViewTest.java @@ -31,18 +31,14 @@ import com.iluwatar.model.view.controller.Nourishment; import org.junit.jupiter.api.Test; -/** - * The type Giant view test. - */ +/** The type Giant view test. */ class GiantViewTest { - /** - * Test display giant. - */ + /** Test display giant. */ @Test void testDispalyGiant() { - GiantModel giantModel = new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, - Nourishment.SATURATED); + GiantModel giantModel = + new GiantModel("giant1", Health.HEALTHY, Fatigue.ALERT, Nourishment.SATURATED); GiantView giantView = new GiantView(); assertDoesNotThrow(() -> giantView.displayGiant(giantModel)); } diff --git a/session-facade/README.md b/session-facade/README.md new file mode 100644 index 000000000000..fb8fe74d7f50 --- /dev/null +++ b/session-facade/README.md @@ -0,0 +1,166 @@ +--- +title: "Session Facade Pattern in Java: Simplifying Complex System Interfaces" +shortTitle: Session Facade +description: "Learn how to implement the Session Facade Design Pattern in Java to create a unified interface for complex subsystems. Simplify your code and enhance maintainability with practical examples and use cases." +category: Structural +language: en +tag: + - API design + - Abstraction + - Architecture + - Business + - Decoupling + - Enterprise patterns + - Facade + - Layered architecture + - Session management +--- + +## Also known as + +* Remote Facade + +## Intent of Session Facade Design Pattern + +Provide a simplified interface to a complex subsystem, reducing complexity and coupling between client and business logic in enterprise Java applications. + +## Detailed Explanation of Session Facade Pattern with Real-World Examples + +Real-world example + +> A real-world analogy for the Session Facade pattern is a hotel concierge service. Guests (clients) don't directly interact with various departments like housekeeping, kitchen, transport services, or maintenance. Instead, they interact with the concierge (the facade), who simplifies these interactions. When a guest requests services like room cleaning, dinner reservations, or taxi bookings, the concierge handles communication with multiple hotel departments behind the scenes, providing a simplified and unified interface to the guest, reducing complexity and enhancing guest satisfaction. + +In plain words + +> Session Facade provides a simplified interface to complex business logic in Java applications, reducing client complexity and minimizing network overhead by encapsulating interactions within a single session component. + +Sequence diagram + +![Session Facade sequence diagram](./etc/session-facade-sequence-diagram.png) + +## Programmatic Example of Session Facade Pattern in Java + +The Session Facade pattern is a structural design pattern that provides a simplified interface to complex subsystems, making the system easier for clients to interact with. It is especially useful when a client needs access to multiple underlying services without needing to understand their internal complexities. + +In the context of an e-commerce website, consider a scenario where users browse products, manage their shopping carts, place orders, and process payments. Rather than directly interacting with each subsystem individually (such as the cart, order, and payment systems), a client can communicate through a single unified Session Facade interface. + +### Example Scenario: + +In this example, the `ShoppingFacade` class simplifies client interactions with three services: the `CartService`, `OrderService`, and `PaymentService`. The client uses the facade to perform high-level operations like adding products to the cart, placing an order, and choosing a payment method, without needing to know the underlying details. + +Here's a simplified Java program demonstrating this pattern: + +```java +public class App { + public static void main(String[] args) { + ShoppingFacade shoppingFacade = new ShoppingFacade(); + shoppingFacade.addToCart(1); + shoppingFacade.order(); + shoppingFacade.selectPaymentMethod("cash"); + } +} +``` + +The `ShoppingFacade` serves as a centralized point of interaction for various shopping-related operations, thereby reducing direct coupling between client code and individual subsystem services: + +```java +public class ShoppingFacade { + + private final CartService cartService; + private final OrderService orderService; + private final PaymentService paymentService; + + public ShoppingFacade() { + Map productCatalog = new HashMap<>(); + productCatalog.put(1, new Product(1, "Wireless Mouse", 25.99, "Ergonomic wireless mouse with USB receiver.")); + productCatalog.put(2, new Product(2, "Gaming Keyboard", 79.99, "RGB mechanical gaming keyboard with programmable keys.")); + Map cart = new HashMap<>(); + cartService = new CartService(cart, productCatalog); + orderService = new OrderService(cart); + paymentService = new PaymentService(); + } + + public Map getCart() { + return this.cartService.getCart(); + } + + public void addToCart(int productId) { + this.cartService.addToCart(productId); + } + + + public void removeFromCart(int productId) { + this.cartService.removeFromCart(productId); + } + + public void order() { + this.orderService.order(); + } + + public Boolean isPaymentRequired() { + double total = this.orderService.getTotal(); + if (total==0.0) { + LOGGER.info("No payment required"); + return false; + } + return true; + } + + public void processPayment(String method) { + Boolean isPaymentRequired = isPaymentRequired(); + if (Boolean.TRUE.equals(isPaymentRequired)) { + paymentService.selectPaymentMethod(method); + } + } +} +``` + +### Console Output + +When running the provided example (App.main()), the output might look similar to: + +``` +19:43:17.883 [main] INFO com.iluwatar.sessionfacade.CartService -- ID: 1 +Name: Wireless Mouse +Price: $25.99 +Description: Ergonomic wireless mouse with USB receiver. successfully added to the cart +19:43:17.910 [main] INFO com.iluwatar.sessionfacade.OrderService -- Client has chosen to order [ID: 1 +``` + +This simplified example demonstrates the essence of the Session Facade pattern. Your actual implementation may vary based on the specific needs of your application. + +## When to Use the Session Facade Pattern in Java + +* When dealing with complex enterprise applications containing multiple business objects. +* To provide simplified API calls to clients, hiding the underlying complexity. +* When seeking improved performance and reduced network calls between clients and servers. + +## Real-World Applications of Server Session Pattern in Java + +* Java EE applications utilizing Enterprise JavaBeans (EJB) as session facades to encapsulate business logic. +* Spring-based applications using services as session facades to simplify interactions between controllers and repositories. + +## Benefits and Trade-offs of Server Session Pattern + +Benefits: + +* Reduces complexity by providing a simpler interface to a subsystem. +* Improves performance by minimizing network traffic and reducing remote calls. +* Enhances modularity and maintainability by clearly separating business logic and client interactions. + +Trade-offs: + +* Can introduce additional layers that might increase initial complexity. +* Risk of creating overly broad facades that violate single responsibility principles. + +## Related Java Design Patterns + +* [Data Transfer Object (DTO)](https://java-design-patterns.com/patterns/data-transfer-object/): Often used together, Session Facade simplifies data transfer by utilizing DTOs to encapsulate data passed between client and server. +* [Facade](https://java-design-patterns.com/patterns/facade/): Session Facade is a specialized version of the Facade pattern, applied specifically in enterprise systems to manage business logic and remote interactions. + +## References and Credits + +* [Core J2EE Patterns: Best Practices and Design Strategies](https://amzn.to/4cAbDap) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Real World Java EE Patterns-Rethinking Best Practices](https://amzn.to/3EvkzS8) +* [Remote Facade (Martin Fowler)](https://martinfowler.com/eaaCatalog/remoteFacade.html) diff --git a/session-facade/etc/session-facade-sequence-diagram.png b/session-facade/etc/session-facade-sequence-diagram.png new file mode 100644 index 000000000000..b70d461ce0c4 Binary files /dev/null and b/session-facade/etc/session-facade-sequence-diagram.png differ diff --git a/session-facade/etc/session-facade.urm.png b/session-facade/etc/session-facade.urm.png new file mode 100644 index 000000000000..71ab9a0481e0 Binary files /dev/null and b/session-facade/etc/session-facade.urm.png differ diff --git a/session-facade/etc/session-facade.urm.puml b/session-facade/etc/session-facade.urm.puml new file mode 100644 index 000000000000..83eed750ed0c --- /dev/null +++ b/session-facade/etc/session-facade.urm.puml @@ -0,0 +1,48 @@ +@startuml +package com.iluwatar.sessionfacade { + class App { + + App() + + main(args : String[]) {static} + } + class CartService { + - LOGGER : Logger {static} + - cart : List + - productCatalog : List + + CartService(cart : List, productCatalog : List) + + addToCart(productId : int) + + removeFromCart(productId : int) + } + class OrderService { + - LOGGER : Logger {static} + - cart : List + + OrderService(cart : List) + + order() + } + class PaymentService { + + LOGGER : Logger {static} + + PaymentService() + + cashPayment() + + creditCardPayment() + + selectPaymentMethod(method : String) + } + class ProductCatalogService { + - products : List + + ProductCatalogService(products : List) + } + class ShoppingFacade { + ~ cart : List + ~ cartService : CartService + ~ orderService : OrderService + ~ paymentService : PaymentService + ~ productCatalog : List + + ShoppingFacade() + + addToCart(productId : int) + + order() + + removeFromCart(productId : int) + + selectPaymentMethod(method : String) + } +} +ShoppingFacade --> "-cartService" CartService +ShoppingFacade --> "-paymentService" PaymentService +ShoppingFacade --> "-orderService" OrderService +@enduml \ No newline at end of file diff --git a/session-facade/pom.xml b/session-facade/pom.xml new file mode 100644 index 000000000000..befdb72d7423 --- /dev/null +++ b/session-facade/pom.xml @@ -0,0 +1,83 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + session-facade + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.mockito + mockito-core + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.sessionfacade.App + + + + + + + + + \ No newline at end of file diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/App.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/App.java new file mode 100644 index 000000000000..cd9194aad20f --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/App.java @@ -0,0 +1,58 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +/** + * The main entry point of the application that demonstrates the usage of the ShoppingFacade to + * manage the shopping process using the Session Facade pattern. This class serves as a client that + * interacts with the simplified interface provided by the ShoppingFacade, which encapsulates + * complex interactions with the underlying business services. The ShoppingFacade acts as a session + * bean that coordinates the communication between multiple services, hiding their complexity and + * providing a single, unified API. + */ +public class App { + /** + * The entry point of the application. This method demonstrates how the ShoppingFacade, acting as + * a Session Facade, is used to: - Add items to the shopping cart - Process a payment - Place the + * order The session facade manages the communication between the individual services and + * simplifies the interactions for the client. + * + * @param args the input arguments + */ + public static void main(String[] args) { + ShoppingFacade shoppingFacade = new ShoppingFacade(); + + // Adding items to the shopping cart + shoppingFacade.addToCart(1); + shoppingFacade.addToCart(2); + + // Processing the payment with the chosen method + shoppingFacade.processPayment("cash"); + + // Finalizing the order + shoppingFacade.order(); + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/CartService.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/CartService.java new file mode 100644 index 000000000000..00cfbb540aeb --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/CartService.java @@ -0,0 +1,81 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +import java.util.Map; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * The type Cart service. Represents the cart entity, has add to cart and remove from cart methods + */ +@Slf4j +public class CartService { + /** -- GETTER -- Gets cart. */ + @Getter private final Map cart; + + private final Map productCatalog; + + /** + * Instantiates a new Cart service. + * + * @param cart the cart + * @param productCatalog the product catalog + */ + public CartService(Map cart, Map productCatalog) { + this.cart = cart; + this.productCatalog = productCatalog; + } + + /** + * Add to cart. + * + * @param productId the product id + */ + public void addToCart(int productId) { + Product product = productCatalog.get(productId); + if (product != null) { + cart.put(productId, product); + LOGGER.info("{} successfully added to the cart", product); + } else { + LOGGER.info("No product is found in catalog with id {}", productId); + } + } + + /** + * Remove from cart. + * + * @param productId the product id + */ + public void removeFromCart(int productId) { + Product product = cart.remove(productId); // Remove product from cart + if (product != null) { + LOGGER.info("{} successfully removed from the cart", product); + } else { + LOGGER.info("No product is found in cart with id {}", productId); + } + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/OrderService.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/OrderService.java new file mode 100644 index 000000000000..a67dda0aae5d --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/OrderService.java @@ -0,0 +1,78 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** + * The OrderService class is responsible for finalizing a customer's order. It includes a method to + * calculate the total cost of the order, which follows the information expert principle from GRASP + * by assigning the responsibility of total calculation to this service. Additionally, it provides a + * method to complete the order, which empties the client's shopping cart once the order is + * finalized. + */ +@Slf4j +public class OrderService { + private final Map cart; + + /** + * Instantiates a new Order service. + * + * @param cart the cart + */ + public OrderService(Map cart) { + this.cart = cart; + } + + /** Order. */ + public void order() { + Double total = getTotal(); + if (!this.cart.isEmpty()) { + LOGGER.info( + "Client has chosen to order {} with total {}", cart, String.format("%.2f", total)); + this.completeOrder(); + } else { + LOGGER.info("Client's shopping cart is empty"); + } + } + + /** + * Gets total. + * + * @return the total + */ + public double getTotal() { + final double[] total = {0.0}; + this.cart.forEach((key, product) -> total[0] += product.price()); + return total[0]; + } + + /** Complete order. */ + public void completeOrder() { + this.cart.clear(); + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/PaymentService.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/PaymentService.java new file mode 100644 index 000000000000..92b6bf5fa5bf --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/PaymentService.java @@ -0,0 +1,67 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The PaymentService class is responsible for handling the selection and processing of different + * payment methods. It provides functionality to select a payment method (cash or credit card) and + * process the corresponding payment option. The class uses logging to inform the client of the + * selected payment method. It includes methods to: - Select the payment method based on the + * client's choice. - Process cash payments through the `cashPayment()` method. - Process credit + * card payments through the `creditCardPayment()` method. + */ +public class PaymentService { + /** The constant LOGGER. */ + public static Logger LOGGER = LoggerFactory.getLogger(PaymentService.class); + + /** + * Select payment method. + * + * @param method the method + */ + public void selectPaymentMethod(String method) { + if (method.equals("cash")) { + cashPayment(); + } else if (method.equals("credit")) { + creditCardPayment(); + } else { + LOGGER.info("Unspecified payment method type"); + } + } + + /** Cash payment. */ + public void cashPayment() { + LOGGER.info("Client have chosen cash payment option"); + } + + /** Credit card payment. */ + public void creditCardPayment() { + LOGGER.info("Client have chosen credit card payment option"); + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/Product.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/Product.java new file mode 100644 index 000000000000..ff4b8afd1868 --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/Product.java @@ -0,0 +1,34 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +/** The type Product. */ +public record Product(int id, String name, double price, String description) { + @Override + public String toString() { + return "ID: " + id + "\nName: " + name + "\nPrice: $" + price + "\nDescription: " + description; + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/ProductCatalogService.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/ProductCatalogService.java new file mode 100644 index 000000000000..b892c32e9050 --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/ProductCatalogService.java @@ -0,0 +1,59 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +import java.util.Map; + +/** + * The type ProductCatalogService. This class manages a catalog of products. It holds a map of + * products, where each product is identified by a unique ID. The class provides functionality to + * access and manage the products in the catalog. + */ +public class ProductCatalogService { + + private final Map products; + + /** + * Instantiates a new ProductCatalogService. + * + * @param products the map of products to be used by this service + */ + public ProductCatalogService(Map products) { + this.products = products; + } + + // Additional methods to interact with products can be added here, for example: + + /** + * Retrieves a product by its ID. + * + * @param id the product ID + * @return the product corresponding to the ID + */ + public Product getProductById(int id) { + return products.get(id); + } +} diff --git a/session-facade/src/main/java/com/iluwatar/sessionfacade/ShoppingFacade.java b/session-facade/src/main/java/com/iluwatar/sessionfacade/ShoppingFacade.java new file mode 100644 index 000000000000..cdfe44a7c292 --- /dev/null +++ b/session-facade/src/main/java/com/iluwatar/sessionfacade/ShoppingFacade.java @@ -0,0 +1,121 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.sessionfacade; + +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; + +/** + * The ShoppingFacade class provides a simplified interface for clients to interact with the + * shopping system. It acts as a facade to handle operations related to a shopping cart, order + * processing, and payment. Responsibilities: - Add products to the shopping cart. - Remove products + * from the shopping cart. - Retrieve the current shopping cart. - Finalize an order by calling the + * order service. - Check if a payment is required based on the order total. - Process payment using + * different payment methods (e.g., cash, credit card). The ShoppingFacade class delegates + * operations to the following services: - CartService: Manages the cart and product catalog. - + * OrderService: Handles the order finalization process and calculation of the total. - + * PaymentService: Handles the payment processing based on the selected payment method. + */ +@Slf4j +public class ShoppingFacade { + private final CartService cartService; + private final OrderService orderService; + private final PaymentService paymentService; + + /** Instantiates a new Shopping facade. */ + public ShoppingFacade() { + Map productCatalog = new HashMap<>(); + productCatalog.put( + 1, new Product(1, "Wireless Mouse", 25.99, "Ergonomic wireless mouse with USB receiver.")); + productCatalog.put( + 2, + new Product( + 2, "Gaming Keyboard", 79.99, "RGB mechanical gaming keyboard with programmable keys.")); + Map cart = new HashMap<>(); + cartService = new CartService(cart, productCatalog); + orderService = new OrderService(cart); + paymentService = new PaymentService(); + } + + /** + * Gets cart. + * + * @return the cart + */ + public Map getCart() { + return this.cartService.getCart(); + } + + /** + * Add to cart. + * + * @param productId the product id + */ + public void addToCart(int productId) { + this.cartService.addToCart(productId); + } + + /** + * Remove from cart. + * + * @param productId the product id + */ + public void removeFromCart(int productId) { + this.cartService.removeFromCart(productId); + } + + /** Order. */ + public void order() { + this.orderService.order(); + } + + /** + * Is payment required boolean. + * + * @return the boolean + */ + public Boolean isPaymentRequired() { + double total = this.orderService.getTotal(); + if (total == 0.0) { + LOGGER.info("No payment required"); + return false; + } + return true; + } + + /** + * Process payment. + * + * @param method the method + */ + public void processPayment(String method) { + Boolean isPaymentRequired = isPaymentRequired(); + if (Boolean.TRUE.equals(isPaymentRequired)) { + paymentService.selectPaymentMethod(method); + } + } +} diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java new file mode 100644 index 000000000000..556c9536d0e9 --- /dev/null +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/AppTest.java @@ -0,0 +1,37 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionfacade; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +/** The type App test. */ +class AppTest { + + /** Should execute application without exception. */ + @org.junit.jupiter.api.Test + void shouldExecuteApplicationWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/CartServiceTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/CartServiceTest.java new file mode 100644 index 000000000000..ef42fcabd963 --- /dev/null +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/CartServiceTest.java @@ -0,0 +1,84 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionfacade; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; + +/** The type Cart service test. */ +@Slf4j +class CartServiceTest { + + private CartService cartService; + private Map cart; + + /** Sets up. */ + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + cart = new HashMap<>(); + Map productCatalog = new HashMap<>(); + productCatalog.put(1, new Product(1, "Product A", 2.0, "any description")); + productCatalog.put(2, new Product(2, "Product B", 300.0, "a watch")); + cartService = new CartService(cart, productCatalog); + } + + /** Test add to cart. */ + @Test + void testAddToCart() { + cartService.addToCart(1); + assertEquals(1, cart.size()); + assertEquals("Product A", cart.get(1).name()); + } + + /** Test remove from cart. */ + @Test + void testRemoveFromCart() { + cartService.addToCart(1); + assertEquals(1, cart.size()); + cartService.removeFromCart(1); + assertTrue(cart.isEmpty()); + } + + /** Test add to cart with invalid product id. */ + @Test + void testAddToCartWithInvalidProductId() { + cartService.addToCart(999); + assertTrue(cart.isEmpty()); + } + + /** Test remove from cart with invalid product id. */ + @Test + void testRemoveFromCartWithInvalidProductId() { + cartService.removeFromCart(999); + assertTrue(cart.isEmpty()); + } +} diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java new file mode 100644 index 000000000000..06852f6d3b31 --- /dev/null +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/PaymentServiceTest.java @@ -0,0 +1,57 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionfacade; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.slf4j.Logger; + +/** The type Payment service test. */ +class PaymentServiceTest { + private PaymentService paymentService; + private Logger mockLogger; + + /** Sets up. */ + @BeforeEach + void setUp() { + paymentService = new PaymentService(); + mockLogger = mock(Logger.class); + paymentService.LOGGER = mockLogger; + } + + @ParameterizedTest + @CsvSource({ + "cash, Client have chosen cash payment option", + "credit, Client have chosen credit card payment option", + "cheque, Unspecified payment method type" + }) + void testSelectPaymentMethod(String method, String expectedLogMessage) { + paymentService.selectPaymentMethod(method); + verify(mockLogger).info(expectedLogMessage); + } +} diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/ProductTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/ProductTest.java new file mode 100644 index 000000000000..3da660ca8f73 --- /dev/null +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/ProductTest.java @@ -0,0 +1,68 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionfacade; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +/** The type Product test. */ +public class ProductTest { + /** Test product creation. */ + @Test + public void testProductCreation() { + int id = 1; + String name = "Product A"; + double price = 200.0; + String description = "a description"; + Product product = new Product(id, name, price, description); + assertEquals(id, product.id()); + assertEquals(name, product.name()); + assertEquals(price, product.price()); + assertEquals(description, product.description()); + } + + /** Test equals and hash code. */ + @Test + public void testEqualsAndHashCode() { + Product product1 = new Product(1, "Product A", 99.99, "a description"); + Product product2 = new Product(1, "Product A", 99.99, "a description"); + Product product3 = new Product(2, "Product B", 199.99, "a description"); + + assertEquals(product1, product2); + assertNotEquals(product1, product3); + assertEquals(product1.hashCode(), product2.hashCode()); + assertNotEquals(product1.hashCode(), product3.hashCode()); + } + + /** Test to string. */ + @Test + public void testToString() { + Product product = new Product(1, "Product A", 99.99, "a description"); + String toStringResult = product.toString(); + assertTrue(toStringResult.contains("Product A")); + assertTrue(toStringResult.contains("99.99")); + } +} diff --git a/session-facade/src/test/java/com/iluwatar/sessionfacade/ShoppingFacadeTest.java b/session-facade/src/test/java/com/iluwatar/sessionfacade/ShoppingFacadeTest.java new file mode 100644 index 000000000000..b77e60296ce0 --- /dev/null +++ b/session-facade/src/test/java/com/iluwatar/sessionfacade/ShoppingFacadeTest.java @@ -0,0 +1,77 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.sessionfacade; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Unit tests for ShoppingFacade. */ +class ShoppingFacadeTest { + + private ShoppingFacade shoppingFacade; + + @BeforeEach + void setUp() { + shoppingFacade = new ShoppingFacade(); + } + + @Test + void testAddToCart() { + shoppingFacade.addToCart(1); + shoppingFacade.addToCart(2); + Map cart = shoppingFacade.getCart(); + assertEquals(2, cart.size(), "Cart should contain two items."); + assertEquals( + "Wireless Mouse", cart.get(1).name(), "First item in the cart should be 'Wireless Mouse'."); + assertEquals( + "Gaming Keyboard", + cart.get(2).name(), + "Second item in the cart should be 'Gaming Keyboard'."); + } + + @Test + void testRemoveFromCart() { + shoppingFacade.addToCart(1); + shoppingFacade.addToCart(2); + shoppingFacade.removeFromCart(1); + Map cart = shoppingFacade.getCart(); + assertEquals(1, cart.size(), "Cart should contain one item after removal."); + assertEquals( + "Gaming Keyboard", cart.get(2).name(), "Remaining item should be 'Gaming Keyboard'."); + } + + @Test + void testOrder() { + shoppingFacade.addToCart(1); + shoppingFacade.addToCart(2); + shoppingFacade.processPayment("cash"); + shoppingFacade.order(); + assertTrue(shoppingFacade.getCart().isEmpty(), "Cart should be empty after placing the order."); + } +} diff --git a/sharding/README.md b/sharding/README.md index fef24abee7b6..19915038d546 100644 --- a/sharding/README.md +++ b/sharding/README.md @@ -35,6 +35,10 @@ Wikipedia says > > There are numerous advantages to the horizontal partitioning approach. Since the tables are divided and distributed into multiple servers, the total number of rows in each table in each database is reduced. This reduces index size, which generally improves search performance. A database shard can be placed on separate hardware, and multiple shards can be placed on multiple machines. This enables a distribution of the database over a large number of machines, greatly improving performance. In addition, if the database shard is based on some real-world segmentation of the data (e.g., European customers v. American customers) then it may be possible to infer the appropriate shard membership easily and automatically, and query only the relevant shard. +Flowchart + +![Sharding flowchart](./etc/sharding-flowchart.png) + ## Programmatic Example of Sharding Pattern in Java Sharding is a type of database partitioning that separates very large databases into smaller, faster, more easily managed parts called data shards. The word shard means a small part of a whole. In software architecture, it refers to a horizontal partition in a database or search engine. Each individual partition is referred to as a shard or database shard. diff --git a/sharding/etc/sharding-flowchart.png b/sharding/etc/sharding-flowchart.png new file mode 100644 index 000000000000..c43f100b308d Binary files /dev/null and b/sharding/etc/sharding-flowchart.png differ diff --git a/sharding/pom.xml b/sharding/pom.xml index 2edd3a458ba7..8461ccf5fad0 100644 --- a/sharding/pom.xml +++ b/sharding/pom.xml @@ -34,6 +34,14 @@ 4.0.0 sharding + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/sharding/src/main/java/com/iluwatar/sharding/App.java b/sharding/src/main/java/com/iluwatar/sharding/App.java index f8c9ce2f85b9..56b0edb2f2db 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/App.java +++ b/sharding/src/main/java/com/iluwatar/sharding/App.java @@ -85,5 +85,4 @@ public static void main(String[] args) { shard2.clearData(); shard3.clearData(); } - } diff --git a/sharding/src/main/java/com/iluwatar/sharding/Data.java b/sharding/src/main/java/com/iluwatar/sharding/Data.java index 546c969dfb10..c0b4188d182a 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/Data.java +++ b/sharding/src/main/java/com/iluwatar/sharding/Data.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.Setter; -/** - * Basic data structure for each tuple stored in data shards. - */ +/** Basic data structure for each tuple stored in data shards. */ @Getter @Setter public class Data { @@ -42,8 +40,9 @@ public class Data { /** * Constructor of Data class. + * * @param key data key - * @param value data vlue + * @param value data value * @param type data type */ public Data(final int key, final String value, final DataType type) { @@ -53,14 +52,13 @@ public Data(final int key, final String value, final DataType type) { } enum DataType { - TYPE_1, TYPE_2, TYPE_3 + TYPE_1, + TYPE_2, + TYPE_3 } @Override public String toString() { - return "Data {" + "key=" - + key + ", value='" + value - + '\'' + ", type=" + type + '}'; + return "Data {" + "key=" + key + ", value='" + value + '\'' + ", type=" + type + '}'; } } - diff --git a/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java index 48961b8a1265..c0d481a0d950 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/HashShardManager.java @@ -27,10 +27,9 @@ import lombok.extern.slf4j.Slf4j; /** - * ShardManager with hash strategy. The purpose of this strategy is to reduce the - * chance of hot-spots in the data. It aims to distribute the data across the shards - * in a way that achieves a balance between the size of each shard and the average - * load that each shard will encounter. + * ShardManager with hash strategy. The purpose of this strategy is to reduce the chance of + * hot-spots in the data. It aims to distribute the data across the shards in a way that achieves a + * balance between the size of each shard and the average load that each shard will encounter. */ @Slf4j public class HashShardManager extends ShardManager { @@ -50,5 +49,4 @@ protected int allocateShard(Data data) { var hash = data.getKey() % shardCount; return hash == 0 ? hash + shardCount : hash; } - } diff --git a/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java index 35195c9db5b1..f9a5c05d6d37 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/LookupShardManager.java @@ -30,9 +30,8 @@ import lombok.extern.slf4j.Slf4j; /** - * ShardManager with lookup strategy. In this strategy the sharding logic implements - * a map that routes a request for data to the shard that contains that data by using - * the shard key. + * ShardManager with lookup strategy. In this strategy the sharding logic implements a map that + * routes a request for data to the shard that contains that data by using the shard key. */ @Slf4j public class LookupShardManager extends ShardManager { @@ -59,5 +58,4 @@ protected int allocateShard(Data data) { return new SecureRandom().nextInt(shardCount - 1) + 1; } } - } diff --git a/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java index 13596b356a48..093b29910f73 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/RangeShardManager.java @@ -51,5 +51,4 @@ protected int allocateShard(Data data) { case TYPE_3 -> 3; }; } - } diff --git a/sharding/src/main/java/com/iluwatar/sharding/Shard.java b/sharding/src/main/java/com/iluwatar/sharding/Shard.java index 535399e74fe4..7ed2cda14e31 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/Shard.java +++ b/sharding/src/main/java/com/iluwatar/sharding/Shard.java @@ -28,13 +28,10 @@ import java.util.Map; import lombok.Getter; -/** - * The Shard class stored data in a HashMap. - */ +/** The Shard class stored data in a HashMap. */ public class Shard { - @Getter - private final int id; + @Getter private final int id; private final Map dataStore; diff --git a/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java b/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java index a87b58d04918..8283b2c6781e 100644 --- a/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java +++ b/sharding/src/main/java/com/iluwatar/sharding/ShardManager.java @@ -28,9 +28,7 @@ import java.util.Map; import lombok.extern.slf4j.Slf4j; -/** - * Abstract class for ShardManager. - */ +/** Abstract class for ShardManager. */ @Slf4j public abstract class ShardManager { @@ -44,8 +42,8 @@ public ShardManager() { * Add a provided shard instance to shardMap. * * @param shard new shard instance. - * @return {@code true} if succeed to add the new instance. - * {@code false} if the shardId is already existed. + * @return {@code true} if succeed to add the new instance. {@code false} if the shardId is + * already existed. */ public boolean addNewShard(final Shard shard) { var shardId = shard.getId(); @@ -97,5 +95,4 @@ public Shard getShardById(final int shardId) { * @return id of shard that the data should be stored */ protected abstract int allocateShard(final Data data); - } diff --git a/sharding/src/test/java/com/iluwatar/sharding/AppTest.java b/sharding/src/test/java/com/iluwatar/sharding/AppTest.java index fc3c59073beb..c6d68aad17ab 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/AppTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/AppTest.java @@ -28,14 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Unit tests for App class. - */ +/** Unit tests for App class. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } - } diff --git a/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java index b5b5202dc2c3..d01d3406e153 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/HashShardManagerTest.java @@ -29,16 +29,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Unit tests for HashShardManager class. - */ +/** Unit tests for HashShardManager class. */ class HashShardManagerTest { private HashShardManager hashShardManager; - /** - * Initialize hashShardManager instance. - */ + /** Initialize hashShardManager instance. */ @BeforeEach void setup() { hashShardManager = new HashShardManager(); @@ -56,5 +52,4 @@ void testStoreData() { hashShardManager.storeData(data); assertEquals(data, hashShardManager.getShardById(1).getDataById(1)); } - } diff --git a/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java index a8b8ab1da55d..d4b3c02dc34d 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/LookupShardManagerTest.java @@ -31,16 +31,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Unit tests for LookupShardManager class. - */ +/** Unit tests for LookupShardManager class. */ class LookupShardManagerTest { private LookupShardManager lookupShardManager; - /** - * Initialize lookupShardManager instance. - */ + /** Initialize lookupShardManager instance. */ @BeforeEach void setup() { lookupShardManager = new LookupShardManager(); diff --git a/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java index 299adc622bac..dbc8a68ba292 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/RangeShardManagerTest.java @@ -29,16 +29,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Unit tests for RangeShardManager class. - */ +/** Unit tests for RangeShardManager class. */ class RangeShardManagerTest { private RangeShardManager rangeShardManager; - /** - * Initialize rangeShardManager instance. - */ + /** Initialize rangeShardManager instance. */ @BeforeEach void setup() { rangeShardManager = new RangeShardManager(); @@ -56,5 +52,4 @@ void testStoreData() { rangeShardManager.storeData(data); assertEquals(data, rangeShardManager.getShardById(1).getDataById(1)); } - } diff --git a/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java b/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java index f5da39ddb34a..fe7cf09c046d 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/ShardManagerTest.java @@ -32,16 +32,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Unit tests for ShardManager class. - */ +/** Unit tests for ShardManager class. */ class ShardManagerTest { private ShardManager shardManager; - /** - * Initialize shardManager instance. - */ + /** Initialize shardManager instance. */ @BeforeEach void setup() { shardManager = new TestShardManager(); diff --git a/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java b/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java index 7b18440a8c8c..81312e9e0270 100644 --- a/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java +++ b/sharding/src/test/java/com/iluwatar/sharding/ShardTest.java @@ -32,10 +32,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -/** - * Unit tests for Shard class. - */ +/** Unit tests for Shard class. */ class ShardTest { private Data data; @@ -60,7 +57,6 @@ void testStoreData() { } catch (NoSuchFieldException | IllegalAccessException e) { fail("Fail to modify field access."); } - } @Test diff --git a/single-table-inheritance/README.md b/single-table-inheritance/README.md index 0ed1f6eadae1..632b93bf3200 100644 --- a/single-table-inheritance/README.md +++ b/single-table-inheritance/README.md @@ -36,6 +36,10 @@ Wikipedia says > Single table inheritance is a way to emulate object-oriented inheritance in a relational database. When mapping from a database table to an object in an object-oriented language, a field in the database identifies what class in the hierarchy the object belongs to. All fields of all the classes are stored in the same table, hence the name "Single Table Inheritance". +Flowchart + +![Single Table Inheritance flowchart](./etc/single-table-inheritance-flowchart.png) + ## Programmatic Example of Single Table Inheritance Pattern in Java Single Table Inheritance is a design pattern that maps an inheritance hierarchy of classes to a single database table. Each row in the table represents an instance of a class in the hierarchy. A special discriminator column is used to identify the class to which each row belongs. diff --git a/single-table-inheritance/etc/single-table-inheritance-flowchart.png b/single-table-inheritance/etc/single-table-inheritance-flowchart.png new file mode 100644 index 000000000000..e70b6c6627d5 Binary files /dev/null and b/single-table-inheritance/etc/single-table-inheritance-flowchart.png differ diff --git a/single-table-inheritance/pom.xml b/single-table-inheritance/pom.xml index a9ea9c70e305..6e3afcf38d69 100644 --- a/single-table-inheritance/pom.xml +++ b/single-table-inheritance/pom.xml @@ -36,18 +36,11 @@ single-table-inheritance - - - - org.springframework.boot - spring-boot-dependencies - pom - 3.2.3 - import - - - + + org.springframework.boot + spring-boot-starter + org.springframework.boot spring-boot-starter-data-jpa @@ -55,17 +48,13 @@ jakarta.xml.bind jakarta.xml.bind-api + 4.0.2 com.h2database h2 runtime - - org.projectlombok - lombok - true - org.springframework.boot spring-boot-starter-test diff --git a/single-table-inheritance/src/main/java/com/iluwatar/SingleTableInheritance.java b/single-table-inheritance/src/main/java/com/iluwatar/SingleTableInheritance.java index 69cc21be6cef..f27b71213a09 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/SingleTableInheritance.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/SingleTableInheritance.java @@ -37,32 +37,28 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; /** - * Single Table Inheritance pattern : + * Single Table Inheritance pattern :
    + * It maps each instance of class in an inheritance tree into a single table.
    + * + *

    In case of current project, in order to specify the Single Table Inheritance to Hibernate we + * annotate the main Vehicle root class with @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + * due to which a single root Vehicle class table will be created in the database and it will + * have columns for all the fields of it's subclasses(Car, Freighter, Train, Truck).
    + * Additional to that, a new separate "vehicle_id" column would be added to the Vehicle table + * to save the type of the subclass object that is being stored in the database. This value is + * specified by the @DiscriminatorValue annotation value for each subclass in case of Hibernate. *
    - * It maps each instance of class in an inheritance tree into a single table. *
    - *

    - * In case of current project, in order to specify the Single Table Inheritance to Hibernate - * we annotate the main Vehicle root class with @Inheritance(strategy = InheritanceType.SINGLE_TABLE) - * due to which a single root Vehicle class table will be created - * in the database and it will have columns for all the fields of - * it's subclasses(Car, Freighter, Train, Truck).
    - * Additional to that, a new separate "vehicle_id" column would be added - * to the Vehicle table to save the type of the subclass object that - * is being stored in the database. This value is specified by the @DiscriminatorValue annotation - * value for each subclass in case of Hibernate.
    - *


    * Below is the main Spring Boot Application class from where the Program Runs. - *

    - * It implements the CommandLineRunner to run the statements at the - * start of the application program. - *

    + * + *

    It implements the CommandLineRunner to run the statements at the start of the application + * program. */ @SpringBootApplication @AllArgsConstructor public class SingleTableInheritance implements CommandLineRunner { - //Autowiring the VehicleService class to execute the business logic methods + // Autowiring the VehicleService class to execute the business logic methods private final VehicleService vehicleService; /** @@ -75,8 +71,7 @@ public static void main(String[] args) { } /** - * The starting point of the CommandLineRunner - * where the main program is run. + * The starting point of the CommandLineRunner where the main program is run. * * @param args program runtime arguments */ @@ -97,7 +92,6 @@ public void run(String... args) { Vehicle truck1 = vehicleService.saveVehicle(vehicle2); log.info("Vehicle 2 saved : {}\n", truck1); - log.info("Fetching Vehicles :- "); // Fetching the Car from DB @@ -114,4 +108,4 @@ public void run(String... args) { List allVehiclesFromDb = vehicleService.getAllVehicles(); allVehiclesFromDb.forEach(s -> log.info(s.toString())); } -} \ No newline at end of file +} diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Car.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Car.java index 932b71af23d2..b934767e1b30 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Car.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Car.java @@ -31,13 +31,12 @@ import lombok.NoArgsConstructor; /** - * A class that extends the PassengerVehicle class - * and provides the concrete inheritance implementation of the Car. + * A class that extends the PassengerVehicle class and provides the concrete inheritance + * implementation of the Car. * * @see PassengerVehicle PassengerVehicle * @see Vehicle Vehicle */ - @Data @NoArgsConstructor @EqualsAndHashCode(callSuper = true) @@ -55,9 +54,6 @@ public Car(String manufacturer, String model, int noOfPassengers, int engineCapa // Overridden the toString method to specify the Vehicle object @Override public String toString() { - return "Car{" - + super.toString() - + '}'; + return "Car{" + super.toString() + '}'; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Freighter.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Freighter.java index 32fcda144b7e..f97343b71db4 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Freighter.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Freighter.java @@ -31,8 +31,8 @@ import lombok.NoArgsConstructor; /** - * A class that extends the TransportVehicle class - * and provides the concrete inheritance implementation of the Car. + * A class that extends the TransportVehicle class and provides the concrete inheritance + * implementation of the Car. * * @see TransportVehicle TransportVehicle * @see Vehicle Vehicle @@ -54,12 +54,6 @@ public Freighter(String manufacturer, String model, int loadCapacity, double fli // Overridden the toString method to specify the Vehicle object @Override public String toString() { - return "Freighter{ " - + super.toString() - + " ," - + "flightLength=" - + flightLength - + '}'; + return "Freighter{ " + super.toString() + " ," + "flightLength=" + flightLength + '}'; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/PassengerVehicle.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/PassengerVehicle.java index f4908c7303fd..b189bbd56dbc 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/PassengerVehicle.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/PassengerVehicle.java @@ -29,8 +29,8 @@ import lombok.NoArgsConstructor; /** - * An abstract class that extends the Vehicle class - * and provides properties for the Passenger type of Vehicles. + * An abstract class that extends the Vehicle class and provides properties for the Passenger type + * of Vehicles. * * @see Vehicle */ diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Train.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Train.java index 00d881054c78..6d220475a6c2 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Train.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Train.java @@ -31,8 +31,8 @@ import lombok.NoArgsConstructor; /** - * A class that extends the PassengerVehicle class - * and provides the concrete inheritance implementation of the Car. + * A class that extends the PassengerVehicle class and provides the concrete inheritance + * implementation of the Car. * * @see PassengerVehicle PassengerVehicle * @see Vehicle Vehicle @@ -54,9 +54,6 @@ public Train(String manufacturer, String model, int noOfPassengers, int noOfCarr // Overridden the toString method to specify the Vehicle object @Override public String toString() { - return "Train{" - + super.toString() - + '}'; + return "Train{" + super.toString() + '}'; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/TransportVehicle.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/TransportVehicle.java index d3c104f3aa4d..a996a9bb7b06 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/TransportVehicle.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/TransportVehicle.java @@ -29,8 +29,8 @@ import lombok.NoArgsConstructor; /** - * An abstract class that extends the Vehicle class - * and provides properties for the Transport type of Vehicles. + * An abstract class that extends the Vehicle class and provides properties for the Transport type + * of Vehicles. * * @see Vehicle */ @@ -45,5 +45,4 @@ protected TransportVehicle(String manufacturer, String model, int loadCapacity) super(manufacturer, model); this.loadCapacity = loadCapacity; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Truck.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Truck.java index 56f2d0f955ec..46ccc29ca710 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Truck.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Truck.java @@ -30,8 +30,8 @@ import lombok.NoArgsConstructor; /** - * A class that extends the PassengerVehicle class - * and provides the concrete inheritance implementation of the Car. + * A class that extends the PassengerVehicle class and provides the concrete inheritance + * implementation of the Car. * * @see TransportVehicle TransportVehicle * @see Vehicle Vehicle @@ -52,11 +52,6 @@ public Truck(String manufacturer, String model, int loadCapacity, int towingCapa // Overridden the toString method to specify the Vehicle object @Override public String toString() { - return "Truck{ " - + super.toString() - + ", " - + "towingCapacity=" - + towingCapacity - + '}'; + return "Truck{ " + super.toString() + ", " + "towingCapacity=" + towingCapacity + '}'; } } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/entity/Vehicle.java b/single-table-inheritance/src/main/java/com/iluwatar/entity/Vehicle.java index 992c4de9cd8b..ed3cf4ae9f08 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/entity/Vehicle.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/entity/Vehicle.java @@ -37,8 +37,8 @@ import lombok.NoArgsConstructor; /** - * An abstract class that is the root of the Vehicle Inheritance hierarchy - * and basic provides properties for all the vehicles. + * An abstract class that is the root of the Vehicle Inheritance hierarchy and basic provides + * properties for all the vehicles. */ @Data @NoArgsConstructor @@ -65,14 +65,13 @@ protected Vehicle(String manufacturer, String model) { @Override public String toString() { return "Vehicle{" - + "vehicleId=" - + vehicleId - + ", manufacturer='" - + manufacturer - + '\'' - + ", model='" - + model - + '}'; + + "vehicleId=" + + vehicleId + + ", manufacturer='" + + manufacturer + + '\'' + + ", model='" + + model + + '}'; } - } diff --git a/single-table-inheritance/src/main/java/com/iluwatar/repository/VehicleRepository.java b/single-table-inheritance/src/main/java/com/iluwatar/repository/VehicleRepository.java index 42ee063ee5c4..1de73abab760 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/repository/VehicleRepository.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/repository/VehicleRepository.java @@ -29,10 +29,8 @@ import org.springframework.stereotype.Repository; /** - * A repository that is extending the JPA Repository - * to provide the default Spring DATA JPA methods for the Vehicle class. + * A repository that is extending the JPA Repository to provide the default Spring DATA JPA methods + * for the Vehicle class. */ @Repository -public interface VehicleRepository extends JpaRepository { - -} +public interface VehicleRepository extends JpaRepository {} diff --git a/single-table-inheritance/src/main/java/com/iluwatar/service/VehicleService.java b/single-table-inheritance/src/main/java/com/iluwatar/service/VehicleService.java index 66946c8139d7..1179590df117 100644 --- a/single-table-inheritance/src/main/java/com/iluwatar/service/VehicleService.java +++ b/single-table-inheritance/src/main/java/com/iluwatar/service/VehicleService.java @@ -31,9 +31,8 @@ import org.springframework.stereotype.Service; /** - * A service class that is used to provide the business logic - * for the Vehicle class and connect to the database to - * perform the CRUD operations on the root Vehicle class. + * A service class that is used to provide the business logic for the Vehicle class and connect to + * the database to perform the CRUD operations on the root Vehicle class. * * @see Vehicle */ @@ -91,5 +90,4 @@ public Vehicle updateVehicle(Vehicle vehicle) { public void deleteVehicle(Vehicle vehicle) { vehicleRepository.delete(vehicle); } - } diff --git a/singleton/README.md b/singleton/README.md index 626a2ed659bf..92505061399e 100644 --- a/singleton/README.md +++ b/singleton/README.md @@ -33,6 +33,10 @@ Wikipedia says > In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. +Sequence diagram + +![Singleton Pattern sequence diagram](./etc/singleton-sequence-diagram.png) + ## Programmatic Example of Singleton Pattern in Java Joshua Bloch, Effective Java 2nd Edition p.18 diff --git a/singleton/etc/singleton-sequence-diagram.png b/singleton/etc/singleton-sequence-diagram.png new file mode 100644 index 000000000000..0de3569a50b6 Binary files /dev/null and b/singleton/etc/singleton-sequence-diagram.png differ diff --git a/singleton/pom.xml b/singleton/pom.xml index c6e7a65ca126..5d6a57326cbc 100644 --- a/singleton/pom.xml +++ b/singleton/pom.xml @@ -34,6 +34,14 @@ singleton + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/singleton/src/main/java/com/iluwatar/singleton/App.java b/singleton/src/main/java/com/iluwatar/singleton/App.java index 1b14f9ad741e..ccc0ff82718f 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/App.java +++ b/singleton/src/main/java/com/iluwatar/singleton/App.java @@ -27,39 +27,40 @@ import lombok.extern.slf4j.Slf4j; /** - *

    Singleton pattern ensures that the class can have only one existing instance per Java - * classloader instance and provides global access to it.

    + * Singleton pattern ensures that the class can have only one existing instance per Java classloader + * instance and provides global access to it. * *

    One of the risks of this pattern is that bugs resulting from setting a singleton up in a - * distributed environment can be tricky to debug since it will work fine if you debug with a - * single classloader. Additionally, these problems can crop up a while after the implementation of - * a singleton, since they may start synchronous and only become async with time, so it may - * not be clear why you are seeing certain changes in behavior.

    + * distributed environment can be tricky to debug since it will work fine if you debug with a single + * classloader. Additionally, these problems can crop up a while after the implementation of a + * singleton, since they may start synchronous and only become async with time, so it may not be + * clear why you are seeing certain changes in behavior. * *

    There are many ways to implement the Singleton. The first one is the eagerly initialized * instance in {@link IvoryTower}. Eager initialization implies that the implementation is thread * safe. If you can afford to give up control of the instantiation moment, then this implementation - * will suit you fine.

    + * will suit you fine. * *

    The other option to implement eagerly initialized Singleton is enum-based Singleton. The * example is found in {@link EnumIvoryTower}. At first glance, the code looks short and simple. * However, you should be aware of the downsides including committing to implementation strategy, * extending the enum class, serializability, and restrictions to coding. These are extensively - * discussed in Stack Overflow: http://programmers.stackexchange.com/questions/179386/what-are-the-downsides-of-implementing - * -a-singleton-with-javas-enum

    + * discussed in Stack Overflow: + * http://programmers.stackexchange.com/questions/179386/what-are-the-downsides-of-implementing + * -a-singleton-with-javas-enum * *

    {@link ThreadSafeLazyLoadedIvoryTower} is a Singleton implementation that is initialized on * demand. The downside is that it is very slow to access since the whole access method is - * synchronized.

    + * synchronized. * - *

    Another Singleton implementation that is initialized on demand is found in - * {@link ThreadSafeDoubleCheckLocking}. It is somewhat faster than {@link - * ThreadSafeLazyLoadedIvoryTower} since it doesn't synchronize the whole access method but only the - * method internals on specific conditions.

    + *

    Another Singleton implementation that is initialized on demand is found in {@link + * ThreadSafeDoubleCheckLocking}. It is somewhat faster than {@link ThreadSafeLazyLoadedIvoryTower} + * since it doesn't synchronize the whole access method but only the method internals on specific + * conditions. * - *

    Yet another way to implement thread-safe lazily initialized Singleton can be found in - * {@link InitializingOnDemandHolderIdiom}. However, this implementation requires at least Java 8 - * API level to work.

    + *

    Yet another way to implement thread-safe lazily initialized Singleton can be found in {@link + * InitializingOnDemandHolderIdiom}. However, this implementation requires at least Java 8 API level + * to work. */ @Slf4j public class App { diff --git a/singleton/src/main/java/com/iluwatar/singleton/BillPughImplementation.java b/singleton/src/main/java/com/iluwatar/singleton/BillPughImplementation.java index 4656cb7624f3..6d784787f735 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/BillPughImplementation.java +++ b/singleton/src/main/java/com/iluwatar/singleton/BillPughImplementation.java @@ -25,43 +25,37 @@ package com.iluwatar.singleton; /** - *

    Bill Pugh Singleton Implementation.

    - * - *

    This implementation of the singleton design pattern takes advantage of the - * Java memory model's guarantees about class initialization. Each class is - * initialized only once, when it is first used. If the class hasn't been used - * yet, it won't be loaded into memory, and no memory will be allocated for - * a static instance. This makes the singleton instance lazy-loaded and thread-safe.

    + * Bill Pugh Singleton Implementation. * + *

    This implementation of the singleton design pattern takes advantage of the Java memory model's + * guarantees about class initialization. Each class is initialized only once, when it is first + * used. If the class hasn't been used yet, it won't be loaded into memory, and no memory will be + * allocated for a static instance. This makes the singleton instance lazy-loaded and thread-safe. */ public final class BillPughImplementation { - /** - * Private constructor to prevent instantiation from outside the class. - */ + /** Private constructor to prevent instantiation from outside the class. */ private BillPughImplementation() { - // private constructor + // to prevent instantiating by Reflection call + if (InstanceHolder.instance != null) { + throw new IllegalStateException("Already initialized."); + } } /** - * The InstanceHolder is a static inner class, and it holds the Singleton instance. - * It is not loaded into memory until the getInstance() method is called. + * The InstanceHolder is a static inner class, and it holds the Singleton instance. It is not + * loaded into memory until the getInstance() method is called. */ private static class InstanceHolder { - /** - * Singleton instance of the class. - */ + /** Singleton instance of the class. */ private static BillPughImplementation instance = new BillPughImplementation(); } /** * Public accessor for the singleton instance. * - *

    - * When this method is called, the InstanceHolder is loaded into memory - * and creates the Singleton instance. This method provides a global access point - * for the singleton instance. - *

    + *

    When this method is called, the InstanceHolder is loaded into memory and creates the + * Singleton instance. This method provides a global access point for the singleton instance. * * @return an instance of the class. */ diff --git a/singleton/src/main/java/com/iluwatar/singleton/EnumIvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/EnumIvoryTower.java index 8130dc55dd70..a33876642993 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/EnumIvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/EnumIvoryTower.java @@ -25,16 +25,14 @@ package com.iluwatar.singleton; /** - *

    Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18

    + * Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18 * - *

    This implementation is thread safe, however adding any other method and its thread safety - * is developers responsibility.

    + *

    This implementation is thread safe, however adding any other method and its thread safety is + * developers responsibility. */ public enum EnumIvoryTower { - /** - * The singleton instance of the class, created by the Java enum singleton pattern. - */ + /** The singleton instance of the class, created by the Java enum singleton pattern. */ INSTANCE; @Override diff --git a/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java b/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java index 6cff5b561a92..9f9a2105789b 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java +++ b/singleton/src/main/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiom.java @@ -25,24 +25,25 @@ package com.iluwatar.singleton; /** - *

    The Initialize-on-demand-holder idiom is a secure way of creating a lazy initialized singleton - * object in Java.

    + * The Initialize-on-demand-holder idiom is a secure way of creating a lazy initialized singleton + * object in Java. * *

    The technique is as lazy as possible and works in all known versions of Java. It takes - * advantage of language guarantees about class initialization, and will therefore work correctly - * in all Java-compliant compilers and virtual machines.

    + * advantage of language guarantees about class initialization, and will therefore work correctly in + * all Java-compliant compilers and virtual machines. * *

    The inner class is referenced no earlier (and therefore loaded no earlier by the class loader) * than the moment that getInstance() is called. Thus, this solution is thread-safe without - * requiring special language constructs (i.e. volatile or synchronized).

    - * + * requiring special language constructs (i.e. volatile or synchronized). */ public final class InitializingOnDemandHolderIdiom { - /** - * Private constructor. - */ + /** Private constructor. */ private InitializingOnDemandHolderIdiom() { + // to prevent instantiating by Reflection call + if (HelperHolder.INSTANCE != null) { + throw new IllegalStateException("Already initialized."); + } } /** @@ -54,14 +55,10 @@ public static InitializingOnDemandHolderIdiom getInstance() { return HelperHolder.INSTANCE; } - /** - * Provides the lazy-loaded Singleton instance. - */ + /** Provides the lazy-loaded Singleton instance. */ private static class HelperHolder { - /** - * Singleton instance of the class. - */ + /** Singleton instance of the class. */ private static final InitializingOnDemandHolderIdiom INSTANCE = new InitializingOnDemandHolderIdiom(); } diff --git a/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java index f27467272762..fc89ab312dd5 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/IvoryTower.java @@ -1,51 +1,49 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.singleton; - -/** - * Singleton class. Eagerly initialized static instance guarantees thread safety. - */ -public final class IvoryTower { - - /** - * Private constructor so nobody can instantiate the class. - */ - private IvoryTower() { - } - - /** - * Static to class instance of the class. - */ - private static final IvoryTower INSTANCE = new IvoryTower(); - - /** - * To be called by user to obtain instance of the class. - * - * @return instance of the singleton. - */ - public static IvoryTower getInstance() { - return INSTANCE; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.singleton; + +/** Singleton class. Eagerly initialized static instance guarantees thread safety. */ +public final class IvoryTower { + + /** Private constructor so nobody can instantiate the class. */ + private IvoryTower() { + // to prevent instantiating by Reflection call + if (INSTANCE != null) { + throw new IllegalStateException("Already initialized."); + } + } + + /** Static to class instance of the class. */ + private static final IvoryTower INSTANCE = new IvoryTower(); + + /** + * To be called by user to obtain instance of the class. + * + * @return instance of the singleton. + */ + public static IvoryTower getInstance() { + return INSTANCE; + } +} diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java index 8e6372493b65..dc3907f120ee 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLocking.java @@ -25,22 +25,20 @@ package com.iluwatar.singleton; /** - *

    Double check locking.

    + * Double check locking. * - *

    http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

    - * - *

    Broken under Java 1.4.

    + *

    http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html * + *

    Broken under Java 1.4. */ public final class ThreadSafeDoubleCheckLocking { /** - * Singleton instance of the class, declared as volatile to ensure atomic access by multiple threads. + * Singleton instance of the class, declared as volatile to ensure atomic access by multiple + * threads. */ private static volatile ThreadSafeDoubleCheckLocking instance; - /** - * private constructor to prevent client from instantiating. - */ + /** private constructor to prevent client from instantiating. */ private ThreadSafeDoubleCheckLocking() { // to prevent instantiating by Reflection call if (instance != null) { diff --git a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java index 4d1c25739673..1ead462b9493 100644 --- a/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java +++ b/singleton/src/main/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTower.java @@ -25,20 +25,18 @@ package com.iluwatar.singleton; /** - *

    Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization - * mechanism.

    - * + * Thread-safe Singleton class. The instance is lazily initialized and thus needs synchronization + * mechanism. */ public final class ThreadSafeLazyLoadedIvoryTower { /** - * Singleton instance of the class, declared as volatile to ensure atomic access by multiple threads. + * Singleton instance of the class, declared as volatile to ensure atomic access by multiple + * threads. */ private static volatile ThreadSafeLazyLoadedIvoryTower instance; - /** - * Private constructor to prevent instantiation from outside the class. - */ + /** Private constructor to prevent instantiation from outside the class. */ private ThreadSafeLazyLoadedIvoryTower() { // Protect against instantiation via reflection if (instance != null) { diff --git a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java index 468fe7299630..085cb0f153d3 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/AppTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.singleton; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/singleton/src/test/java/com/iluwatar/singleton/BillPughImplementationTest.java b/singleton/src/test/java/com/iluwatar/singleton/BillPughImplementationTest.java index 0b00cfe223e9..0f61c6776190 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/BillPughImplementationTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/BillPughImplementationTest.java @@ -24,16 +24,10 @@ */ package com.iluwatar.singleton; -/** - * BillPughImplementationTest - * - */ -public class BillPughImplementationTest - extends SingletonTest{ - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ - public BillPughImplementationTest() { - super(BillPughImplementation::getInstance); - } +/** BillPughImplementationTest */ +public class BillPughImplementationTest extends SingletonTest { + /** Create a new singleton test instance using the given 'getInstance' method. */ + public BillPughImplementationTest() { + super(BillPughImplementation::getInstance); + } } diff --git a/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java index 6b1c6e235294..c6af4420ae0f 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/EnumIvoryTowerTest.java @@ -24,17 +24,24 @@ */ package com.iluwatar.singleton; -/** - * EnumIvoryTowerTest - * - */ +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** EnumIvoryTowerTest */ class EnumIvoryTowerTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public EnumIvoryTowerTest() { super(() -> EnumIvoryTower.INSTANCE); } + /** Test creating new instance by reflection. */ + @Override + @Test + void testCreatingNewInstanceByReflection() throws Exception { + // Java does not allow Enum instantiation + // http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.9 + assertThrows(ReflectiveOperationException.class, EnumIvoryTower.class::getDeclaredConstructor); + } } diff --git a/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java b/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java index 5fd63f87de66..5071dfc4fdd1 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/InitializingOnDemandHolderIdiomTest.java @@ -24,18 +24,11 @@ */ package com.iluwatar.singleton; -/** - * InitializingOnDemandHolderIdiomTest - * - */ -class InitializingOnDemandHolderIdiomTest - extends SingletonTest { +/** InitializingOnDemandHolderIdiomTest */ +class InitializingOnDemandHolderIdiomTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public InitializingOnDemandHolderIdiomTest() { super(InitializingOnDemandHolderIdiom::getInstance); } - -} \ No newline at end of file +} diff --git a/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java index d9ce652aee09..98621224de8b 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/IvoryTowerTest.java @@ -24,17 +24,11 @@ */ package com.iluwatar.singleton; -/** - * IvoryTowerTest - * - */ +/** IvoryTowerTest */ class IvoryTowerTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public IvoryTowerTest() { super(IvoryTower::getInstance); } - -} \ No newline at end of file +} diff --git a/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java b/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java index 7093ffc7307f..bf0b403d8f7b 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/SingletonTest.java @@ -27,8 +27,10 @@ import static java.time.Duration.ofMillis; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTimeout; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.concurrent.Callable; import java.util.concurrent.Executors; @@ -38,19 +40,17 @@ import org.junit.jupiter.api.Test; /** - *

    This class provides several test case that test singleton construction.

    + * This class provides several test case that test singleton construction. * *

    The first proves that multiple calls to the singleton getInstance object are the same when * called in the SAME thread. The second proves that multiple calls to the singleton getInstance - * object are the same when called in the DIFFERENT thread.

    + * object are the same when called in the DIFFERENT thread. * * @param Supplier method generating singletons */ abstract class SingletonTest { - /** - * The singleton's getInstance method. - */ + /** The singleton's getInstance method. */ private final Supplier singletonInstanceMethod; /** @@ -62,9 +62,7 @@ public SingletonTest(final Supplier singletonInstanceMethod) { this.singletonInstanceMethod = singletonInstanceMethod; } - /** - * Test the singleton in a non-concurrent setting. - */ + /** Test the singleton in a non-concurrent setting. */ @Test void testMultipleCallsReturnTheSameObjectInSameThread() { // Create several instances in the same calling thread @@ -77,33 +75,41 @@ void testMultipleCallsReturnTheSameObjectInSameThread() { assertSame(instance2, instance3); } - /** - * Test singleton instance in a concurrent setting. - */ + /** Test singleton instance in a concurrent setting. */ @Test void testMultipleCallsReturnTheSameObjectInDifferentThreads() { - assertTimeout(ofMillis(10000), () -> { - // Create 10000 tasks and inside each callable instantiate the singleton class - final var tasks = IntStream.range(0, 10000) - .>mapToObj(i -> this.singletonInstanceMethod::get) - .collect(Collectors.toCollection(ArrayList::new)); - - // Use up to 8 concurrent threads to handle the tasks - final var executorService = Executors.newFixedThreadPool(8); - final var results = executorService.invokeAll(tasks); + assertTimeout( + ofMillis(10000), + () -> { + // Create 10000 tasks and inside each callable instantiate the singleton class + final var tasks = + IntStream.range(0, 10000) + .>mapToObj(i -> this.singletonInstanceMethod::get) + .collect(Collectors.toCollection(ArrayList::new)); - // wait for all the threads to complete - final var expectedInstance = this.singletonInstanceMethod.get(); - for (var res : results) { - final var instance = res.get(); - assertNotNull(instance); - assertSame(expectedInstance, instance); - } + // Use up to 8 concurrent threads to handle the tasks + final var executorService = Executors.newFixedThreadPool(8); + final var results = executorService.invokeAll(tasks); - // tidy up the executor - executorService.shutdown(); - }); + // wait for all the threads to complete + final var expectedInstance = this.singletonInstanceMethod.get(); + for (var res : results) { + final var instance = res.get(); + assertNotNull(instance); + assertSame(expectedInstance, instance); + } + // tidy up the executor + executorService.shutdown(); + }); } + /** Test creating new instance by reflection. */ + @Test + void testCreatingNewInstanceByReflection() throws Exception { + var firstTimeInstantiated = this.singletonInstanceMethod.get(); + var constructor = firstTimeInstantiated.getClass().getDeclaredConstructor(); + constructor.setAccessible(true); + assertThrows(InvocationTargetException.class, () -> constructor.newInstance((Object[]) null)); + } } diff --git a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java index e9d8ef0482d6..b0bc8a4b9135 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeDoubleCheckLockingTest.java @@ -24,33 +24,11 @@ */ package com.iluwatar.singleton; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.lang.reflect.InvocationTargetException; -import org.junit.jupiter.api.Test; - -/** - * ThreadSafeDoubleCheckLockingTest - * - */ +/** ThreadSafeDoubleCheckLockingTest */ class ThreadSafeDoubleCheckLockingTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public ThreadSafeDoubleCheckLockingTest() { super(ThreadSafeDoubleCheckLocking::getInstance); } - - /** - * Test creating new instance by refection. - */ - @Test - void testCreatingNewInstanceByRefection() throws Exception { - ThreadSafeDoubleCheckLocking.getInstance(); - var constructor = ThreadSafeDoubleCheckLocking.class.getDeclaredConstructor(); - constructor.setAccessible(true); - assertThrows(InvocationTargetException.class, () -> constructor.newInstance((Object[]) null)); - } - } diff --git a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java index a451f9c21765..e1cd097a2cc0 100644 --- a/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java +++ b/singleton/src/test/java/com/iluwatar/singleton/ThreadSafeLazyLoadedIvoryTowerTest.java @@ -24,18 +24,11 @@ */ package com.iluwatar.singleton; -/** - * ThreadSafeLazyLoadedIvoryTowerTest - * - */ -class ThreadSafeLazyLoadedIvoryTowerTest - extends SingletonTest { +/** ThreadSafeLazyLoadedIvoryTowerTest */ +class ThreadSafeLazyLoadedIvoryTowerTest extends SingletonTest { - /** - * Create a new singleton test instance using the given 'getInstance' method. - */ + /** Create a new singleton test instance using the given 'getInstance' method. */ public ThreadSafeLazyLoadedIvoryTowerTest() { super(ThreadSafeLazyLoadedIvoryTower::getInstance); } - } diff --git a/spatial-partition/README.md b/spatial-partition/README.md index 26f070d5b6c0..0a13b6f26970 100644 --- a/spatial-partition/README.md +++ b/spatial-partition/README.md @@ -39,6 +39,10 @@ Wikipedia says > > For example, in ray tracing, space partitioning helps quickly determine the objects a ray might intersect by narrowing down the search space, leading to faster rendering times. Similarly, in game development, Quadtrees can efficiently manage 2D game environments by segmenting the space into smaller regions, facilitating quicker collision detection and rendering. +Flowchart + +![Spatial Partition flowchart](./etc/spatial-partition-flowchart.png) + ## Programmatic Example of Spatial Partition Pattern in Java The Spatial Partition design pattern in Java is a strategic approach for handling multiple objects in expansive game worlds or detailed simulation environments, boosting query efficiency and operational speed. It allows us to efficiently manage these objects and perform operations like collision detection or range queries. The pattern works by dividing the space into smaller, manageable regions, and each object is associated with the region it belongs to. This way, we can limit our operations to a specific region, instead of checking every object against every other object. diff --git a/spatial-partition/etc/spatial-partition-flowchart.png b/spatial-partition/etc/spatial-partition-flowchart.png new file mode 100644 index 000000000000..0e287932d97f Binary files /dev/null and b/spatial-partition/etc/spatial-partition-flowchart.png differ diff --git a/spatial-partition/pom.xml b/spatial-partition/pom.xml index 753ee7422304..a9d33cff6b59 100644 --- a/spatial-partition/pom.xml +++ b/spatial-partition/pom.xml @@ -34,6 +34,14 @@ spatial-partition + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java index e7b14249a732..8eabbed060ad 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/App.java @@ -30,76 +30,77 @@ import lombok.extern.slf4j.Slf4j; /** - *

    The idea behind the Spatial Partition design pattern is to enable efficient location - * of objects by storing them in a data structure that is organised by their positions. This is + * The idea behind the Spatial Partition design pattern is to enable efficient location of + * objects by storing them in a data structure that is organised by their positions. This is * especially useful in the gaming world, where one may need to look up all the objects within a * certain boundary, or near a certain other object, repeatedly. The data structure can be used to * store moving and static objects, though in order to keep track of the moving objects, their * positions will have to be reset each time they move. This would mean having to create a new * instance of the data structure each frame, which would use up additional memory, and so this * pattern should only be used if one does not mind trading memory for speed and the number of - * objects to keep track of is large to justify the use of the extra space.

    + * objects to keep track of is large to justify the use of the extra space. + * *

    In our example, we use {@link QuadTree} data structure which divides into 4 (quad) * sub-sections when the number of objects added to it exceeds a certain number (int field - * capacity). There is also a - * {@link Rect} class to define the boundary of the quadtree. We use an abstract class - * {@link Point} - * with x and y coordinate fields and also an id field so that it can easily be put and looked up in - * the hashmap. This class has abstract methods to define how the object moves (move()), when to - * check for collision with any object (touches(obj)) and how to handle collision - * (handleCollision(obj)), and will be extended by any object whose position has to be kept track of - * in the quadtree. The {@link SpatialPartitionGeneric} abstract class has 2 fields - a - * hashmap containing all objects (we use hashmap for faster lookups, insertion and deletion) - * and a quadtree, and contains an abstract method which defines how to handle interactions between - * objects using the quadtree.

    + * capacity). There is also a {@link Rect} class to define the boundary of the quadtree. We + * use an abstract class {@link Point} with x and y coordinate fields and also an id field so + * that it can easily be put and looked up in the hashmap. This class has abstract methods to define + * how the object moves (move()), when to check for collision with any object (touches(obj)) and how + * to handle collision (handleCollision(obj)), and will be extended by any object whose position has + * to be kept track of in the quadtree. The {@link SpatialPartitionGeneric} abstract class + * has 2 fields - a hashmap containing all objects (we use hashmap for faster lookups, insertion and + * deletion) and a quadtree, and contains an abstract method which defines how to handle + * interactions between objects using the quadtree. + * *

    Using the quadtree data structure will reduce the time complexity of finding the objects * within a certain range from O(n^2) to O(nlogn), increasing the speed of computations * immensely in case of large number of objects, which will have a positive effect on the rendering - * speed of the game.

    + * speed of the game. */ - @Slf4j public class App { static void noSpatialPartition(int numOfMovements, Map bubbles) { - //all bubbles have to be checked for collision for all bubbles + // all bubbles have to be checked for collision for all bubbles var bubblesToCheck = bubbles.values(); - //will run numOfMovement times or till all bubbles have popped + // will run numOfMovement times or till all bubbles have popped while (numOfMovements > 0 && !bubbles.isEmpty()) { - bubbles.forEach((i, bubble) -> { - // bubble moves, new position gets updated - // and collisions are checked with all bubbles in bubblesToCheck - bubble.move(); - bubbles.replace(i, bubble); - bubble.handleCollision(bubblesToCheck, bubbles); - }); + bubbles.forEach( + (i, bubble) -> { + // bubble moves, new position gets updated + // and collisions are checked with all bubbles in bubblesToCheck + bubble.move(); + bubbles.replace(i, bubble); + bubble.handleCollision(bubblesToCheck, bubbles); + }); numOfMovements--; } - //bubbles not popped + // bubbles not popped bubbles.keySet().forEach(key -> LOGGER.info("Bubble {} not popped", key)); } static void withSpatialPartition( int height, int width, int numOfMovements, Map bubbles) { - //creating quadtree + // creating quadtree var rect = new Rect(width / 2D, height / 2D, width, height); var quadTree = new QuadTree(rect, 4); - //will run numOfMovement times or till all bubbles have popped + // will run numOfMovement times or till all bubbles have popped while (numOfMovements > 0 && !bubbles.isEmpty()) { - //quadtree updated each time + // quadtree updated each time bubbles.values().forEach(quadTree::insert); - bubbles.forEach((i, bubble) -> { - //bubble moves, new position gets updated, quadtree used to reduce computations - bubble.move(); - bubbles.replace(i, bubble); - var sp = new SpatialPartitionBubbles(bubbles, quadTree); - sp.handleCollisionsUsingQt(bubble); - }); + bubbles.forEach( + (i, bubble) -> { + // bubble moves, new position gets updated, quadtree used to reduce computations + bubble.move(); + bubbles.replace(i, bubble); + var sp = new SpatialPartitionBubbles(bubbles, quadTree); + sp.handleCollisionsUsingQt(bubble); + }); numOfMovements--; } - //bubbles not popped + // bubbles not popped bubbles.keySet().forEach(key -> LOGGER.info("Bubble {} not popped", key)); } @@ -108,7 +109,6 @@ static void withSpatialPartition( * * @param args command line args */ - public static void main(String[] args) { var bubbles1 = new ConcurrentHashMap(); var bubbles2 = new ConcurrentHashMap(); @@ -117,8 +117,8 @@ public static void main(String[] args) { var b = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1); bubbles1.put(i, b); bubbles2.put(i, b); - LOGGER.info("Bubble {} with radius {} added at ({},{})", - i, b.radius, b.coordinateX, b.coordinateY); + LOGGER.info( + "Bubble {} with radius {} added at ({},{})", i, b.radius, b.coordinateX, b.coordinateY); } var start1 = System.currentTimeMillis(); diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Bubble.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Bubble.java index 70f8cae48d65..b2b0a10d9a0a 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Bubble.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Bubble.java @@ -33,7 +33,6 @@ * Bubble class extends Point. In this example, we create several bubbles in the field, let them * move and keep track of which ones have popped and which ones remain. */ - @Slf4j public class Bubble extends Point { private static final SecureRandom RANDOM = new SecureRandom(); @@ -46,15 +45,15 @@ public class Bubble extends Point { } void move() { - //moves by 1 unit in either direction + // moves by 1 unit in either direction this.coordinateX += RANDOM.nextInt(3) - 1; this.coordinateY += RANDOM.nextInt(3) - 1; } boolean touches(Bubble b) { - //distance between them is greater than sum of radii (both sides of equation squared) + // distance between them is greater than sum of radii (both sides of equation squared) return (this.coordinateX - b.coordinateX) * (this.coordinateX - b.coordinateX) - + (this.coordinateY - b.coordinateY) * (this.coordinateY - b.coordinateY) + + (this.coordinateY - b.coordinateY) * (this.coordinateY - b.coordinateY) <= (this.radius + b.radius) * (this.radius + b.radius); } @@ -64,12 +63,12 @@ void pop(Map allBubbles) { } void handleCollision(Collection toCheck, Map allBubbles) { - var toBePopped = false; //if any other bubble collides with it, made true + var toBePopped = false; // if any other bubble collides with it, made true for (var point : toCheck) { var otherId = point.id; - if (allBubbles.get(otherId) != null //the bubble hasn't been popped yet - && this.id != otherId //the two bubbles are not the same - && this.touches(allBubbles.get(otherId))) { //the bubbles touch + if (allBubbles.get(otherId) != null // the bubble hasn't been popped yet + && this.id != otherId // the two bubbles are not the same + && this.touches(allBubbles.get(otherId))) { // the bubbles touch allBubbles.get(otherId).pop(allBubbles); toBePopped = true; } diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Point.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Point.java index 1b24e70fa37e..adcc4862b155 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Point.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Point.java @@ -33,7 +33,6 @@ * * @param T will be type subclass */ - public abstract class Point { public int coordinateX; @@ -46,9 +45,7 @@ public abstract class Point { this.id = id; } - /** - * defines how the object moves. - */ + /** defines how the object moves. */ abstract void move(); /** @@ -63,7 +60,7 @@ public abstract class Point { * handling interactions/collisions with other objects. * * @param toCheck contains the objects which need to be checked - * @param all contains hashtable of all points on field at this time + * @param all contains hashtable of all points on field at this time */ abstract void handleCollision(Collection toCheck, Map all); } diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/QuadTree.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/QuadTree.java index 40c09a1d8f6e..c83ccc42cc43 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/QuadTree.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/QuadTree.java @@ -33,7 +33,6 @@ * insert(Point) and query(range) methods to insert a new object and find the objects within a * certain (rectangular) range respectively. */ - public class QuadTree { Rect boundary; int capacity; @@ -93,13 +92,9 @@ void divide() { } Collection query(Rect r, Collection relevantPoints) { - //could also be a circle instead of a rectangle + // could also be a circle instead of a rectangle if (this.boundary.intersects(r)) { - this.points - .values() - .stream() - .filter(r::contains) - .forEach(relevantPoints::add); + this.points.values().stream().filter(r::contains).forEach(relevantPoints::add); if (this.divided) { this.northwest.query(r, relevantPoints); this.northeast.query(r, relevantPoints); diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Rect.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Rect.java index 5fc654949e59..9f743e0d69f4 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Rect.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/Rect.java @@ -28,14 +28,13 @@ * The Rect class helps in defining the boundary of the quadtree and is also used to define the * range within which objects need to be found in our example. */ - public class Rect { double coordinateX; double coordinateY; double width; double height; - //(x,y) - centre of rectangle + // (x,y) - centre of rectangle Rect(double x, double y, double width, double height) { this.coordinateX = x; @@ -58,4 +57,3 @@ boolean intersects(Rect other) { || this.coordinateY - this.height / 2 >= other.coordinateY + other.height / 2); } } - diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java index 3551f5ca3b9b..cefc1a824c46 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionBubbles.java @@ -31,7 +31,6 @@ * This class extends the generic SpatialPartition abstract class and is used in our example to keep * track of all the bubbles that collide, pop and stay un-popped. */ - public class SpatialPartitionBubbles extends SpatialPartitionGeneric { private final Map bubbles; @@ -48,7 +47,7 @@ void handleCollisionsUsingQt(Bubble b) { var rect = new Rect(b.coordinateX, b.coordinateY, 2D * b.radius, 2D * b.radius); var quadTreeQueryResult = new ArrayList(); this.bubblesQuadTree.query(rect, quadTreeQueryResult); - //handling these collisions + // handling these collisions b.handleCollision(quadTreeQueryResult, this.bubbles); } } diff --git a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionGeneric.java b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionGeneric.java index 7c03969a08e6..1e5baabb8d07 100644 --- a/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionGeneric.java +++ b/spatial-partition/src/main/java/com/iluwatar/spatialpartition/SpatialPartitionGeneric.java @@ -32,7 +32,6 @@ * * @param T will be type of object (that extends Point) */ - public abstract class SpatialPartitionGeneric { Map playerPositions; diff --git a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/BubbleTest.java b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/BubbleTest.java index 2fb277057091..3f9ae809d918 100644 --- a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/BubbleTest.java +++ b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/BubbleTest.java @@ -33,10 +33,7 @@ import java.util.HashMap; import org.junit.jupiter.api.Test; -/** - * Testing methods in Bubble class. - */ - +/** Testing methods in Bubble class. */ class BubbleTest { @Test @@ -45,7 +42,7 @@ void moveTest() { var initialX = b.coordinateX; var initialY = b.coordinateY; b.move(); - //change in x and y < |2| + // change in x and y < |2| assertTrue(b.coordinateX - initialX < 2 && b.coordinateX - initialX > -2); assertTrue(b.coordinateY - initialY < 2 && b.coordinateY - initialY > -2); } @@ -55,7 +52,7 @@ void touchesTest() { var b1 = new Bubble(0, 0, 1, 2); var b2 = new Bubble(1, 1, 2, 1); var b3 = new Bubble(10, 10, 3, 1); - //b1 touches b2 but not b3 + // b1 touches b2 but not b3 assertTrue(b1.touches(b2)); assertFalse(b1.touches(b3)); } @@ -68,7 +65,7 @@ void popTest() { bubbles.put(1, b1); bubbles.put(2, b2); b1.pop(bubbles); - //after popping, bubble no longer in hashMap containing all bubbles + // after popping, bubble no longer in hashMap containing all bubbles assertNull(bubbles.get(1)); assertNotNull(bubbles.get(2)); } @@ -86,7 +83,7 @@ void handleCollisionTest() { bubblesToCheck.add(b2); bubblesToCheck.add(b3); b1.handleCollision(bubblesToCheck, bubbles); - //b1 touches b2 and not b3, so b1, b2 will be popped + // b1 touches b2 and not b3, so b1, b2 will be popped assertNull(bubbles.get(1)); assertNull(bubbles.get(2)); assertNotNull(bubbles.get(3)); diff --git a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/QuadTreeTest.java b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/QuadTreeTest.java index 8d4adadaaa26..b1d9e48b792a 100644 --- a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/QuadTreeTest.java +++ b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/QuadTreeTest.java @@ -33,10 +33,7 @@ import java.util.stream.Collectors; import org.junit.jupiter.api.Test; -/** - * Testing QuadTree class. - */ - +/** Testing QuadTree class. */ class QuadTreeTest { @Test @@ -47,22 +44,21 @@ void queryTest() { var p = new Bubble(rand.nextInt(300), rand.nextInt(300), i, rand.nextInt(2) + 1); points.add(p); } - var field = new Rect(150, 150, 300, 300); //size of field - var queryRange = new Rect(70, 130, 100, 100); //result = all points lying in this rectangle - //points found in the query range using quadtree and normal method is same + var field = new Rect(150, 150, 300, 300); // size of field + var queryRange = new Rect(70, 130, 100, 100); // result = all points lying in this rectangle + // points found in the query range using quadtree and normal method is same var points1 = QuadTreeTest.quadTreeTest(points, field, queryRange); var points2 = QuadTreeTest.verify(points, queryRange); assertEquals(points1, points2); } - static Hashtable quadTreeTest(Collection points, Rect field, Rect queryRange) { - //creating quadtree and inserting all points + static Hashtable quadTreeTest( + Collection points, Rect field, Rect queryRange) { + // creating quadtree and inserting all points var qTree = new QuadTree(queryRange, 4); points.forEach(qTree::insert); - return qTree - .query(field, new ArrayList<>()) - .stream() + return qTree.query(field, new ArrayList<>()).stream() .collect(Collectors.toMap(p -> p.id, p -> p, (a, b) -> b, Hashtable::new)); } diff --git a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/RectTest.java b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/RectTest.java index 3a2ea45628aa..0aca25388ea0 100644 --- a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/RectTest.java +++ b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/RectTest.java @@ -29,10 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Testing Rect class. - */ - +/** Testing Rect class. */ class RectTest { @Test @@ -40,7 +37,7 @@ void containsTest() { var r = new Rect(10, 10, 20, 20); var b1 = new Bubble(2, 2, 1, 1); var b2 = new Bubble(30, 30, 2, 1); - //r contains b1 and not b2 + // r contains b1 and not b2 assertTrue(r.contains(b1)); assertFalse(r.contains(b2)); } @@ -50,7 +47,7 @@ void intersectsTest() { var r1 = new Rect(10, 10, 20, 20); var r2 = new Rect(15, 15, 20, 20); var r3 = new Rect(50, 50, 20, 20); - //r1 intersects r2 and not r3 + // r1 intersects r2 and not r3 assertTrue(r1.intersects(r2)); assertFalse(r1.intersects(r3)); } diff --git a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/SpatialPartitionBubblesTest.java b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/SpatialPartitionBubblesTest.java index c491ecb46e23..b36fa99bc74f 100644 --- a/spatial-partition/src/test/java/com/iluwatar/spatialpartition/SpatialPartitionBubblesTest.java +++ b/spatial-partition/src/test/java/com/iluwatar/spatialpartition/SpatialPartitionBubblesTest.java @@ -30,10 +30,7 @@ import java.util.HashMap; import org.junit.jupiter.api.Test; -/** - * Testing SpatialPartition_Bubbles class. - */ - +/** Testing SpatialPartition_Bubbles class. */ class SpatialPartitionBubblesTest { @Test @@ -55,7 +52,7 @@ void handleCollisionsUsingQtTest() { qt.insert(b4); var sp = new SpatialPartitionBubbles(bubbles, qt); sp.handleCollisionsUsingQt(b1); - //b1 touches b3 and b4 but not b2 - so b1,b3,b4 get popped + // b1 touches b3 and b4 but not b2 - so b1,b3,b4 get popped assertNull(bubbles.get(1)); assertNotNull(bubbles.get(2)); assertNull(bubbles.get(3)); diff --git a/special-case/README.md b/special-case/README.md index eda6ad0ff797..e7bbeb39df85 100644 --- a/special-case/README.md +++ b/special-case/README.md @@ -37,6 +37,10 @@ In [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) Ma > If you’ll pardon the unresistable pun, I see [Null Object](https://java-design-patterns.com/patterns/null-object/) as special case of Special Case. +Sequnce diagram + +![Special Case sequence diagram](./etc/special-case-sequence-diagram.png) + ## Programmatic Example of Special Case Pattern in Java The Special Case Pattern is a software design pattern that is used to handle a specific, often uncommon, case separately from the general case in the code. This pattern is useful when a class has behavior that requires conditional logic based on its state. Instead of cluttering the class with conditional logic, we can encapsulate the special behavior in a subclass. diff --git a/special-case/etc/special-case-sequence-diagram.png b/special-case/etc/special-case-sequence-diagram.png new file mode 100644 index 000000000000..87842ec4cfa1 Binary files /dev/null and b/special-case/etc/special-case-sequence-diagram.png differ diff --git a/special-case/pom.xml b/special-case/pom.xml index 59ec1c10f121..55400ffba9bc 100644 --- a/special-case/pom.xml +++ b/special-case/pom.xml @@ -34,10 +34,37 @@ 4.0.0 special-case + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine test + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.specialcase.App + + + + + + + + diff --git a/special-case/src/main/java/com/iluwatar/specialcase/App.java b/special-case/src/main/java/com/iluwatar/specialcase/App.java index 5c6a10602543..6bbad3322b40 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/App.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/App.java @@ -28,10 +28,10 @@ import org.slf4j.LoggerFactory; /** - *

    The Special Case Pattern is a software design pattern that encapsulates particular cases - * into subclasses that provide special behaviors.

    + * The Special Case Pattern is a software design pattern that encapsulates particular cases into + * subclasses that provide special behaviors. * - *

    In this example ({@link ReceiptViewModel}) encapsulates all particular cases.

    + *

    In this example ({@link ReceiptViewModel}) encapsulates all particular cases. */ public class App { @@ -44,13 +44,13 @@ public class App { private static final String ITEM_CAR = "car"; private static final String ITEM_COMPUTER = "computer"; - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { // DB seeding - LOGGER.info("Db seeding: " + "1 user: {\"ignite1771\", amount = 1000.0}, " - + "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}"); + LOGGER.info( + "Db seeding: " + + "1 user: {\"ignite1771\", amount = 1000.0}, " + + "2 products: {\"computer\": price = 800.0, \"car\": price = 20000.0}"); Db.getInstance().seedUser(TEST_USER_1, 1000.0); Db.getInstance().seedItem(ITEM_COMPUTER, 800.0); Db.getInstance().seedItem(ITEM_CAR, 20000.0); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServices.java b/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServices.java index 9dafdba44f68..507415663f31 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServices.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServices.java @@ -24,9 +24,7 @@ */ package com.iluwatar.specialcase; -/** - * ApplicationServices interface to demonstrate special case pattern. - */ +/** ApplicationServices interface to demonstrate special case pattern. */ public interface ApplicationServices { ReceiptViewModel loggedInUserPurchase(String userName, String itemName); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServicesImpl.java b/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServicesImpl.java index cb21e57f6592..fa455a23dca2 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServicesImpl.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/ApplicationServicesImpl.java @@ -24,9 +24,7 @@ */ package com.iluwatar.specialcase; -/** - * Implementation of special case pattern. - */ +/** Implementation of special case pattern. */ public class ApplicationServicesImpl implements ApplicationServices { private DomainServicesImpl domain = new DomainServicesImpl(); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/Db.java b/special-case/src/main/java/com/iluwatar/specialcase/Db.java index da8d588a3464..32ac6501d4ef 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/Db.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/Db.java @@ -29,9 +29,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * DB class for seeding user info. - */ +/** DB class for seeding user info. */ public class Db { private static Db instance; @@ -118,9 +116,7 @@ public Product findProductByItemName(String itemName) { return itemName2Product.get(itemName); } - /** - * User class to store user info. - */ + /** User class to store user info. */ @RequiredArgsConstructor @Getter public class User { @@ -132,9 +128,7 @@ public ReceiptDto purchase(Product item) { } } - /** - * Account info. - */ + /** Account info. */ @RequiredArgsConstructor @Getter public static class Account { @@ -155,9 +149,7 @@ public MoneyTransaction withdraw(Double price) { } } - /** - * Product info. - */ + /** Product info. */ @RequiredArgsConstructor @Getter public static class Product { diff --git a/special-case/src/main/java/com/iluwatar/specialcase/DomainServices.java b/special-case/src/main/java/com/iluwatar/specialcase/DomainServices.java index 84502e64d4fc..daf14c062310 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/DomainServices.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/DomainServices.java @@ -24,8 +24,5 @@ */ package com.iluwatar.specialcase; -/** - * DomainServices interface. - */ -public interface DomainServices { -} +/** DomainServices interface. */ +public interface DomainServices {} diff --git a/special-case/src/main/java/com/iluwatar/specialcase/DomainServicesImpl.java b/special-case/src/main/java/com/iluwatar/specialcase/DomainServicesImpl.java index ac31a90839c1..54c7b99833cb 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/DomainServicesImpl.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/DomainServicesImpl.java @@ -24,9 +24,7 @@ */ package com.iluwatar.specialcase; -/** - * Implementation of DomainServices for special case. - */ +/** Implementation of DomainServices for special case. */ public class DomainServicesImpl implements DomainServices { /** @@ -47,9 +45,8 @@ public ReceiptViewModel purchase(String userName, String itemName) { } /** - * Domain purchase with user, account and itemName, - * with validation for whether product is out of stock - * and whether user has insufficient funds in the account. + * Domain purchase with user, account and itemName, with validation for whether product is out of + * stock and whether user has insufficient funds in the account. * * @param user in Db * @param account in Db diff --git a/special-case/src/main/java/com/iluwatar/specialcase/DownForMaintenance.java b/special-case/src/main/java/com/iluwatar/specialcase/DownForMaintenance.java index 713dcaef7686..504678ecad1e 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/DownForMaintenance.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/DownForMaintenance.java @@ -27,9 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Down for Maintenance view for the ReceiptViewModel. - */ +/** Down for Maintenance view for the ReceiptViewModel. */ public class DownForMaintenance implements ReceiptViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(DownForMaintenance.class); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/InsufficientFunds.java b/special-case/src/main/java/com/iluwatar/specialcase/InsufficientFunds.java index 03dff2c41d2c..66c974ca5bd8 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/InsufficientFunds.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/InsufficientFunds.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * View representing insufficient funds. - */ +/** View representing insufficient funds. */ @Slf4j public class InsufficientFunds implements ReceiptViewModel { @@ -51,7 +49,12 @@ public InsufficientFunds(String userName, Double amount, String itemName) { @Override public void show() { - LOGGER.info("Insufficient funds: " + amount + " of user: " + userName - + " for buying item: " + itemName); + LOGGER.info( + "Insufficient funds: " + + amount + + " of user: " + + userName + + " for buying item: " + + itemName); } } diff --git a/special-case/src/main/java/com/iluwatar/specialcase/InvalidUser.java b/special-case/src/main/java/com/iluwatar/specialcase/InvalidUser.java index 861d69e8c98d..37c08d1f72ce 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/InvalidUser.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/InvalidUser.java @@ -27,9 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Receipt View representing invalid user. - */ +/** Receipt View representing invalid user. */ public class InvalidUser implements ReceiptViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(InvalidUser.class); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/MaintenanceLock.java b/special-case/src/main/java/com/iluwatar/specialcase/MaintenanceLock.java index b98094285215..0e39e900f538 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/MaintenanceLock.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/MaintenanceLock.java @@ -28,17 +28,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Acquire lock on the DB for maintenance. - */ +/** Acquire lock on the DB for maintenance. */ public class MaintenanceLock { private static final Logger LOGGER = LoggerFactory.getLogger(MaintenanceLock.class); private static MaintenanceLock instance; - @Getter - private boolean lock = true; + @Getter private boolean lock = true; /** * Get the instance of MaintenanceLock. diff --git a/special-case/src/main/java/com/iluwatar/specialcase/MoneyTransaction.java b/special-case/src/main/java/com/iluwatar/specialcase/MoneyTransaction.java index 4bf9b92f9956..cdaf3fb785b9 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/MoneyTransaction.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/MoneyTransaction.java @@ -26,9 +26,7 @@ import lombok.RequiredArgsConstructor; -/** - * Represents the money transaction taking place at a given moment. - */ +/** Represents the money transaction taking place at a given moment. */ @RequiredArgsConstructor public class MoneyTransaction { diff --git a/special-case/src/main/java/com/iluwatar/specialcase/OutOfStock.java b/special-case/src/main/java/com/iluwatar/specialcase/OutOfStock.java index 5bfa09186d2d..79c47df9ae6d 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/OutOfStock.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/OutOfStock.java @@ -27,9 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Receipt view for showing out of stock message. - */ +/** Receipt view for showing out of stock message. */ public class OutOfStock implements ReceiptViewModel { private static final Logger LOGGER = LoggerFactory.getLogger(OutOfStock.class); diff --git a/special-case/src/main/java/com/iluwatar/specialcase/ReceiptDto.java b/special-case/src/main/java/com/iluwatar/specialcase/ReceiptDto.java index 7143d797bf69..45d2129d6421 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/ReceiptDto.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/ReceiptDto.java @@ -29,9 +29,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Receipt view representing the transaction recceipt. - */ +/** Receipt view representing the transaction recceipt. */ @RequiredArgsConstructor @Getter public class ReceiptDto implements ReceiptViewModel { diff --git a/special-case/src/main/java/com/iluwatar/specialcase/ReceiptViewModel.java b/special-case/src/main/java/com/iluwatar/specialcase/ReceiptViewModel.java index 633005b07d30..df33227bb245 100644 --- a/special-case/src/main/java/com/iluwatar/specialcase/ReceiptViewModel.java +++ b/special-case/src/main/java/com/iluwatar/specialcase/ReceiptViewModel.java @@ -24,9 +24,7 @@ */ package com.iluwatar.specialcase; -/** - * ReceiptViewModel interface. - */ +/** ReceiptViewModel interface. */ public interface ReceiptViewModel { void show(); diff --git a/special-case/src/test/java/com/iluwatar/specialcase/AppTest.java b/special-case/src/test/java/com/iluwatar/specialcase/AppTest.java index a571024a57c0..15dd9cf1074a 100644 --- a/special-case/src/test/java/com/iluwatar/specialcase/AppTest.java +++ b/special-case/src/test/java/com/iluwatar/specialcase/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * Application test. - */ +/** Application test. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/special-case/src/test/java/com/iluwatar/specialcase/SpecialCasesTest.java b/special-case/src/test/java/com/iluwatar/specialcase/SpecialCasesTest.java index 8411398b2020..da5d532b8ac9 100644 --- a/special-case/src/test/java/com/iluwatar/specialcase/SpecialCasesTest.java +++ b/special-case/src/test/java/com/iluwatar/specialcase/SpecialCasesTest.java @@ -26,19 +26,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.BeforeAll; -import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.read.ListAppender; import java.util.List; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; -/** - * Special cases unit tests. (including the successful scenario {@link ReceiptDto}) - */ +/** Special cases unit tests. (including the successful scenario {@link ReceiptDto}) */ class SpecialCasesTest { private static ApplicationServices applicationServices; private static ReceiptViewModel receipt; @@ -102,8 +100,8 @@ void testOutOfStock() { receipt.show(); List loggingEventList = listAppender.list; - assertEquals("Out of stock: tv for user = ignite1771 to buy" - , loggingEventList.get(0).getMessage()); + assertEquals( + "Out of stock: tv for user = ignite1771 to buy", loggingEventList.get(0).getMessage()); assertEquals(Level.INFO, loggingEventList.get(0).getLevel()); } @@ -119,8 +117,9 @@ void testInsufficientFunds() { receipt.show(); List loggingEventList = listAppender.list; - assertEquals("Insufficient funds: 1000.0 of user: ignite1771 for buying item: car" - , loggingEventList.get(0).getMessage()); + assertEquals( + "Insufficient funds: 1000.0 of user: ignite1771 for buying item: car", + loggingEventList.get(0).getMessage()); assertEquals(Level.INFO, loggingEventList.get(0).getLevel()); } @@ -136,8 +135,7 @@ void testReceiptDto() { receipt.show(); List loggingEventList = listAppender.list; - assertEquals("Receipt: 800.0 paid" - , loggingEventList.get(0).getMessage()); + assertEquals("Receipt: 800.0 paid", loggingEventList.get(0).getMessage()); assertEquals(Level.INFO, loggingEventList.get(0).getLevel()); } } diff --git a/specification/README.md b/specification/README.md index 4ccb68cc55de..9310555ecbeb 100644 --- a/specification/README.md +++ b/specification/README.md @@ -37,6 +37,10 @@ Wikipedia says > In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic. +Flowchart + +![Specification Pattern flowchart](./etc/specification-flowchart.png) + ## Programmatic Example of Specification Pattern in Java Let's consider a creature pool example. We have a collection of creatures with specific properties. These properties might belong to a predefined, limited set (represented by enums like `Size`, `Movement`, and `Color`) or they might be continuous values (e.g., the mass of a `Creature`). In cases with continuous values, it's better to use a "parameterized specification," where the property value is provided as an argument when the `Creature` is instantiated, allowing for greater flexibility. Additionally, predefined and/or parameterized properties can be combined using boolean logic, offering almost limitless selection possibilities (this is known as a "composite specification," explained further below). The advantages and disadvantages of each approach are detailed in the table at the end of this document. diff --git a/specification/etc/specification-flowchart.png b/specification/etc/specification-flowchart.png new file mode 100644 index 000000000000..73bc446f7646 Binary files /dev/null and b/specification/etc/specification-flowchart.png differ diff --git a/specification/pom.xml b/specification/pom.xml index 6f97d68dcddc..078025193aba 100644 --- a/specification/pom.xml +++ b/specification/pom.xml @@ -34,6 +34,14 @@ specification + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/specification/src/main/java/com/iluwatar/specification/app/App.java b/specification/src/main/java/com/iluwatar/specification/app/App.java index 381cce202c3a..83486d21d862 100644 --- a/specification/src/main/java/com/iluwatar/specification/app/App.java +++ b/specification/src/main/java/com/iluwatar/specification/app/App.java @@ -44,32 +44,25 @@ import lombok.extern.slf4j.Slf4j; /** - *

    The central idea of the Specification pattern is to separate the statement of how to match a + * The central idea of the Specification pattern is to separate the statement of how to match a * candidate, from the candidate object that it is matched against. As well as its usefulness in - * selection, it is also valuable for validation and for building to order.

    + * selection, it is also valuable for validation and for building to order. * *

    In this example we have a pool of creatures with different properties. We then have defined * separate selection rules (Specifications) that we apply to the collection and as output receive - * only the creatures that match the selection criteria.

    + * only the creatures that match the selection criteria. * - *

    http://martinfowler.com/apsupp/spec.pdf

    + *

    http://martinfowler.com/apsupp/spec.pdf */ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { // initialize creatures list - var creatures = List.of( - new Goblin(), - new Octopus(), - new Dragon(), - new Shark(), - new Troll(), - new KillerBee() - ); + var creatures = + List.of( + new Goblin(), new Octopus(), new Dragon(), new Shark(), new Troll(), new KillerBee()); // so-called "hard-coded" specification LOGGER.info("Demonstrating hard-coded specification :"); // find all walking creatures @@ -96,9 +89,11 @@ public static void main(String[] args) { print(creatures, redAndFlying); // find all creatures dark or red, non-swimming, and heavier than or equal to 400kg LOGGER.info("Find all scary creatures"); - var scaryCreaturesSelector = new ColorSelector(Color.DARK) - .or(new ColorSelector(Color.RED)).and(new MovementSelector(Movement.SWIMMING).not()) - .and(new MassGreaterThanSelector(400.0).or(new MassEqualSelector(400.0))); + var scaryCreaturesSelector = + new ColorSelector(Color.DARK) + .or(new ColorSelector(Color.RED)) + .and(new MovementSelector(Movement.SWIMMING).not()) + .and(new MassGreaterThanSelector(400.0).or(new MassEqualSelector(400.0))); print(creatures, scaryCreaturesSelector); } diff --git a/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java b/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java index 610404fb914e..9afa65e432fe 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/AbstractCreature.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Base class for concrete creatures. - */ +/** Base class for concrete creatures. */ public abstract class AbstractCreature implements Creature { private final String name; @@ -40,9 +38,7 @@ public abstract class AbstractCreature implements Creature { private final Color color; private final Mass mass; - /** - * Constructor. - */ + /** Constructor. */ public AbstractCreature(String name, Size size, Movement movement, Color color, Mass mass) { this.name = name; this.size = size; @@ -53,8 +49,8 @@ public AbstractCreature(String name, Size size, Movement movement, Color color, @Override public String toString() { - return String.format("%s [size=%s, movement=%s, color=%s, mass=%s]", - name, size, movement, color, mass); + return String.format( + "%s [size=%s, movement=%s, color=%s, mass=%s]", name, size, movement, color, mass); } @Override diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Creature.java b/specification/src/main/java/com/iluwatar/specification/creature/Creature.java index b52d2d7ef704..0e02e8b6d312 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Creature.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Creature.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Creature interface. - */ +/** Creature interface. */ public interface Creature { String getName(); diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Dragon.java b/specification/src/main/java/com/iluwatar/specification/creature/Dragon.java index 9d85da31b22c..5d05819c0014 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Dragon.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Dragon.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Dragon creature. - */ +/** Dragon creature. */ public class Dragon extends AbstractCreature { public Dragon() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Goblin.java b/specification/src/main/java/com/iluwatar/specification/creature/Goblin.java index 4177df1edcf9..faff6750a0da 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Goblin.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Goblin.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Goblin creature. - */ +/** Goblin creature. */ public class Goblin extends AbstractCreature { public Goblin() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/KillerBee.java b/specification/src/main/java/com/iluwatar/specification/creature/KillerBee.java index c27e931197a9..4bfc3eefef8f 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/KillerBee.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/KillerBee.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * KillerBee creature. - */ +/** KillerBee creature. */ public class KillerBee extends AbstractCreature { public KillerBee() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Octopus.java b/specification/src/main/java/com/iluwatar/specification/creature/Octopus.java index 66096d465bc4..5760511cf802 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Octopus.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Octopus.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Octopus creature. - */ +/** Octopus creature. */ public class Octopus extends AbstractCreature { public Octopus() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Shark.java b/specification/src/main/java/com/iluwatar/specification/creature/Shark.java index 2a485092daae..972feae762f5 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Shark.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Shark.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Shark creature. - */ +/** Shark creature. */ public class Shark extends AbstractCreature { public Shark() { diff --git a/specification/src/main/java/com/iluwatar/specification/creature/Troll.java b/specification/src/main/java/com/iluwatar/specification/creature/Troll.java index de4ab9b75876..0ef9ca2570bb 100644 --- a/specification/src/main/java/com/iluwatar/specification/creature/Troll.java +++ b/specification/src/main/java/com/iluwatar/specification/creature/Troll.java @@ -29,9 +29,7 @@ import com.iluwatar.specification.property.Movement; import com.iluwatar.specification.property.Size; -/** - * Troll creature. - */ +/** Troll creature. */ public class Troll extends AbstractCreature { public Troll() { diff --git a/specification/src/main/java/com/iluwatar/specification/property/Color.java b/specification/src/main/java/com/iluwatar/specification/property/Color.java index 9b15a82c958b..d52ec3088e83 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Color.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Color.java @@ -24,12 +24,12 @@ */ package com.iluwatar.specification.property; -/** - * Color property. - */ +/** Color property. */ public enum Color { - - DARK("dark"), LIGHT("light"), GREEN("green"), RED("red"); + DARK("dark"), + LIGHT("light"), + GREEN("green"), + RED("red"); private final String title; diff --git a/specification/src/main/java/com/iluwatar/specification/property/Mass.java b/specification/src/main/java/com/iluwatar/specification/property/Mass.java index 384f0dc7b756..68682235148d 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Mass.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Mass.java @@ -26,9 +26,7 @@ import lombok.EqualsAndHashCode; -/** - * Mass property. - */ +/** Mass property. */ @EqualsAndHashCode public class Mass { @@ -60,5 +58,4 @@ public final boolean smallerThanOrEq(Mass other) { public String toString() { return title; } - } diff --git a/specification/src/main/java/com/iluwatar/specification/property/Movement.java b/specification/src/main/java/com/iluwatar/specification/property/Movement.java index d6deb7cac3cb..2bead515390b 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Movement.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Movement.java @@ -24,12 +24,11 @@ */ package com.iluwatar.specification.property; -/** - * Movement property. - */ +/** Movement property. */ public enum Movement { - - WALKING("walking"), SWIMMING("swimming"), FLYING("flying"); + WALKING("walking"), + SWIMMING("swimming"), + FLYING("flying"); private final String title; diff --git a/specification/src/main/java/com/iluwatar/specification/property/Size.java b/specification/src/main/java/com/iluwatar/specification/property/Size.java index 9869c26b7551..66b32dc885bc 100644 --- a/specification/src/main/java/com/iluwatar/specification/property/Size.java +++ b/specification/src/main/java/com/iluwatar/specification/property/Size.java @@ -24,12 +24,11 @@ */ package com.iluwatar.specification.property; -/** - * Size property. - */ +/** Size property. */ public enum Size { - - SMALL("small"), NORMAL("normal"), LARGE("large"); + SMALL("small"), + NORMAL("normal"), + LARGE("large"); private final String title; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java index c223c203a9c4..48b04c2bd1bc 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/AbstractSelector.java @@ -26,9 +26,7 @@ import java.util.function.Predicate; -/** - * Base class for selectors. - */ +/** Base class for selectors. */ public abstract class AbstractSelector implements Predicate { public AbstractSelector and(AbstractSelector other) { diff --git a/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java index a14bb6c98319..97d9757e068d 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/ColorSelector.java @@ -27,9 +27,7 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Color; -/** - * Color selector. - */ +/** Color selector. */ public class ColorSelector extends AbstractSelector { private final Color color; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java index 60d13deb9d58..303fea7ae4a3 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/ConjunctionSelector.java @@ -26,9 +26,7 @@ import java.util.List; -/** - * A Selector defined as the conjunction (AND) of other (leaf) selectors. - */ +/** A Selector defined as the conjunction (AND) of other (leaf) selectors. */ public class ConjunctionSelector extends AbstractSelector { private final List> leafComponents; @@ -38,9 +36,7 @@ public class ConjunctionSelector extends AbstractSelector { this.leafComponents = List.of(selectors); } - /** - * Tests if *all* selectors pass the test. - */ + /** Tests if *all* selectors pass the test. */ @Override public boolean test(T t) { return leafComponents.stream().allMatch(comp -> (comp.test(t))); diff --git a/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java index 212b6f9df314..0a8003034ea8 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/DisjunctionSelector.java @@ -26,9 +26,7 @@ import java.util.List; -/** - * A Selector defined as the disjunction (OR) of other (leaf) selectors. - */ +/** A Selector defined as the disjunction (OR) of other (leaf) selectors. */ public class DisjunctionSelector extends AbstractSelector { private final List> leafComponents; @@ -38,9 +36,7 @@ public class DisjunctionSelector extends AbstractSelector { this.leafComponents = List.of(selectors); } - /** - * Tests if *at least one* selector passes the test. - */ + /** Tests if *at least one* selector passes the test. */ @Override public boolean test(T t) { return leafComponents.stream().anyMatch(comp -> comp.test(t)); diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java index 32cbee4452a2..244b49d55666 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MassEqualSelector.java @@ -27,16 +27,12 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Mass; -/** - * Mass selector for values exactly equal than the parameter. - */ +/** Mass selector for values exactly equal than the parameter. */ public class MassEqualSelector extends AbstractSelector { private final Mass mass; - /** - * The use of a double as a parameter will spare some typing when instantiating this class. - */ + /** The use of a double as a parameter will spare some typing when instantiating this class. */ public MassEqualSelector(double mass) { this.mass = new Mass(mass); } diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java index d77b2cbdb590..62a92281ce66 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MassGreaterThanSelector.java @@ -27,16 +27,12 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Mass; -/** - * Mass selector for values greater than the parameter. - */ +/** Mass selector for values greater than the parameter. */ public class MassGreaterThanSelector extends AbstractSelector { private final Mass mass; - /** - * The use of a double as a parameter will spare some typing when instantiating this class. - */ + /** The use of a double as a parameter will spare some typing when instantiating this class. */ public MassGreaterThanSelector(double mass) { this.mass = new Mass(mass); } diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java index 75b6d0bc2591..7abb593a0e87 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MassSmallerThanOrEqSelector.java @@ -27,16 +27,12 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Mass; -/** - * Mass selector for values smaller or equal to the parameter. - */ +/** Mass selector for values smaller or equal to the parameter. */ public class MassSmallerThanOrEqSelector extends AbstractSelector { private final Mass mass; - /** - * The use of a double as a parameter will spare some typing when instantiating this class. - */ + /** The use of a double as a parameter will spare some typing when instantiating this class. */ public MassSmallerThanOrEqSelector(double mass) { this.mass = new Mass(mass); } diff --git a/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java index c4b6a5da2e87..d6c56de8a73f 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/MovementSelector.java @@ -27,9 +27,7 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Movement; -/** - * Movement selector. - */ +/** Movement selector. */ public class MovementSelector extends AbstractSelector { private final Movement movement; diff --git a/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java index 3239eee01293..2948e26bd309 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/NegationSelector.java @@ -24,7 +24,6 @@ */ package com.iluwatar.specification.selector; - /** * A Selector defined as the negation (NOT) of a (leaf) selectors. This is of course only useful * when used in combination with other composite selectors. @@ -37,9 +36,7 @@ public class NegationSelector extends AbstractSelector { this.component = selector; } - /** - * Tests if the selector fails the test (yes). - */ + /** Tests if the selector fails the test (yes). */ @Override public boolean test(T t) { return !(component.test(t)); diff --git a/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java b/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java index 16e6d1f22b14..c9b7764f9f39 100644 --- a/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java +++ b/specification/src/main/java/com/iluwatar/specification/selector/SizeSelector.java @@ -27,9 +27,7 @@ import com.iluwatar.specification.creature.Creature; import com.iluwatar.specification.property.Size; -/** - * Size selector. - */ +/** Size selector. */ public class SizeSelector extends AbstractSelector { private final Size size; diff --git a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java index 1d1be65a7e35..5b9d76843f0f 100644 --- a/specification/src/test/java/com/iluwatar/specification/app/AppTest.java +++ b/specification/src/test/java/com/iluwatar/specification/app/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.specification.app; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java index 96923741a8d6..e400c9835894 100644 --- a/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java +++ b/specification/src/test/java/com/iluwatar/specification/creature/CreatureTest.java @@ -36,10 +36,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -/** - * CreatureTest - * - */ +/** CreatureTest */ class CreatureTest { /** @@ -47,19 +44,24 @@ class CreatureTest { */ public static Collection dataProvider() { return List.of( - new Object[]{new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED, - new Mass(39300.0)}, - new Object[]{new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN, - new Mass(30.0)}, - new Object[]{new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT, - new Mass(6.7)}, - new Object[]{new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK, - new Mass(12.0)}, - new Object[]{new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT, - new Mass(500.0)}, - new Object[]{new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK, - new Mass(4000.0)} - ); + new Object[] { + new Dragon(), "Dragon", Size.LARGE, Movement.FLYING, Color.RED, new Mass(39300.0) + }, + new Object[] { + new Goblin(), "Goblin", Size.SMALL, Movement.WALKING, Color.GREEN, new Mass(30.0) + }, + new Object[] { + new KillerBee(), "KillerBee", Size.SMALL, Movement.FLYING, Color.LIGHT, new Mass(6.7) + }, + new Object[] { + new Octopus(), "Octopus", Size.NORMAL, Movement.SWIMMING, Color.DARK, new Mass(12.0) + }, + new Object[] { + new Shark(), "Shark", Size.NORMAL, Movement.SWIMMING, Color.LIGHT, new Mass(500.0) + }, + new Object[] { + new Troll(), "Troll", Size.LARGE, Movement.WALKING, Color.DARK, new Mass(4000.0) + }); } @ParameterizedTest @@ -82,25 +84,27 @@ void testGetMovement(Creature testedCreature, String name, Size size, Movement m @ParameterizedTest @MethodSource("dataProvider") - void testGetColor(Creature testedCreature, String name, Size size, Movement movement, - Color color) { + void testGetColor( + Creature testedCreature, String name, Size size, Movement movement, Color color) { assertEquals(color, testedCreature.getColor()); } @ParameterizedTest @MethodSource("dataProvider") - void testGetMass(Creature testedCreature, String name, Size size, Movement movement, - Color color, Mass mass) { + void testGetMass( + Creature testedCreature, String name, Size size, Movement movement, Color color, Mass mass) { assertEquals(mass, testedCreature.getMass()); } @ParameterizedTest @MethodSource("dataProvider") - void testToString(Creature testedCreature, String name, Size size, Movement movement, - Color color, Mass mass) { + void testToString( + Creature testedCreature, String name, Size size, Movement movement, Color color, Mass mass) { final var toString = testedCreature.toString(); assertNotNull(toString); - assertEquals(String - .format("%s [size=%s, movement=%s, color=%s, mass=%s]", name, size, movement, color, mass), toString); + assertEquals( + String.format( + "%s [size=%s, movement=%s, color=%s, mass=%s]", name, size, movement, color, mass), + toString); } -} \ No newline at end of file +} diff --git a/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java index d9f4aeaf5b70..d2e6d2624f5d 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/ColorSelectorTest.java @@ -33,15 +33,10 @@ import com.iluwatar.specification.property.Color; import org.junit.jupiter.api.Test; -/** - * ColorSelectorTest - * - */ +/** ColorSelectorTest */ class ColorSelectorTest { - /** - * Verify if the color selector gives the correct results - */ + /** Verify if the color selector gives the correct results */ @Test void testColor() { final var greenCreature = mock(Creature.class); @@ -53,7 +48,5 @@ void testColor() { final var greenSelector = new ColorSelector(Color.GREEN); assertTrue(greenSelector.test(greenCreature)); assertFalse(greenSelector.test(redCreature)); - } - -} \ No newline at end of file +} diff --git a/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java b/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java index cc314dd6bfaf..d7dc5e12c2a6 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/CompositeSelectorsTest.java @@ -36,9 +36,7 @@ class CompositeSelectorsTest { - /** - * Verify if the conjunction selector gives the correct results. - */ + /** Verify if the conjunction selector gives the correct results. */ @Test void testAndComposition() { final var swimmingHeavyCreature = mock(Creature.class); @@ -49,15 +47,13 @@ void testAndComposition() { when(swimmingLightCreature.getMovement()).thenReturn(Movement.SWIMMING); when(swimmingLightCreature.getMass()).thenReturn(new Mass(25.0)); - final var lightAndSwimmingSelector = new MassSmallerThanOrEqSelector(50.0) - .and(new MovementSelector(Movement.SWIMMING)); + final var lightAndSwimmingSelector = + new MassSmallerThanOrEqSelector(50.0).and(new MovementSelector(Movement.SWIMMING)); assertFalse(lightAndSwimmingSelector.test(swimmingHeavyCreature)); assertTrue(lightAndSwimmingSelector.test(swimmingLightCreature)); } - /** - * Verify if the disjunction selector gives the correct results. - */ + /** Verify if the disjunction selector gives the correct results. */ @Test void testOrComposition() { final var swimmingHeavyCreature = mock(Creature.class); @@ -68,15 +64,13 @@ void testOrComposition() { when(swimmingLightCreature.getMovement()).thenReturn(Movement.SWIMMING); when(swimmingLightCreature.getMass()).thenReturn(new Mass(25.0)); - final var lightOrSwimmingSelector = new MassSmallerThanOrEqSelector(50.0) - .or(new MovementSelector(Movement.SWIMMING)); + final var lightOrSwimmingSelector = + new MassSmallerThanOrEqSelector(50.0).or(new MovementSelector(Movement.SWIMMING)); assertTrue(lightOrSwimmingSelector.test(swimmingHeavyCreature)); assertTrue(lightOrSwimmingSelector.test(swimmingLightCreature)); } - /** - * Verify if the negation selector gives the correct results. - */ + /** Verify if the negation selector gives the correct results. */ @Test void testNotComposition() { final var swimmingHeavyCreature = mock(Creature.class); diff --git a/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java index b4697b60fe96..e55ddbde1346 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/MassSelectorTest.java @@ -35,9 +35,7 @@ class MassSelectorTest { - /** - * Verify if the mass selector gives the correct results. - */ + /** Verify if the mass selector gives the correct results. */ @Test void testMass() { final var lightCreature = mock(Creature.class); diff --git a/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java index 7118a68173b0..e0fb51569021 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/MovementSelectorTest.java @@ -33,15 +33,10 @@ import com.iluwatar.specification.property.Movement; import org.junit.jupiter.api.Test; -/** - * MovementSelectorTest - * - */ +/** MovementSelectorTest */ class MovementSelectorTest { - /** - * Verify if the movement selector gives the correct results. - */ + /** Verify if the movement selector gives the correct results. */ @Test void testMovement() { final var swimmingCreature = mock(Creature.class); @@ -53,7 +48,5 @@ void testMovement() { final var swimmingSelector = new MovementSelector(Movement.SWIMMING); assertTrue(swimmingSelector.test(swimmingCreature)); assertFalse(swimmingSelector.test(flyingCreature)); - } - -} \ No newline at end of file +} diff --git a/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java index 68f7692ef5bd..6aa65226ac17 100644 --- a/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java +++ b/specification/src/test/java/com/iluwatar/specification/selector/SizeSelectorTest.java @@ -33,15 +33,10 @@ import com.iluwatar.specification.property.Size; import org.junit.jupiter.api.Test; -/** - * SizeSelectorTest - * - */ +/** SizeSelectorTest */ class SizeSelectorTest { - /** - * Verify if the size selector gives the correct results - */ + /** Verify if the size selector gives the correct results */ @Test void testMovement() { final var normalCreature = mock(Creature.class); @@ -54,5 +49,4 @@ void testMovement() { assertTrue(normalSelector.test(normalCreature)); assertFalse(normalSelector.test(smallCreature)); } - } diff --git a/state/README.md b/state/README.md index 7fdc242aefef..b35768834bf5 100644 --- a/state/README.md +++ b/state/README.md @@ -38,6 +38,10 @@ Wikipedia says > The state pattern is a behavioral software design pattern that allows an object to alter its behavior when its internal state changes. This pattern is close to the concept of finite-state machines. The state pattern can be interpreted as a strategy pattern, which is able to switch a strategy through invocations of methods defined in the pattern's interface. +Flowchart + +![State flowchart](./etc/state-flowchart.png) + ## Programmatic Example of State Pattern in Java In our programmatic example there is a mammoth with alternating moods. diff --git a/state/etc/state-flowchart.png b/state/etc/state-flowchart.png new file mode 100644 index 000000000000..fb49c9e0ace2 Binary files /dev/null and b/state/etc/state-flowchart.png differ diff --git a/state/pom.xml b/state/pom.xml index c4ce3b6f382f..9bd6df825b64 100644 --- a/state/pom.xml +++ b/state/pom.xml @@ -34,6 +34,14 @@ state + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/state/src/main/java/com/iluwatar/state/AngryState.java b/state/src/main/java/com/iluwatar/state/AngryState.java index c20085c44838..f26126ee0696 100644 --- a/state/src/main/java/com/iluwatar/state/AngryState.java +++ b/state/src/main/java/com/iluwatar/state/AngryState.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Angry state. - */ +/** Angry state. */ @Slf4j public class AngryState implements State { @@ -47,5 +45,4 @@ public void observe() { public void onEnterState() { LOGGER.info("{} gets angry!", mammoth); } - } diff --git a/state/src/main/java/com/iluwatar/state/App.java b/state/src/main/java/com/iluwatar/state/App.java index 12ee7871bf9d..37d8ae37b09d 100644 --- a/state/src/main/java/com/iluwatar/state/App.java +++ b/state/src/main/java/com/iluwatar/state/App.java @@ -28,16 +28,14 @@ * In the State pattern, the container object has an internal state object that defines the current * behavior. The state object can be changed to alter the behavior. * - *

    This can be a cleaner way for an object to change its behavior at runtime without resorting - * to large monolithic conditional statements and thus improves maintainability. + *

    This can be a cleaner way for an object to change its behavior at runtime without resorting to + * large monolithic conditional statements and thus improves maintainability. * *

    In this example the {@link Mammoth} changes its behavior as time passes by. */ public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) { var mammoth = new Mammoth(); diff --git a/state/src/main/java/com/iluwatar/state/Mammoth.java b/state/src/main/java/com/iluwatar/state/Mammoth.java index 4815e1259473..f9b3006eca6e 100644 --- a/state/src/main/java/com/iluwatar/state/Mammoth.java +++ b/state/src/main/java/com/iluwatar/state/Mammoth.java @@ -1,62 +1,58 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.state; - -/** - * Mammoth has internal state that defines its behavior. - */ -public class Mammoth { - - private State state; - - public Mammoth() { - state = new PeacefulState(this); - } - - /** - * Makes time pass for the mammoth. - */ - public void timePasses() { - if (state.getClass().equals(PeacefulState.class)) { - changeStateTo(new AngryState(this)); - } else { - changeStateTo(new PeacefulState(this)); - } - } - - private void changeStateTo(State newState) { - this.state = newState; - this.state.onEnterState(); - } - - @Override - public String toString() { - return "The mammoth"; - } - - public void observe() { - this.state.observe(); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.state; + +/** Mammoth has internal state that defines its behavior. */ +public class Mammoth { + + private State state; + + public Mammoth() { + state = new PeacefulState(this); + } + + /** Makes time pass for the mammoth. */ + public void timePasses() { + if (state.getClass().equals(PeacefulState.class)) { + changeStateTo(new AngryState(this)); + } else { + changeStateTo(new PeacefulState(this)); + } + } + + private void changeStateTo(State newState) { + this.state = newState; + this.state.onEnterState(); + } + + @Override + public String toString() { + return "The mammoth"; + } + + public void observe() { + this.state.observe(); + } +} diff --git a/state/src/main/java/com/iluwatar/state/PeacefulState.java b/state/src/main/java/com/iluwatar/state/PeacefulState.java index 4f558bb7d1a7..3511ba4cb7c0 100644 --- a/state/src/main/java/com/iluwatar/state/PeacefulState.java +++ b/state/src/main/java/com/iluwatar/state/PeacefulState.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Peaceful state. - */ +/** Peaceful state. */ @Slf4j public class PeacefulState implements State { @@ -47,5 +45,4 @@ public void observe() { public void onEnterState() { LOGGER.info("{} calms down.", mammoth); } - } diff --git a/state/src/main/java/com/iluwatar/state/State.java b/state/src/main/java/com/iluwatar/state/State.java index ee39a8aa69b5..f305dff21ebf 100644 --- a/state/src/main/java/com/iluwatar/state/State.java +++ b/state/src/main/java/com/iluwatar/state/State.java @@ -1,35 +1,33 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.state; - -/** - * State interface. - */ -public interface State { - - void onEnterState(); - - void observe(); -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.state; + +/** State interface. */ +public interface State { + + void onEnterState(); + + void observe(); +} diff --git a/state/src/test/java/com/iluwatar/state/AppTest.java b/state/src/test/java/com/iluwatar/state/AppTest.java index e6623790ddec..dc80141b96b7 100644 --- a/state/src/test/java/com/iluwatar/state/AppTest.java +++ b/state/src/test/java/com/iluwatar/state/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.state; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/state/src/test/java/com/iluwatar/state/MammothTest.java b/state/src/test/java/com/iluwatar/state/MammothTest.java index 6299635803f1..8fa8be1ea0e2 100644 --- a/state/src/test/java/com/iluwatar/state/MammothTest.java +++ b/state/src/test/java/com/iluwatar/state/MammothTest.java @@ -37,10 +37,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * MammothTest - * - */ +/** MammothTest */ class MammothTest { private InMemoryAppender appender; @@ -82,12 +79,9 @@ void testTimePasses() { mammoth.observe(); assertEquals("The mammoth is calm and peaceful.", appender.getLastMessage()); assertEquals(5, appender.getLogSize()); - } - /** - * Verify if {@link Mammoth#toString()} gives the expected value - */ + /** Verify if {@link Mammoth#toString()} gives the expected value */ @Test void testToString() { final var toString = new Mammoth().toString(); @@ -116,5 +110,4 @@ public String getLastMessage() { return log.get(log.size() - 1).getFormattedMessage(); } } - } diff --git a/step-builder/README.md b/step-builder/README.md index a651cff45642..3b0f4c479720 100644 --- a/step-builder/README.md +++ b/step-builder/README.md @@ -35,6 +35,10 @@ Wikipedia says > The Step Builder pattern is a variation of the Builder design pattern, designed to provide a flexible solution for constructing complex objects step-by-step. This pattern is particularly useful when an object requires multiple initialization steps, which can be done incrementally to ensure clarity and flexibility in the creation process. +Sequence diagram + +![Step Builder sequence diagram](./etc/step-builder-sequence-diagram.png) + ## Programmatic Example of Step Builder Pattern in Java The Step Builder pattern in Java is an extension of the Builder pattern that guides the user through the creation of an object in a step-by-step manner. This pattern improves the user experience by only showing the next step methods available, and not showing the build method until it's the right time to build the object. @@ -162,10 +166,6 @@ Console output: 12:58:13.889 [main] INFO com.iluwatar.stepbuilder.App -- This is a Rogue named Desmond armed with a with nothing. ``` -## Detailed Explanation of Step Builder Pattern with Real-World Examples - -![Step Builder](./etc/step-builder.png "Step Builder") - ## When to Use the Step Builder Pattern in Java The Step Builder pattern in Java is used diff --git a/step-builder/etc/step-builder-sequence-diagram.png b/step-builder/etc/step-builder-sequence-diagram.png new file mode 100644 index 000000000000..c2c45821963a Binary files /dev/null and b/step-builder/etc/step-builder-sequence-diagram.png differ diff --git a/step-builder/pom.xml b/step-builder/pom.xml index 2c6979d489d8..79ebbec525ed 100644 --- a/step-builder/pom.xml +++ b/step-builder/pom.xml @@ -34,6 +34,14 @@ step-builder + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java b/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java index a5ffb6060c15..dfa73e573282 100644 --- a/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java +++ b/step-builder/src/main/java/com/iluwatar/stepbuilder/App.java @@ -31,30 +31,28 @@ * *

    Intent
    * An extension of the Builder pattern that fully guides the user through the creation of the object - * with no chances of confusion.
    The user experience will be much more improved by the fact - * that he will only see the next step methods available, NO build method until is the right time to - * build the object. + * with no chances of confusion.
    + * The user experience will be much more improved by the fact that he will only see the next step + * methods available, NO build method until is the right time to build the object. * *

    Implementation
    * The concept is simple: - *

      - * - *
    • Write creational steps inner classes or interfaces where each method knows what can be - * displayed next.
    • * - *
    • Implement all your steps interfaces in an inner static class.
    • - * - *
    • Last step is the BuildStep, in charge of creating the object you need to build.
    • + *
        + *
      • Write creational steps inner classes or interfaces where each method knows what can be + * displayed next. + *
      • Implement all your steps interfaces in an inner static class. + *
      • Last step is the BuildStep, in charge of creating the object you need to build. *
      * *

      Applicability
      * Use the Step Builder pattern when the algorithm for creating a complex object should be * independent of the parts that make up the object and how they're assembled the construction * process must allow different representations for the object that's constructed when in the - * process of constructing the order is important. - *
      + * process of constructing the order is important.
      * - * @see http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html + * @see http://rdafbn.blogspot.co.uk/2012/07/step-builder-pattern_28.html */ @Slf4j public class App { @@ -66,34 +64,30 @@ public class App { */ public static void main(String[] args) { - var warrior = CharacterStepBuilder - .newBuilder() - .name("Amberjill") - .fighterClass("Paladin") - .withWeapon("Sword") - .noAbilities() - .build(); + var warrior = + CharacterStepBuilder.newBuilder() + .name("Amberjill") + .fighterClass("Paladin") + .withWeapon("Sword") + .noAbilities() + .build(); LOGGER.info(warrior.toString()); - var mage = CharacterStepBuilder - .newBuilder() - .name("Riobard") - .wizardClass("Sorcerer") - .withSpell("Fireball") - .withAbility("Fire Aura") - .withAbility("Teleport") - .noMoreAbilities() - .build(); + var mage = + CharacterStepBuilder.newBuilder() + .name("Riobard") + .wizardClass("Sorcerer") + .withSpell("Fireball") + .withAbility("Fire Aura") + .withAbility("Teleport") + .noMoreAbilities() + .build(); LOGGER.info(mage.toString()); - var thief = CharacterStepBuilder - .newBuilder() - .name("Desmond") - .fighterClass("Rogue") - .noWeapon() - .build(); + var thief = + CharacterStepBuilder.newBuilder().name("Desmond").fighterClass("Rogue").noWeapon().build(); LOGGER.info(thief.toString()); } diff --git a/step-builder/src/main/java/com/iluwatar/stepbuilder/Character.java b/step-builder/src/main/java/com/iluwatar/stepbuilder/Character.java index 3ecd70de42fa..dedcd33efd33 100644 --- a/step-builder/src/main/java/com/iluwatar/stepbuilder/Character.java +++ b/step-builder/src/main/java/com/iluwatar/stepbuilder/Character.java @@ -28,11 +28,7 @@ import lombok.Getter; import lombok.Setter; - - -/** - * The class with many parameters. - */ +/** The class with many parameters. */ @Getter @Setter public class Character { @@ -48,7 +44,6 @@ public Character(String name) { this.name = name; } - @Override public String toString() { return new StringBuilder() diff --git a/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java b/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java index 01c23130fe70..ff5b136312fd 100644 --- a/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java +++ b/step-builder/src/main/java/com/iluwatar/stepbuilder/CharacterStepBuilder.java @@ -27,21 +27,16 @@ import java.util.ArrayList; import java.util.List; -/** - * The Step Builder class. - */ +/** The Step Builder class. */ public final class CharacterStepBuilder { - private CharacterStepBuilder() { - } + private CharacterStepBuilder() {} public static NameStep newBuilder() { return new CharacterSteps(); } - /** - * First Builder Step in charge of the Character name. Next Step available : ClassStep - */ + /** First Builder Step in charge of the Character name. Next Step available : ClassStep */ public interface NameStep { ClassStep name(String name); } @@ -76,9 +71,7 @@ public interface SpellStep { BuildStep noSpell(); } - /** - * This step is in charge of abilities. Next Step available : BuildStep - */ + /** This step is in charge of abilities. Next Step available : BuildStep */ public interface AbilityStep { AbilityStep withAbility(String ability); @@ -94,12 +87,9 @@ public interface BuildStep { Character build(); } - - /** - * Step Builder implementation. - */ - private static class CharacterSteps implements NameStep, ClassStep, WeaponStep, SpellStep, - AbilityStep, BuildStep { + /** Step Builder implementation. */ + private static class CharacterSteps + implements NameStep, ClassStep, WeaponStep, SpellStep, AbilityStep, BuildStep { private String name; private String fighterClass; diff --git a/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java b/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java index b147f6da847c..1fcf9ae93cd4 100644 --- a/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java +++ b/step-builder/src/test/java/com/iluwatar/stepbuilder/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.stepbuilder; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java b/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java index f205722637bd..e6c1ba59d0c4 100644 --- a/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java +++ b/step-builder/src/test/java/com/iluwatar/stepbuilder/CharacterStepBuilderTest.java @@ -31,25 +31,21 @@ import org.junit.jupiter.api.Test; -/** - * CharacterStepBuilderTest - * - */ +/** CharacterStepBuilderTest */ class CharacterStepBuilderTest { - /** - * Build a new wizard {@link Character} and verify if it has the expected attributes - */ + /** Build a new wizard {@link Character} and verify if it has the expected attributes */ @Test void testBuildWizard() { - final var character = CharacterStepBuilder.newBuilder() - .name("Merlin") - .wizardClass("alchemist") - .withSpell("poison") - .withAbility("invisibility") - .withAbility("wisdom") - .noMoreAbilities() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .withSpell("poison") + .withAbility("invisibility") + .withAbility("wisdom") + .noMoreAbilities() + .build(); assertEquals("Merlin", character.getName()); assertEquals("alchemist", character.getWizardClass()); @@ -61,7 +57,6 @@ void testBuildWizard() { assertEquals(2, abilities.size()); assertTrue(abilities.contains("invisibility")); assertTrue(abilities.contains("wisdom")); - } /** @@ -70,53 +65,46 @@ void testBuildWizard() { */ @Test void testBuildPoorWizard() { - final var character = CharacterStepBuilder.newBuilder() - .name("Merlin") - .wizardClass("alchemist") - .noSpell() - .build(); + final var character = + CharacterStepBuilder.newBuilder().name("Merlin").wizardClass("alchemist").noSpell().build(); assertEquals("Merlin", character.getName()); assertEquals("alchemist", character.getWizardClass()); assertNull(character.getSpell()); assertNull(character.getAbilities()); assertNotNull(character.toString()); - } - /** - * Build a new wizard {@link Character} and verify if it has the expected attributes - */ + /** Build a new wizard {@link Character} and verify if it has the expected attributes */ @Test void testBuildWeakWizard() { - final var character = CharacterStepBuilder.newBuilder() - .name("Merlin") - .wizardClass("alchemist") - .withSpell("poison") - .noAbilities() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Merlin") + .wizardClass("alchemist") + .withSpell("poison") + .noAbilities() + .build(); assertEquals("Merlin", character.getName()); assertEquals("alchemist", character.getWizardClass()); assertEquals("poison", character.getSpell()); assertNull(character.getAbilities()); assertNotNull(character.toString()); - } - /** - * Build a new warrior {@link Character} and verify if it has the expected attributes - */ + /** Build a new warrior {@link Character} and verify if it has the expected attributes */ @Test void testBuildWarrior() { - final var character = CharacterStepBuilder.newBuilder() - .name("Cuauhtemoc") - .fighterClass("aztec") - .withWeapon("spear") - .withAbility("speed") - .withAbility("strength") - .noMoreAbilities() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Cuauhtemoc") + .fighterClass("aztec") + .withWeapon("spear") + .withAbility("speed") + .withAbility("strength") + .noMoreAbilities() + .build(); assertEquals("Cuauhtemoc", character.getName()); assertEquals("aztec", character.getFighterClass()); @@ -128,7 +116,6 @@ void testBuildWarrior() { assertEquals(2, abilities.size()); assertTrue(abilities.contains("speed")); assertTrue(abilities.contains("strength")); - } /** @@ -137,18 +124,18 @@ void testBuildWarrior() { */ @Test void testBuildPoorWarrior() { - final var character = CharacterStepBuilder.newBuilder() - .name("Poor warrior") - .fighterClass("none") - .noWeapon() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Poor warrior") + .fighterClass("none") + .noWeapon() + .build(); assertEquals("Poor warrior", character.getName()); assertEquals("none", character.getFighterClass()); assertNull(character.getWeapon()); assertNull(character.getAbilities()); assertNotNull(character.toString()); - } /** @@ -157,19 +144,18 @@ void testBuildPoorWarrior() { */ @Test void testBuildWeakWarrior() { - final var character = CharacterStepBuilder.newBuilder() - .name("Weak warrior") - .fighterClass("none") - .withWeapon("Slingshot") - .noAbilities() - .build(); + final var character = + CharacterStepBuilder.newBuilder() + .name("Weak warrior") + .fighterClass("none") + .withWeapon("Slingshot") + .noAbilities() + .build(); assertEquals("Weak warrior", character.getName()); assertEquals("none", character.getFighterClass()); assertEquals("Slingshot", character.getWeapon()); assertNull(character.getAbilities()); assertNotNull(character.toString()); - } - -} \ No newline at end of file +} diff --git a/strangler/README.md b/strangler/README.md index 569a2151f38d..22baf44ec541 100644 --- a/strangler/README.md +++ b/strangler/README.md @@ -32,6 +32,10 @@ Wikipedia says > The Strangler Design Pattern involves incrementally migrating a legacy system by gradually replacing it with a new system. It wraps old code with new code, redirecting or logging uses of the old code to ensure a seamless transition. This pattern is named after the strangler fig plant, which grows around a host tree and eventually replaces it entirely. It's particularly useful for modernizing monolithic applications and transitioning them to microservices architecture with minimal risk and disruption. +Flowchart + +![Strangler flowchart](./etc/strangler-flowchart.png) + ## Programmatic Example of Strangler Pattern in Java The Strangler design pattern in Java is a software design pattern that incrementally migrates a legacy system by gradually replacing specific pieces of functionality with new applications and services. As features from the legacy system are replaced, the new system eventually replaces all the old system's features, strangling the old system and allowing you to decommission it. diff --git a/strangler/etc/strangler-flowchart.png b/strangler/etc/strangler-flowchart.png new file mode 100644 index 000000000000..20574690967b Binary files /dev/null and b/strangler/etc/strangler-flowchart.png differ diff --git a/strangler/pom.xml b/strangler/pom.xml index 74b800b06cfb..c05d6c7ec29a 100644 --- a/strangler/pom.xml +++ b/strangler/pom.xml @@ -34,6 +34,14 @@ 4.0.0 strangler + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/strangler/src/main/java/com/iluwatar/strangler/App.java b/strangler/src/main/java/com/iluwatar/strangler/App.java index 930ce457d837..ab8382ca6c9a 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/App.java +++ b/strangler/src/main/java/com/iluwatar/strangler/App.java @@ -25,43 +25,40 @@ package com.iluwatar.strangler; /** + * The Strangler pattern is a software design pattern that incrementally migrate a legacy system by + * gradually replacing specific pieces of functionality with new applications and services. As + * features from the legacy system are replaced, the new system eventually replaces all of the old + * system's features, strangling the old system and allowing you to decommission it. * - *

      The Strangler pattern is a software design pattern that incrementally migrate a legacy - * system by gradually replacing specific pieces of functionality with new applications and - * services. As features from the legacy system are replaced, the new system eventually - * replaces all of the old system's features, strangling the old system and allowing you - * to decommission it.

      - * - *

      This pattern is not only about updating but also enhancement.

      - * - *

      In this example, {@link OldArithmetic} indicates old system and its implementation depends - * on its source ({@link OldSource}). Now we tend to update system with new techniques and - * new features. In reality, the system may too complex, so usually need gradual migration. - * {@link HalfArithmetic} indicates system in the process of migration, its implementation - * depends on old one ({@link OldSource}) and under development one ({@link HalfSource}). The - * {@link HalfSource} covers part of {@link OldSource} and add new functionality. You can release - * this version system with new features, which also supports old version system functionalities. - * After whole migration, the new system ({@link NewArithmetic}) only depends on new source - * ({@link NewSource}).

      + *

      This pattern is not only about updating but also enhancement. * + *

      In this example, {@link OldArithmetic} indicates old system and its implementation depends on + * its source ({@link OldSource}). Now we tend to update system with new techniques and new + * features. In reality, the system may too complex, so usually need gradual migration. {@link + * HalfArithmetic} indicates system in the process of migration, its implementation depends on old + * one ({@link OldSource}) and under development one ({@link HalfSource}). The {@link HalfSource} + * covers part of {@link OldSource} and add new functionality. You can release this version system + * with new features, which also supports old version system functionalities. After whole migration, + * the new system ({@link NewArithmetic}) only depends on new source ({@link NewSource}). */ public class App { /** * Program entry point. + * * @param args command line args */ public static void main(final String[] args) { - final var nums = new int[]{1, 2, 3, 4, 5}; - //Before migration + final var nums = new int[] {1, 2, 3, 4, 5}; + // Before migration final var oldSystem = new OldArithmetic(new OldSource()); oldSystem.sum(nums); oldSystem.mul(nums); - //In process of migration + // In process of migration final var halfSystem = new HalfArithmetic(new HalfSource(), new OldSource()); halfSystem.sum(nums); halfSystem.mul(nums); halfSystem.ifHasZero(nums); - //After migration + // After migration final var newSystem = new NewArithmetic(new NewSource()); newSystem.sum(nums); newSystem.mul(nums); diff --git a/strangler/src/main/java/com/iluwatar/strangler/HalfArithmetic.java b/strangler/src/main/java/com/iluwatar/strangler/HalfArithmetic.java index 161ec0655eba..a34e46bde4b0 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/HalfArithmetic.java +++ b/strangler/src/main/java/com/iluwatar/strangler/HalfArithmetic.java @@ -27,8 +27,8 @@ import lombok.extern.slf4j.Slf4j; /** - * System under migration. Depends on old version source ({@link OldSource}) and - * developing one ({@link HalfSource}). + * System under migration. Depends on old version source ({@link OldSource}) and developing one + * ({@link HalfSource}). */ @Slf4j public class HalfArithmetic { @@ -44,6 +44,7 @@ public HalfArithmetic(HalfSource newSource, OldSource oldSource) { /** * Accumulate sum. + * * @param nums numbers need to add together * @return accumulate sum */ @@ -54,6 +55,7 @@ public int sum(int... nums) { /** * Accumulate multiplication. + * * @param nums numbers need to multiply together * @return accumulate multiplication */ @@ -64,6 +66,7 @@ public int mul(int... nums) { /** * Check if it has any zero. + * * @param nums numbers need to check * @return if it has any zero, return true, else, return false */ diff --git a/strangler/src/main/java/com/iluwatar/strangler/HalfSource.java b/strangler/src/main/java/com/iluwatar/strangler/HalfSource.java index 9413b55d2733..ad9f38ec036c 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/HalfSource.java +++ b/strangler/src/main/java/com/iluwatar/strangler/HalfSource.java @@ -27,26 +27,18 @@ import java.util.Arrays; import lombok.extern.slf4j.Slf4j; -/** - * Source under development. Replace part of old source and has added some new features. - */ +/** Source under development. Replace part of old source and has added some new features. */ @Slf4j public class HalfSource { private static final String VERSION = "1.5"; - /** - * Implement accumulate sum with new technique. - * Replace old one in {@link OldSource} - */ + /** Implement accumulate sum with new technique. Replace old one in {@link OldSource} */ public int accumulateSum(int... nums) { LOGGER.info("Source module {}", VERSION); return Arrays.stream(nums).reduce(0, Integer::sum); } - /** - * Check if all number is not zero. - * New feature. - */ + /** Check if all number is not zero. New feature. */ public boolean ifNonZero(int... nums) { LOGGER.info("Source module {}", VERSION); return Arrays.stream(nums).allMatch(num -> num != 0); diff --git a/strangler/src/main/java/com/iluwatar/strangler/NewArithmetic.java b/strangler/src/main/java/com/iluwatar/strangler/NewArithmetic.java index bd90fc12347c..75156da20e7c 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/NewArithmetic.java +++ b/strangler/src/main/java/com/iluwatar/strangler/NewArithmetic.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * System after whole migration. Only depends on new version source ({@link NewSource}). - */ +/** System after whole migration. Only depends on new version source ({@link NewSource}). */ @Slf4j public class NewArithmetic { private static final String VERSION = "2.0"; @@ -41,6 +39,7 @@ public NewArithmetic(NewSource source) { /** * Accumulate sum. + * * @param nums numbers need to add together * @return accumulate sum */ @@ -51,6 +50,7 @@ public int sum(int... nums) { /** * Accumulate multiplication. + * * @param nums numbers need to multiply together * @return accumulate multiplication */ @@ -61,6 +61,7 @@ public int mul(int... nums) { /** * Check if it has any zero. + * * @param nums numbers need to check * @return if it has any zero, return true, else, return false */ diff --git a/strangler/src/main/java/com/iluwatar/strangler/NewSource.java b/strangler/src/main/java/com/iluwatar/strangler/NewSource.java index 25bef8a3e07a..78e709408846 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/NewSource.java +++ b/strangler/src/main/java/com/iluwatar/strangler/NewSource.java @@ -28,8 +28,8 @@ import lombok.extern.slf4j.Slf4j; /** - * New source. Completely covers functionalities of old source with new techniques - * and also has some new features. + * New source. Completely covers functionalities of old source with new techniques and also has some + * new features. */ @Slf4j public class NewSource { @@ -41,10 +41,7 @@ public int accumulateSum(int... nums) { return Arrays.stream(nums).reduce(0, Integer::sum); } - /** - * Implement accumulate multiply with new technique. - * Replace old one in {@link OldSource} - */ + /** Implement accumulate multiply with new technique. Replace old one in {@link OldSource} */ public int accumulateMul(int... nums) { LOGGER.info(SOURCE_MODULE, VERSION); return Arrays.stream(nums).reduce(1, (a, b) -> a * b); diff --git a/strangler/src/main/java/com/iluwatar/strangler/OldArithmetic.java b/strangler/src/main/java/com/iluwatar/strangler/OldArithmetic.java index 5b5162b32e6c..299f61d30657 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/OldArithmetic.java +++ b/strangler/src/main/java/com/iluwatar/strangler/OldArithmetic.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Old version system depends on old version source ({@link OldSource}). - */ +/** Old version system depends on old version source ({@link OldSource}). */ @Slf4j public class OldArithmetic { private static final String VERSION = "1.0"; @@ -41,6 +39,7 @@ public OldArithmetic(OldSource source) { /** * Accumulate sum. + * * @param nums numbers need to add together * @return accumulate sum */ @@ -51,6 +50,7 @@ public int sum(int... nums) { /** * Accumulate multiplication. + * * @param nums numbers need to multiply together * @return accumulate multiplication */ diff --git a/strangler/src/main/java/com/iluwatar/strangler/OldSource.java b/strangler/src/main/java/com/iluwatar/strangler/OldSource.java index 7e833dea9dd9..5be29bffc879 100644 --- a/strangler/src/main/java/com/iluwatar/strangler/OldSource.java +++ b/strangler/src/main/java/com/iluwatar/strangler/OldSource.java @@ -26,16 +26,12 @@ import lombok.extern.slf4j.Slf4j; -/** - * Old source with techniques out of date. - */ +/** Old source with techniques out of date. */ @Slf4j public class OldSource { private static final String VERSION = "1.0"; - /** - * Implement accumulate sum with old technique. - */ + /** Implement accumulate sum with old technique. */ public int accumulateSum(int... nums) { LOGGER.info("Source module {}", VERSION); var sum = 0; @@ -45,9 +41,7 @@ public int accumulateSum(int... nums) { return sum; } - /** - * Implement accumulate multiply with old technique. - */ + /** Implement accumulate multiply with old technique. */ public int accumulateMul(int... nums) { LOGGER.info("Source module {}", VERSION); var sum = 1; diff --git a/strangler/src/test/java/com/iluwatar/strangler/AppTest.java b/strangler/src/test/java/com/iluwatar/strangler/AppTest.java index 2e5c2015011f..777af17660e3 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/AppTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.strangler; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/strangler/src/test/java/com/iluwatar/strangler/HalfArithmeticTest.java b/strangler/src/test/java/com/iluwatar/strangler/HalfArithmeticTest.java index 94aee37435d0..c8f92e18af24 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/HalfArithmeticTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/HalfArithmeticTest.java @@ -29,11 +29,10 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in HalfArithmetic - */ +/** Test methods in HalfArithmetic */ class HalfArithmeticTest { - private static final HalfArithmetic arithmetic = new HalfArithmetic(new HalfSource(), new OldSource()); + private static final HalfArithmetic arithmetic = + new HalfArithmetic(new HalfSource(), new OldSource()); @Test void testSum() { @@ -49,4 +48,4 @@ void testMul() { void testIfHasZero() { assertTrue(arithmetic.ifHasZero(-1, 0, 1)); } -} \ No newline at end of file +} diff --git a/strangler/src/test/java/com/iluwatar/strangler/HalfSourceTest.java b/strangler/src/test/java/com/iluwatar/strangler/HalfSourceTest.java index 4b6926a0c8d9..09ae98c925a9 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/HalfSourceTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/HalfSourceTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in HalfSource - */ +/** Test methods in HalfSource */ class HalfSourceTest { private static final HalfSource source = new HalfSource(); diff --git a/strangler/src/test/java/com/iluwatar/strangler/NewArithmeticTest.java b/strangler/src/test/java/com/iluwatar/strangler/NewArithmeticTest.java index 51dda81c63e8..5c6958f3d77c 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/NewArithmeticTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/NewArithmeticTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in NewArithmetic - */ +/** Test methods in NewArithmetic */ class NewArithmeticTest { private static final NewArithmetic arithmetic = new NewArithmetic(new NewSource()); @@ -49,4 +47,4 @@ void testMul() { void testIfHasZero() { assertTrue(arithmetic.ifHasZero(-1, 0, 1)); } -} \ No newline at end of file +} diff --git a/strangler/src/test/java/com/iluwatar/strangler/NewSourceTest.java b/strangler/src/test/java/com/iluwatar/strangler/NewSourceTest.java index 2ceb745ee96a..658aa53971fe 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/NewSourceTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/NewSourceTest.java @@ -29,9 +29,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in NewSource - */ +/** Test methods in NewSource */ class NewSourceTest { private static final NewSource source = new NewSource(); diff --git a/strangler/src/test/java/com/iluwatar/strangler/OldArithmeticTest.java b/strangler/src/test/java/com/iluwatar/strangler/OldArithmeticTest.java index 4c449cbdfb7e..4ce9d2dddaa0 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/OldArithmeticTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/OldArithmeticTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in OldArithmetic - */ +/** Test methods in OldArithmetic */ class OldArithmeticTest { private static final OldArithmetic arithmetic = new OldArithmetic(new OldSource()); @@ -43,4 +41,4 @@ void testSum() { void testMul() { assertEquals(0, arithmetic.mul(-1, 0, 1)); } -} \ No newline at end of file +} diff --git a/strangler/src/test/java/com/iluwatar/strangler/OldSourceTest.java b/strangler/src/test/java/com/iluwatar/strangler/OldSourceTest.java index cb1d1b331bf4..b90d08af33b4 100644 --- a/strangler/src/test/java/com/iluwatar/strangler/OldSourceTest.java +++ b/strangler/src/test/java/com/iluwatar/strangler/OldSourceTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test methods in OldSource - */ +/** Test methods in OldSource */ class OldSourceTest { private static final OldSource source = new OldSource(); diff --git a/strategy/README.md b/strategy/README.md index 08004ad25a1c..9f9148ccd842 100644 --- a/strategy/README.md +++ b/strategy/README.md @@ -34,6 +34,10 @@ Wikipedia says > In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. +Flowchart + +![Strategy flowchart](./etc/strategy-flowchart.png) + ## Programmatic Example of Strategy Pattern in Java Slaying dragons is a dangerous job. With experience, it becomes easier. Veteran dragonslayers have developed different fighting strategies against different types of dragons. @@ -127,7 +131,7 @@ public class App { // Java 8 functional implementation Strategy pattern LOGGER.info(GREEN_DRAGON_SPOTTED); dragonSlayer = new DragonSlayer( - () -> LOGGER.info("With your Excalibur you severe the dragon's head!")); + () -> LOGGER.info("With your Excalibur you sever the dragon's head!")); dragonSlayer.goToBattle(); LOGGER.info(RED_DRAGON_EMERGES); dragonSlayer.changeStrategy(() -> LOGGER.info( @@ -162,13 +166,13 @@ Program output: 13:06:36.634 [main] INFO com.iluwatar.strategy.App -- Black dragon lands before you. 13:06:36.634 [main] INFO com.iluwatar.strategy.SpellStrategy -- You cast the spell of disintegration and the dragon vaporizes in a pile of dust! 13:06:36.634 [main] INFO com.iluwatar.strategy.App -- Green dragon spotted ahead! -13:06:36.634 [main] INFO com.iluwatar.strategy.App -- With your Excalibur you severe the dragon's head! +13:06:36.634 [main] INFO com.iluwatar.strategy.App -- With your Excalibur you sever the dragon's head! 13:06:36.634 [main] INFO com.iluwatar.strategy.App -- Red dragon emerges. 13:06:36.635 [main] INFO com.iluwatar.strategy.App -- You shoot the dragon with the magical crossbow and it falls dead on the ground! 13:06:36.635 [main] INFO com.iluwatar.strategy.App -- Black dragon lands before you. 13:06:36.635 [main] INFO com.iluwatar.strategy.App -- You cast the spell of disintegration and the dragon vaporizes in a pile of dust! 13:06:36.635 [main] INFO com.iluwatar.strategy.App -- Green dragon spotted ahead! -13:06:36.637 [main] INFO com.iluwatar.strategy.LambdaStrategy -- With your Excalibur you severe the dragon's head! +13:06:36.637 [main] INFO com.iluwatar.strategy.LambdaStrategy -- With your Excalibur you sever the dragon's head! 13:06:36.637 [main] INFO com.iluwatar.strategy.App -- Red dragon emerges. 13:06:36.637 [main] INFO com.iluwatar.strategy.LambdaStrategy -- You shoot the dragon with the magical crossbow and it falls dead on the ground! 13:06:36.637 [main] INFO com.iluwatar.strategy.App -- Black dragon lands before you. diff --git a/strategy/etc/strategy-flowchart.png b/strategy/etc/strategy-flowchart.png new file mode 100644 index 000000000000..18623f248e00 Binary files /dev/null and b/strategy/etc/strategy-flowchart.png differ diff --git a/strategy/pom.xml b/strategy/pom.xml index 9b7b1e9c80a1..3c84050bf19d 100644 --- a/strategy/pom.xml +++ b/strategy/pom.xml @@ -34,6 +34,14 @@ strategy + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/strategy/src/main/java/com/iluwatar/strategy/App.java b/strategy/src/main/java/com/iluwatar/strategy/App.java index aa603c1065ed..6c24d0e46ad5 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/App.java +++ b/strategy/src/main/java/com/iluwatar/strategy/App.java @@ -27,17 +27,15 @@ import lombok.extern.slf4j.Slf4j; /** + * The Strategy pattern (also known as the policy pattern) is a software design pattern that enables + * an algorithm's behavior to be selected at runtime. * - *

      The Strategy pattern (also known as the policy pattern) is a software design pattern that - * enables an algorithm's behavior to be selected at runtime.

      - * - *

      Before Java 8 the Strategies needed to be separate classes forcing the developer - * to write lots of boilerplate code. With modern Java, it is easy to pass behavior - * with method references and lambdas making the code shorter and more readable.

      + *

      Before Java 8 the Strategies needed to be separate classes forcing the developer to write lots + * of boilerplate code. With modern Java, it is easy to pass behavior with method references and + * lambdas making the code shorter and more readable. * *

      In this example ({@link DragonSlayingStrategy}) encapsulates an algorithm. The containing - * object ({@link DragonSlayer}) can alter its behavior by changing its strategy.

      - * + * object ({@link DragonSlayer}) can alter its behavior by changing its strategy. */ @Slf4j public class App { @@ -65,16 +63,20 @@ public static void main(String[] args) { // Java 8 functional implementation Strategy pattern LOGGER.info(GREEN_DRAGON_SPOTTED); - dragonSlayer = new DragonSlayer( - () -> LOGGER.info("With your Excalibur you severe the dragon's head!")); + dragonSlayer = + new DragonSlayer(() -> LOGGER.info("With your Excalibur you sever the dragon's head!")); dragonSlayer.goToBattle(); LOGGER.info(RED_DRAGON_EMERGES); - dragonSlayer.changeStrategy(() -> LOGGER.info( - "You shoot the dragon with the magical crossbow and it falls dead on the ground!")); + dragonSlayer.changeStrategy( + () -> + LOGGER.info( + "You shoot the dragon with the magical crossbow and it falls dead on the ground!")); dragonSlayer.goToBattle(); LOGGER.info(BLACK_DRAGON_LANDS); - dragonSlayer.changeStrategy(() -> LOGGER.info( - "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); + dragonSlayer.changeStrategy( + () -> + LOGGER.info( + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); dragonSlayer.goToBattle(); // Java 8 lambda implementation with enum Strategy pattern @@ -88,4 +90,4 @@ public static void main(String[] args) { dragonSlayer.changeStrategy(LambdaStrategy.Strategy.SPELL_STRATEGY); dragonSlayer.goToBattle(); } -} \ No newline at end of file +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java index 43416a4f620d..1e623e665e3f 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java +++ b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayer.java @@ -1,45 +1,43 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -/** - * DragonSlayer uses different strategies to slay the dragon. - */ -public class DragonSlayer { - - private DragonSlayingStrategy strategy; - - public DragonSlayer(DragonSlayingStrategy strategy) { - this.strategy = strategy; - } - - public void changeStrategy(DragonSlayingStrategy strategy) { - this.strategy = strategy; - } - - public void goToBattle() { - strategy.execute(); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +/** DragonSlayer uses different strategies to slay the dragon. */ +public class DragonSlayer { + + private DragonSlayingStrategy strategy; + + public DragonSlayer(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void changeStrategy(DragonSlayingStrategy strategy) { + this.strategy = strategy; + } + + public void goToBattle() { + strategy.execute(); + } +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java index ccba437368bd..c05b1965a90a 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/DragonSlayingStrategy.java @@ -1,35 +1,32 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -/** - * Strategy interface. - */ -@FunctionalInterface -public interface DragonSlayingStrategy { - - void execute(); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +/** Strategy interface. */ +@FunctionalInterface +public interface DragonSlayingStrategy { + + void execute(); +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/LambdaStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/LambdaStrategy.java index e31621db0ea3..27137bcc10e3 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/LambdaStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/LambdaStrategy.java @@ -26,22 +26,21 @@ import lombok.extern.slf4j.Slf4j; -/** - * Lambda implementation for enum strategy pattern. - */ +/** Lambda implementation for enum strategy pattern. */ @Slf4j public class LambdaStrategy { - /** - * Enum to demonstrate strategy pattern. - */ + /** Enum to demonstrate strategy pattern. */ public enum Strategy implements DragonSlayingStrategy { - MELEE_STRATEGY(() -> LOGGER.info( - "With your Excalibur you severe the dragon's head!")), - PROJECTILE_STRATEGY(() -> LOGGER.info( - "You shoot the dragon with the magical crossbow and it falls dead on the ground!")), - SPELL_STRATEGY(() -> LOGGER.info( - "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); + MELEE_STRATEGY(() -> LOGGER.info("With your Excalibur you sever the dragon's head!")), + PROJECTILE_STRATEGY( + () -> + LOGGER.info( + "You shoot the dragon with the magical crossbow and it falls dead on the ground!")), + SPELL_STRATEGY( + () -> + LOGGER.info( + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!")); private final DragonSlayingStrategy dragonSlayingStrategy; @@ -54,4 +53,4 @@ public void execute() { dragonSlayingStrategy.execute(); } } -} \ No newline at end of file +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java index 23769944c3f4..6bf4603f4b5c 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/MeleeStrategy.java @@ -1,39 +1,37 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -import lombok.extern.slf4j.Slf4j; - -/** - * Melee strategy. - */ -@Slf4j -public class MeleeStrategy implements DragonSlayingStrategy { - - @Override - public void execute() { - LOGGER.info("With your Excalibur you sever the dragon's head!"); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +import lombok.extern.slf4j.Slf4j; + +/** Melee strategy. */ +@Slf4j +public class MeleeStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("With your Excalibur you sever the dragon's head!"); + } +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java index 93af41fa6bb9..38f51bd8a434 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/ProjectileStrategy.java @@ -1,39 +1,37 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -import lombok.extern.slf4j.Slf4j; - -/** - * Projectile strategy. - */ -@Slf4j -public class ProjectileStrategy implements DragonSlayingStrategy { - - @Override - public void execute() { - LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!"); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +import lombok.extern.slf4j.Slf4j; + +/** Projectile strategy. */ +@Slf4j +public class ProjectileStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!"); + } +} diff --git a/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java b/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java index 5b7127bc28b0..6b284dbd74ee 100644 --- a/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java +++ b/strategy/src/main/java/com/iluwatar/strategy/SpellStrategy.java @@ -1,40 +1,37 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.strategy; - -import lombok.extern.slf4j.Slf4j; - -/** - * Spell strategy. - */ -@Slf4j -public class SpellStrategy implements DragonSlayingStrategy { - - @Override - public void execute() { - LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"); - } - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.strategy; + +import lombok.extern.slf4j.Slf4j; + +/** Spell strategy. */ +@Slf4j +public class SpellStrategy implements DragonSlayingStrategy { + + @Override + public void execute() { + LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!"); + } +} diff --git a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java index 5e0ec437a7b0..e26c469a57da 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/AppTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.strategy; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java index d0df5f468651..49b636ecfbbb 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayerTest.java @@ -30,15 +30,10 @@ import org.junit.jupiter.api.Test; -/** - * DragonSlayerTest - * - */ +/** DragonSlayerTest */ class DragonSlayerTest { - /** - * Verify if the dragon slayer uses the strategy during battle. - */ + /** Verify if the dragon slayer uses the strategy during battle. */ @Test void testGoToBattle() { final var strategy = mock(DragonSlayingStrategy.class); @@ -49,9 +44,7 @@ void testGoToBattle() { verifyNoMoreInteractions(strategy); } - /** - * Verify if the dragon slayer uses the new strategy during battle after a change of strategy. - */ + /** Verify if the dragon slayer uses the new strategy during battle after a change of strategy. */ @Test void testChangeStrategy() { final var initialStrategy = mock(DragonSlayingStrategy.class); @@ -68,4 +61,4 @@ void testChangeStrategy() { verifyNoMoreInteractions(initialStrategy, newStrategy); } -} \ No newline at end of file +} diff --git a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java index 75ed97366984..6162c641cd7f 100644 --- a/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java +++ b/strategy/src/test/java/com/iluwatar/strategy/DragonSlayingStrategyTest.java @@ -38,10 +38,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.LoggerFactory; -/** - * DragonSlayingStrategyTest - * - */ +/** DragonSlayingStrategyTest */ class DragonSlayingStrategyTest { /** @@ -51,19 +48,15 @@ class DragonSlayingStrategyTest { */ static Collection dataProvider() { return List.of( - new Object[]{ - new MeleeStrategy(), - "With your Excalibur you sever the dragon's head!" - }, - new Object[]{ - new ProjectileStrategy(), - "You shoot the dragon with the magical crossbow and it falls dead on the ground!" + new Object[] {new MeleeStrategy(), "With your Excalibur you sever the dragon's head!"}, + new Object[] { + new ProjectileStrategy(), + "You shoot the dragon with the magical crossbow and it falls dead on the ground!" }, - new Object[]{ - new SpellStrategy(), - "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!" - } - ); + new Object[] { + new SpellStrategy(), + "You cast the spell of disintegration and the dragon vaporizes in a pile of dust!" + }); } private InMemoryAppender appender; @@ -78,10 +71,7 @@ void tearDown() { appender.stop(); } - - /** - * Test if executing the strategy gives the correct response. - */ + /** Test if executing the strategy gives the correct response. */ @ParameterizedTest @MethodSource("dataProvider") void testExecute(DragonSlayingStrategy strategy, String expectedResult) { diff --git a/subclass-sandbox/README.md b/subclass-sandbox/README.md index 1dbea163ed19..ffe2727492d2 100644 --- a/subclass-sandbox/README.md +++ b/subclass-sandbox/README.md @@ -35,6 +35,10 @@ In plain words > A base class defines an abstract sandbox method and several provided operations. Marking them protected makes it clear that they are for use by derived classes. Each derived sandboxed subclass implements the sandbox method using the provided operations. +Flowchart + +![Subclass Sandbox flowchart](./etc/subclass-sandbox-flowchart.png) + ## Programmatic Example of Subclass Sandbox Pattern in Java Using the Subclass Sandbox pattern, developers can create distinct functionalities within Java applications, enhancing game development and software design. diff --git a/subclass-sandbox/etc/subclass-sandbox-flowchart.png b/subclass-sandbox/etc/subclass-sandbox-flowchart.png new file mode 100644 index 000000000000..580d2c03018c Binary files /dev/null and b/subclass-sandbox/etc/subclass-sandbox-flowchart.png differ diff --git a/subclass-sandbox/pom.xml b/subclass-sandbox/pom.xml index bc3a2a2d7920..9d21c7400f09 100644 --- a/subclass-sandbox/pom.xml +++ b/subclass-sandbox/pom.xml @@ -33,11 +33,30 @@ 4.0.0 subclass-sandbox + + 2.0.17 + 1.5.18 + com.github.stefanbirkner system-lambda + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + + + ch.qos.logback + logback-core + ${logback.version} + org.junit.jupiter junit-jupiter-engine diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java index fad01e36c525..7db986965aff 100644 --- a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/App.java @@ -27,17 +27,18 @@ import lombok.extern.slf4j.Slf4j; /** - * The subclass sandbox pattern describes a basic idea, while not having a lot - * of detailed mechanics. You will need the pattern when you have several similar - * subclasses. If you have to make a tiny change, then change the base class, - * while all subclasses shouldn't have to be touched. So the base class has to be - * able to provide all the operations a derived class needs to perform. + * The subclass sandbox pattern describes a basic idea, while not having a lot of detailed + * mechanics. You will need the pattern when you have several similar subclasses. If you have to + * make a tiny change, then change the base class, while all subclasses shouldn't have to be + * touched. So the base class has to be able to provide all the operations a derived class needs to + * perform. */ @Slf4j public class App { /** * Entry point of the main program. + * * @param args Program runtime arguments. */ public static void main(String[] args) { @@ -48,5 +49,4 @@ public static void main(String[] args) { var groundDive = new GroundDive(); groundDive.activate(); } - } diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java index 6cd9d324a8da..606dd3206684 100644 --- a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/GroundDive.java @@ -26,9 +26,7 @@ import org.slf4j.LoggerFactory; -/** - * GroundDive superpower. - */ +/** GroundDive superpower. */ public class GroundDive extends Superpower { public GroundDive() { diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java index 9bab6ef9f37e..611dacb2ff3d 100644 --- a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/SkyLaunch.java @@ -26,9 +26,7 @@ import org.slf4j.LoggerFactory; -/** - * SkyLaunch superpower. - */ +/** SkyLaunch superpower. */ public class SkyLaunch extends Superpower { public SkyLaunch() { diff --git a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java index 3c4f42aba53e..fefcce736105 100644 --- a/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java +++ b/subclass-sandbox/src/main/java/com/iluwatar/subclasssandbox/Superpower.java @@ -27,21 +27,22 @@ import org.slf4j.Logger; /** - * Superpower abstract class. In this class the basic operations of all types of - * superpowers are provided as protected methods. + * Superpower abstract class. In this class the basic operations of all types of superpowers are + * provided as protected methods. */ public abstract class Superpower { protected Logger logger; /** - * Subclass of superpower should implement this sandbox method by calling the - * methods provided in this super class. + * Subclass of superpower should implement this sandbox method by calling the methods provided in + * this super class. */ protected abstract void activate(); /** * Move to (x, y, z). + * * @param x X coordinate. * @param y Y coordinate. * @param z Z coordinate. @@ -52,6 +53,7 @@ protected void move(double x, double y, double z) { /** * Play sound effect for the superpower. + * * @param soundName Sound name. * @param volume Value of volume. */ @@ -61,6 +63,7 @@ protected void playSound(String soundName, int volume) { /** * Spawn particles for the superpower. + * * @param particleType Particle type. * @param count Count of particles to be spawned. */ diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java index e578f32ed03a..18eb36065d42 100644 --- a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * App unit tests. - */ +/** App unit tests. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java index cda2ee8c493d..45cb4ff50b4a 100644 --- a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/GroundDiveTest.java @@ -30,9 +30,7 @@ import com.github.stefanbirkner.systemlambda.Statement; import org.junit.jupiter.api.Test; -/** - * GroundDive unit tests. - */ +/** GroundDive unit tests. */ class GroundDiveTest { @Test @@ -55,8 +53,7 @@ void testPlaySound() throws Exception { @Test void testSpawnParticles() throws Exception { var groundDive = new GroundDive(); - final var outputLog = getLogContent( - () -> groundDive.spawnParticles("PARTICLE_TYPE", 100)); + final var outputLog = getLogContent(() -> groundDive.spawnParticles("PARTICLE_TYPE", 100)); final var expectedLog = "Spawn 100 particle with type PARTICLE_TYPE"; assertEquals(outputLog, expectedLog); } @@ -64,8 +61,7 @@ void testSpawnParticles() throws Exception { @Test void testActivate() throws Exception { var groundDive = new GroundDive(); - var logs = tapSystemOutNormalized(groundDive::activate) - .split("\n"); + var logs = tapSystemOutNormalized(groundDive::activate).split("\n"); final var expectedSize = 3; final var log1 = logs[0].split("--")[1].trim(); final var expectedLog1 = "Move to ( 0.0, 0.0, -20.0 )"; @@ -87,5 +83,4 @@ private String getLogContent(Statement statement) throws Exception { private String getLogContent(String log) { return log.split("--")[1].trim(); } - } diff --git a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java index 5af6cf68e415..3af507fb0be9 100644 --- a/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java +++ b/subclass-sandbox/src/test/java/com/iluwatar/subclasssandbox/SkyLaunchTest.java @@ -30,9 +30,7 @@ import com.github.stefanbirkner.systemlambda.Statement; import org.junit.jupiter.api.Test; -/** - * SkyLaunch unit tests. - */ +/** SkyLaunch unit tests. */ class SkyLaunchTest { @Test @@ -54,8 +52,7 @@ void testPlaySound() throws Exception { @Test void testSpawnParticles() throws Exception { var skyLaunch = new SkyLaunch(); - var outputLog = getLogContent( - () -> skyLaunch.spawnParticles("PARTICLE_TYPE", 100)); + var outputLog = getLogContent(() -> skyLaunch.spawnParticles("PARTICLE_TYPE", 100)); var expectedLog = "Spawn 100 particle with type PARTICLE_TYPE"; assertEquals(outputLog, expectedLog); } @@ -63,8 +60,7 @@ void testSpawnParticles() throws Exception { @Test void testActivate() throws Exception { var skyLaunch = new SkyLaunch(); - var logs = tapSystemOutNormalized(skyLaunch::activate) - .split("\n"); + var logs = tapSystemOutNormalized(skyLaunch::activate).split("\n"); final var expectedSize = 3; final var log1 = getLogContent(logs[0]); final var expectedLog1 = "Move to ( 0.0, 0.0, 20.0 )"; diff --git a/table-inheritance/README.md b/table-inheritance/README.md new file mode 100644 index 000000000000..fc9f7500286c --- /dev/null +++ b/table-inheritance/README.md @@ -0,0 +1,256 @@ +--- +title: "Table Inheritance Pattern in Java: Modeling Hierarchical Data in Relational Databases" +shortTitle: Table Inheritance +description: "Explore the Table Inheritance pattern in Java with real-world examples, database schema, and tutorials. Learn how to model class hierarchies elegantly in relational databases." +category: Data access +language: en +tag: + - Data access + - Database + - Inheritance + - Persistence + - Polymorphism +--- + +## Also known as + +* Class Table Inheritance +* Joined Table Inheritance + +## Intent of Table Inheritance Pattern + +Represent inheritance hierarchies in relational databases by mapping each class in a hierarchy to a database table. + +## Detailed Explanation of Table Inheritance Pattern with Real-World Examples + +Real-world example + +> A classic real-world analogy for the Table Inheritance (Joined Table) pattern is managing employee records in an organization: +> Imagine a company's database storing information about employees. All employees have common attributes (name, employee ID, hire date), stored in a general "Employee" table. However, the company also has different types of employees: Full-time Employees (with a salary and benefits) and Contractors (hourly rate, contract duration). Each employee type has distinct data stored in separate specialized tables ("FullTimeEmployee" and "Contractor"), which reference the main "Employee" table. +> This structure mirrors the Table Inheritance pattern—shared fields in a common table and unique fields split into subclass-specific tables. + +In plain words + +> The Table Inheritance pattern maps each class within an inheritance hierarchy to its own database table, storing common attributes in a base table and subclass-specific attributes in separate joined tables. + +Martin Fowler says + +> Relational databases don't support inheritance, which creates a mismatch when mapping objects. To fix this, Table Inheritance uses a separate table for each class in the hierarchy while maintaining relationships through foreign keys, making it easier to link the classes together in the database. + +Mind map + +![Table Inheritance Pattern Mind Map](./etc/table-inheritance-mind-map.png) + +## Programmatic Example of Table Inheritance Pattern in Java + +The `Vehicle` class will be the superclass, and we will have subclasses `Car` and `Truck` that extend `Vehicle`. The superclass `Vehicle` stores common attributes, while subclasses store their own specific attributes. + +### Key Aspects of the Pattern: + +**Superclass (`Vehicle`):** + +The superclass stores shared attributes: + +* `make`: Manufacturer of the vehicle. +* `model`: Model of the vehicle. +* `year`: Year of manufacture. +* `id`: Unique identifier for the vehicle. + +These common attributes will reside in a dedicated database table (`Vehicle` table). + +**Subclasses (`Car` and `Truck`):** + +Each subclass adds attributes specific to its type: + +* `Car`: `numberOfDoors`, indicating how many doors the car has. +* `Truck`: `payloadCapacity`, representing how much payload the truck can carry. + +Each subclass stores these specific attributes in their respective tables (`Car` and `Truck` tables). + +**Foreign Key Relationship:** + +Each subclass table references the superclass table via a foreign key. The subclass's `id` links to the primary key of the superclass, thus connecting common and subclass-specific data. + +### Java Implementation Using JPA Annotations: + +```java +@Setter +@Getter +public class Vehicle { + + private String make; + private String model; + private int year; + private int id; + + public Vehicle(int year, String make, String model, int id) { + this.make = make; + this.model = model; + this.year = year; + this.id = id; + } + + @Override + public String toString() { + return "Vehicle{" + + "id=" + + id + + ", make='" + + make + + '\'' + + ", model='" + + model + + '\'' + + ", year=" + + year + + '}'; + } +} + +@Getter +public class Car extends Vehicle { + private int numDoors; + + public Car(int year, String make, String model, int numDoors, int id) { + super(year, make, model, id); + if (numDoors <= 0) { + throw new IllegalArgumentException("Number of doors must be positive."); + } + this.numDoors = numDoors; + } + + public void setNumDoors(int doors) { + if (doors <= 0) { + throw new IllegalArgumentException("Number of doors must be positive."); + } + this.numDoors = doors; + } + + @Override + public String toString() { + return "Car{" + + "id=" + + getId() + + ", make='" + + getMake() + + '\'' + + ", model='" + + getModel() + + '\'' + + ", year=" + + getYear() + + ", numberOfDoors=" + + getNumDoors() + + '}'; + } +} + +@Getter +public class Truck extends Vehicle { + private double loadCapacity; + + public Truck(int year, String make, String model, double loadCapacity, int id) { + super(year, make, model, id); + if (loadCapacity <= 0) { + throw new IllegalArgumentException("Load capacity must be positive."); + } + this.loadCapacity = loadCapacity; + } + + public void setLoadCapacity(double capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Load capacity must be positive."); + } + this.loadCapacity = capacity; + } + + @Override + public String toString() { + return "Truck{" + + "id=" + + getId() + + ", make='" + + getMake() + + '\'' + + ", model='" + + getModel() + + '\'' + + ", year=" + + getYear() + + ", payloadCapacity=" + + getLoadCapacity() + + '}'; + } +} +``` + +### Explanation of the JPA annotations used above: + +* `@Entity`: Indicates that the class is a JPA entity mapped to a database table. +* `@Inheritance(strategy = InheritanceType.JOINED)`: Configures joined table inheritance, meaning each class (superclass and subclasses) maps to its own table. +* `@Table(name = "XYZ")`: Explicitly specifies the database table name for clarity. +* `@Id`: Marks the primary key of the entity. +* `@GeneratedValue(strategy = GenerationType.IDENTITY)`: Specifies auto-generation of primary key values by the database. + +### Database Structure Result: + +Applying this code will result in three database tables structured as follows: + +**Vehicle table** +* id +* make +* model +* year + +**Car table** +* id (FK to Vehicle) +* numberOfDoors + +**Truck table** +* id (FK to Vehicle) +* payloadCapacity + +This approach clearly represents the Table Inheritance (Joined Table) pattern, with common attributes centrally managed in the superclass table and subclass-specific attributes cleanly separated in their own tables. + +## When to Use the Table Inheritance Pattern in Java + +* When persisting an inheritance hierarchy of Java classes in a relational database. +* Suitable when classes share common attributes but also have distinct fields. +* Beneficial when polymorphic queries across subclasses are frequent. + +## Table Inheritance Pattern Java Tutorials + +- [Software Patterns Lexicon: Class Table Inheritance](https://softwarepatternslexicon.com/patterns-sql/4/4/2/) +- [Martin Fowler: Class Table Inheritance](http://thierryroussel.free.fr/java/books/martinfowler/www.martinfowler.com/isa/classTableInheritance.html) + +## Real-World Applications of Table Inheritance Pattern in Java + +* Hibernate ORM (`@Inheritance(strategy = InheritanceType.JOINED)` in Java) +* EclipseLink (Joined Inheritance strategy in JPA) +* Spring Data JPA applications modeling complex domain hierarchies. + +## Benefits and Trade-offs of Table Inheritance Pattern + +Benefits: + + * Normalized database schema reduces redundancy. + * Clearly models class hierarchies at the database level. + * Easier to implement polymorphic queries due to clear class distinctions. + +Trade-offs: + + * Increased complexity in database queries involving multiple joins. + * Reduced performance for deep inheritance hierarchies due to costly joins. + * Maintenance overhead increases with the complexity of inheritance structures. + +## Related Java Design Patterns + +* [Single Table Inheritance](https://java-design-patterns.com/patterns/single-table-inheritance/): Alternative strategy mapping an entire class hierarchy into a single database table, useful when fewer joins are preferred at the cost of nullable columns. +* Concrete Table Inheritance – Each subclass has its own standalone table; related in providing an alternate approach to storing inheritance hierarchies. + +## References and Credits + +* [Java Persistence with Hibernate](https://amzn.to/44tP1ox) +* [Object-Relational Mapping (Wikipedia)](https://en.wikipedia.org/wiki/Object-relational_mapping) +* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR) +* [Pro JPA 2: Mastering the Java Persistence API](https://amzn.to/4b7UoMC) diff --git a/table-inheritance/etc/table-inheritance-mind-map.png b/table-inheritance/etc/table-inheritance-mind-map.png new file mode 100644 index 000000000000..c403077b667b Binary files /dev/null and b/table-inheritance/etc/table-inheritance-mind-map.png differ diff --git a/table-inheritance/etc/table-inheritance.urm.puml b/table-inheritance/etc/table-inheritance.urm.puml new file mode 100644 index 000000000000..966fdc8d8e5e --- /dev/null +++ b/table-inheritance/etc/table-inheritance.urm.puml @@ -0,0 +1,52 @@ +@startuml +package com.iluwatar.table.inheritance { + class App { + + App() + + main(args : String[]) {static} + } + class Car { + - numDoors : int + + Car(year : int, make : String, model : String, numDoors : int, id : int) + + getNumDoors() : int + + setNumDoors(doors : int) + + toString() : String + } + class Truck { + - loadCapacity : double + + Truck(year : int, make : String, model : String, loadCapacity : double, id : int) + + getLoadCapacity() : double + + setLoadCapacity(capacity : double) + + toString() : String + } + class Vehicle { + - id : int + - make : String + - model : String + - year : int + + Vehicle(year : int, make : String, model : String, id : int) + + getId() : int + + getMake() : String + + getModel() : String + + getYear() : int + + setId(id : int) + + setMake(make : String) + + setModel(model : String) + + setYear(year : int) + + toString() : String + } + class VehicleDatabase { + - carTable : Map + ~ logger : Logger + - truckTable : Map + - vehicleTable : Map + + VehicleDatabase() + + getCar(id : int) : Car + + getTruck(id : int) : Truck + + getVehicle(id : int) : Vehicle + + printAllVehicles() + + saveVehicle(vehicle : Vehicle) + } +} +Car --|> Vehicle +Truck --|> Vehicle +@enduml \ No newline at end of file diff --git a/table-inheritance/pom.xml b/table-inheritance/pom.xml new file mode 100644 index 000000000000..e5ca009ed848 --- /dev/null +++ b/table-inheritance/pom.xml @@ -0,0 +1,50 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + table-inheritance + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + + \ No newline at end of file diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/App.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/App.java new file mode 100644 index 000000000000..da097ab9edec --- /dev/null +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/App.java @@ -0,0 +1,85 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; + +import java.util.logging.Logger; + +/** + * The main entry point of the application demonstrating the use of vehicles. + * + *

      The Table Inheritance pattern models a class hierarchy in a relational database by creating + * separate tables for each class in the hierarchy. These tables share a common primary key, which + * in subclass tables also serves as a foreign key referencing the primary key of the base class + * table. This linkage maintains relationships and effectively represents the inheritance structure. + * This pattern enables the organization of complex data models, particularly when subclasses have + * unique properties that must be stored in distinct tables. + */ +public class App { + /** + * Manages the storage and retrieval of Vehicle objects, including Cars and Trucks. + * + *

      This example demonstrates the **Table Inheritance** pattern, where each vehicle type (Car + * and Truck) is stored in its own separate table. The `VehicleDatabase` simulates a simple + * database that manages these entities, with each subclass (Car and Truck) being stored in its + * respective table. + * + *

      The `VehicleDatabase` contains the following tables: - `vehicleTable`: Stores all vehicle + * objects, including both `Car` and `Truck` objects. - `carTable`: Stores only `Car` objects, + * with fields specific to cars. - `truckTable`: Stores only `Truck` objects, with fields specific + * to trucks. + * + *

      The example demonstrates: 1. Saving instances of `Car` and `Truck` to their respective + * tables in the database. 2. Retrieving vehicles (both cars and trucks) from the appropriate + * table based on their ID. 3. Printing all vehicles stored in the database. 4. Showing how to + * retrieve specific types of vehicles (`Car` or `Truck`) by their IDs. + * + *

      In the **Table Inheritance** pattern, each subclass has its own table, making it easier to + * manage specific attributes of each subclass. + * + * @param args command-line arguments + */ + public static void main(String[] args) { + + final Logger logger = Logger.getLogger(App.class.getName()); + + VehicleDatabase database = new VehicleDatabase(); + + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + Truck truck = new Truck(2018, "Ford", "F-150", 60, 2); + + database.saveVehicle(car); + database.saveVehicle(truck); + + database.printAllVehicles(); + + Vehicle vehicle = database.getVehicle(car.getId()); + Car retrievedCar = database.getCar(car.getId()); + Truck retrievedTruck = database.getTruck(truck.getId()); + + logger.info(String.format("Retrieved Vehicle: %s", vehicle)); + logger.info(String.format("Retrieved Car: %s", retrievedCar)); + logger.info(String.format("Retrieved Truck: %s", retrievedTruck)); + } +} diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Car.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Car.java new file mode 100644 index 000000000000..0adaaa648b51 --- /dev/null +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Car.java @@ -0,0 +1,80 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; + +import lombok.Getter; + +/** Represents a car with a specific number of doors. */ +@Getter +public class Car extends Vehicle { + private int numDoors; + + /** + * Constructs a Car object. + * + * @param year the manufacturing year + * @param make the make of the car + * @param model the model of the car + * @param numDoors the number of doors + * @param id the unique identifier for the car + */ + public Car(int year, String make, String model, int numDoors, int id) { + super(year, make, model, id); + if (numDoors <= 0) { + throw new IllegalArgumentException("Number of doors must be positive."); + } + this.numDoors = numDoors; + } + + /** + * Sets the number of doors for the car. + * + * @param doors the number of doors + */ + public void setNumDoors(int doors) { + if (doors <= 0) { + throw new IllegalArgumentException("Number of doors must be positive."); + } + this.numDoors = doors; + } + + @Override + public String toString() { + return "Car{" + + "id=" + + getId() + + ", make='" + + getMake() + + '\'' + + ", model='" + + getModel() + + '\'' + + ", year=" + + getYear() + + ", numberOfDoors=" + + getNumDoors() + + '}'; + } +} diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Truck.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Truck.java new file mode 100644 index 000000000000..b79c5362215b --- /dev/null +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Truck.java @@ -0,0 +1,85 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; + +import lombok.Getter; + +/** Represents a truck, a type of vehicle with a specific load capacity. */ +@Getter +public class Truck extends Vehicle { + private double loadCapacity; + + /** + * Constructs a Truck object with the given parameters. + * + * @param year the year of manufacture + * @param make the make of the truck + * @param model the model of the truck + * @param loadCapacity the load capacity of the truck + * @param id the unique ID of the truck + */ + public Truck(int year, String make, String model, double loadCapacity, int id) { + super(year, make, model, id); + if (loadCapacity <= 0) { + throw new IllegalArgumentException("Load capacity must be positive."); + } + this.loadCapacity = loadCapacity; + } + + /** + * Sets the load capacity of the truck. + * + * @param capacity the new load capacity + */ + public void setLoadCapacity(double capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Load capacity must be positive."); + } + this.loadCapacity = capacity; + } + + /** + * Returns a string representation of the truck. + * + * @return a string with the truck's details + */ + @Override + public String toString() { + return "Truck{" + + "id=" + + getId() + + ", make='" + + getMake() + + '\'' + + ", model='" + + getModel() + + '\'' + + ", year=" + + getYear() + + ", payloadCapacity=" + + getLoadCapacity() + + '}'; + } +} diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Vehicle.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Vehicle.java new file mode 100644 index 000000000000..87db4092d3a6 --- /dev/null +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/Vehicle.java @@ -0,0 +1,75 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; + +import lombok.Getter; +import lombok.Setter; + +/** Represents a generic vehicle with basic attributes like make, model, year, and ID. */ +@Setter +@Getter +public class Vehicle { + + private String make; + private String model; + private int year; + private int id; + + /** + * Constructs a Vehicle object with the given parameters. + * + * @param year the year of manufacture + * @param make the make of the vehicle + * @param model the model of the vehicle + * @param id the unique ID of the vehicle + */ + public Vehicle(int year, String make, String model, int id) { + this.make = make; + this.model = model; + this.year = year; + this.id = id; + } + + /** + * Returns a string representation of the vehicle. + * + * @return a string with the vehicle's details + */ + @Override + public String toString() { + return "Vehicle{" + + "id=" + + id + + ", make='" + + make + + '\'' + + ", model='" + + model + + '\'' + + ", year=" + + year + + '}'; + } +} diff --git a/table-inheritance/src/main/java/com/iluwatar/table/inheritance/VehicleDatabase.java b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/VehicleDatabase.java new file mode 100644 index 000000000000..0f04ab0a959e --- /dev/null +++ b/table-inheritance/src/main/java/com/iluwatar/table/inheritance/VehicleDatabase.java @@ -0,0 +1,91 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +/** Manages the storage and retrieval of Vehicle objects, including Cars and Trucks. */ +public class VehicleDatabase { + + final Logger logger = Logger.getLogger(VehicleDatabase.class.getName()); + + private Map vehicleTable = new HashMap<>(); + private Map carTable = new HashMap<>(); + private Map truckTable = new HashMap<>(); + + /** + * Saves a vehicle to the database. If the vehicle is a Car or Truck, it is added to the + * respective table. + * + * @param vehicle the vehicle to save + */ + public void saveVehicle(Vehicle vehicle) { + vehicleTable.put(vehicle.getId(), vehicle); + if (vehicle instanceof Car) { + carTable.put(vehicle.getId(), (Car) vehicle); + } else if (vehicle instanceof Truck) { + truckTable.put(vehicle.getId(), (Truck) vehicle); + } + } + + /** + * Retrieves a vehicle by its ID. + * + * @param id the ID of the vehicle + * @return the vehicle with the given ID, or null if not found + */ + public Vehicle getVehicle(int id) { + return vehicleTable.get(id); + } + + /** + * Retrieves a car by its ID. + * + * @param id the ID of the car + * @return the car with the given ID, or null if not found + */ + public Car getCar(int id) { + return carTable.get(id); + } + + /** + * Retrieves a truck by its ID. + * + * @param id the ID of the truck + * @return the truck with the given ID, or null if not found + */ + public Truck getTruck(int id) { + return truckTable.get(id); + } + + /** Prints all vehicles in the database. */ + public void printAllVehicles() { + for (Vehicle vehicle : vehicleTable.values()) { + logger.info(vehicle.toString()); + } + } +} diff --git a/table-inheritance/src/test/java/com/iluwatar/table/inheritance/AppTest.java b/table-inheritance/src/test/java/com/iluwatar/table/inheritance/AppTest.java new file mode 100644 index 000000000000..8cbae5b2bfb9 --- /dev/null +++ b/table-inheritance/src/test/java/com/iluwatar/table/inheritance/AppTest.java @@ -0,0 +1,92 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; /* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.logging.ConsoleHandler; +import java.util.logging.Handler; +import java.util.logging.Logger; +import org.junit.jupiter.api.Test; + +/** Tests if the main method runs without throwing exceptions and prints expected output. */ +class AppTest { + + @Test + void testAppMainMethod() { + + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(outContent); + + System.setOut(printStream); + + Logger logger = Logger.getLogger(App.class.getName()); + + Handler handler = + new ConsoleHandler() { + @Override + public void publish(java.util.logging.LogRecord recordObj) { + printStream.println(getFormatter().format(recordObj)); + } + }; + handler.setLevel(java.util.logging.Level.ALL); + logger.addHandler(handler); + + App.main(new String[] {}); + + String output = outContent.toString(); + + assertTrue(output.contains("Retrieved Vehicle:")); + assertTrue(output.contains("Toyota")); // Car make + assertTrue(output.contains("Ford")); // Truck make + assertTrue(output.contains("Retrieved Car:")); + assertTrue(output.contains("Retrieved Truck:")); + } +} diff --git a/table-inheritance/src/test/java/com/iluwatar/table/inheritance/VehicleDatabaseTest.java b/table-inheritance/src/test/java/com/iluwatar/table/inheritance/VehicleDatabaseTest.java new file mode 100644 index 000000000000..9c290fd5c325 --- /dev/null +++ b/table-inheritance/src/test/java/com/iluwatar/table/inheritance/VehicleDatabaseTest.java @@ -0,0 +1,208 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.table.inheritance; /* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for the {@link VehicleDatabase} class. Tests saving, retrieving, and printing vehicles + * of different types. + */ +class VehicleDatabaseTest { + + private VehicleDatabase vehicleDatabase; + + /** Sets up a new instance of {@link VehicleDatabase} before each test. */ + @BeforeEach + void setUp() { + vehicleDatabase = new VehicleDatabase(); + } + + /** Tests saving a {@link Car} to the database and retrieving it. */ + @Test + void testSaveAndRetrieveCar() { + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + vehicleDatabase.saveVehicle(car); + + Vehicle retrievedVehicle = vehicleDatabase.getVehicle(car.getId()); + assertNotNull(retrievedVehicle); + assertEquals(car.getId(), retrievedVehicle.getId()); + assertEquals(car.getMake(), retrievedVehicle.getMake()); + assertEquals(car.getModel(), retrievedVehicle.getModel()); + assertEquals(car.getYear(), retrievedVehicle.getYear()); + + Car retrievedCar = vehicleDatabase.getCar(car.getId()); + assertNotNull(retrievedCar); + assertEquals(car.getNumDoors(), retrievedCar.getNumDoors()); + } + + /** Tests saving a {@link Truck} to the database and retrieving it. */ + @Test + void testSaveAndRetrieveTruck() { + Truck truck = new Truck(2018, "Ford", "F-150", 60, 2); + vehicleDatabase.saveVehicle(truck); + + Vehicle retrievedVehicle = vehicleDatabase.getVehicle(truck.getId()); + assertNotNull(retrievedVehicle); + assertEquals(truck.getId(), retrievedVehicle.getId()); + assertEquals(truck.getMake(), retrievedVehicle.getMake()); + assertEquals(truck.getModel(), retrievedVehicle.getModel()); + assertEquals(truck.getYear(), retrievedVehicle.getYear()); + + Truck retrievedTruck = vehicleDatabase.getTruck(truck.getId()); + assertNotNull(retrievedTruck); + assertEquals(truck.getLoadCapacity(), retrievedTruck.getLoadCapacity()); + } + + /** Tests saving multiple vehicles to the database and printing them. */ + @Test + void testPrintAllVehicles() { + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + Truck truck = new Truck(2018, "Ford", "F-150", 60, 2); + vehicleDatabase.saveVehicle(car); + vehicleDatabase.saveVehicle(truck); + + vehicleDatabase.printAllVehicles(); + + Vehicle retrievedCar = vehicleDatabase.getVehicle(car.getId()); + Vehicle retrievedTruck = vehicleDatabase.getVehicle(truck.getId()); + + assertNotNull(retrievedCar); + assertNotNull(retrievedTruck); + } + + /** Tests the constructor of {@link Car} with valid values. */ + @Test + void testCarConstructor() { + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + assertEquals(2020, car.getYear()); + assertEquals("Toyota", car.getMake()); + assertEquals("Corolla", car.getModel()); + assertEquals(4, car.getNumDoors()); + assertEquals(1, car.getId()); // Assuming the ID is auto-generated in the constructor + } + + /** Tests the constructor of {@link Car} with invalid number of doors (negative value). */ + @Test + void testCarConstructorWithInvalidNumDoors() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + new Car(2020, "Toyota", "Corolla", -4, 1); + }); + assertEquals("Number of doors must be positive.", exception.getMessage()); + } + + /** Tests the constructor of {@link Car} with zero doors. */ + @Test + void testCarConstructorWithZeroDoors() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + new Car(2020, "Toyota", "Corolla", 0, 1); + }); + assertEquals("Number of doors must be positive.", exception.getMessage()); + } + + /** Tests the constructor of {@link Truck} with invalid load capacity (negative value). */ + @Test + void testTruckConstructorWithInvalidLoadCapacity() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + new Truck(2018, "Ford", "F-150", -60, 2); + }); + assertEquals("Load capacity must be positive.", exception.getMessage()); + } + + /** Tests the constructor of {@link Truck} with zero load capacity. */ + @Test + void testTruckConstructorWithZeroLoadCapacity() { + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + new Truck(2018, "Ford", "F-150", 0, 2); + }); + assertEquals("Load capacity must be positive.", exception.getMessage()); + } + + /** Tests setting invalid number of doors in {@link Car} using setter (negative value). */ + @Test + void testSetInvalidNumDoors() { + Car car = new Car(2020, "Toyota", "Corolla", 4, 1); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + car.setNumDoors(-2); + }); + assertEquals("Number of doors must be positive.", exception.getMessage()); + } + + /** Tests setting invalid load capacity in {@link Truck} using setter (negative value). */ + @Test + void testSetInvalidLoadCapacity() { + Truck truck = new Truck(2018, "Ford", "F-150", 60, 2); + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> { + truck.setLoadCapacity(-10); + }); + assertEquals("Load capacity must be positive.", exception.getMessage()); + } +} diff --git a/table-module/README.md b/table-module/README.md index e69892bb7227..a613d687901e 100644 --- a/table-module/README.md +++ b/table-module/README.md @@ -28,6 +28,10 @@ In plain words > The Table Module pattern centralizes and encapsulates database access logic for a specific table, simplifying data retrieval and manipulation while hiding database complexities. +Flowchart + +![Table Module flowchart](./etc/table-module-flowchart.png) + ## Programmatic Example of Table Module Pattern in Java In the user system example, the domain logic for user login and registration needs to be managed. By using the Table Module pattern, we can create an instance of the `UserTableModule` class to encapsulate and handle all business logic associated with the rows in the user table. diff --git a/table-module/etc/table-module-flowchart.png b/table-module/etc/table-module-flowchart.png new file mode 100644 index 000000000000..489d4c8bed59 Binary files /dev/null and b/table-module/etc/table-module-flowchart.png differ diff --git a/table-module/pom.xml b/table-module/pom.xml index f931a8701af4..9e06a4d8c1bf 100644 --- a/table-module/pom.xml +++ b/table-module/pom.xml @@ -34,6 +34,14 @@ 4.0.0 table-module + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + com.h2database h2 diff --git a/table-module/src/main/java/com/iluwatar/tablemodule/App.java b/table-module/src/main/java/com/iluwatar/tablemodule/App.java index 1c528df8a7f5..4d222e852ad9 100644 --- a/table-module/src/main/java/com/iluwatar/tablemodule/App.java +++ b/table-module/src/main/java/com/iluwatar/tablemodule/App.java @@ -29,30 +29,22 @@ import lombok.extern.slf4j.Slf4j; import org.h2.jdbcx.JdbcDataSource; - /** - * Table Module pattern is a domain logic pattern. - * In Table Module a single class encapsulates all the domain logic for all - * records stored in a table or view. It's important to note that there is no - * translation of data between objects and rows, as it happens in Domain Model, - * hence implementation is relatively simple when compared to the Domain - * Model pattern. + * Table Module pattern is a domain logic pattern. In Table Module a single class encapsulates all + * the domain logic for all records stored in a table or view. It's important to note that there is + * no translation of data between objects and rows, as it happens in Domain Model, hence + * implementation is relatively simple when compared to the Domain Model pattern. * - *

      In this example we will use the Table Module pattern to implement register - * and login methods for the records stored in the user table. The main - * method will initialise an instance of {@link UserTableModule} and use it to - * handle the domain logic for the user table.

      + *

      In this example we will use the Table Module pattern to implement register and login methods + * for the records stored in the user table. The main method will initialise an instance of {@link + * UserTableModule} and use it to handle the domain logic for the user table. */ @Slf4j public final class App { private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; - /** - * Private constructor. - */ - private App() { - - } + /** Private constructor. */ + private App() {} /** * Program entry point. @@ -80,18 +72,16 @@ public static void main(final String[] args) throws SQLException { deleteSchema(dataSource); } - private static void deleteSchema(final DataSource dataSource) - throws SQLException { + private static void deleteSchema(final DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(UserTableModule.DELETE_SCHEMA_SQL); } } - private static void createSchema(final DataSource dataSource) - throws SQLException { + private static void createSchema(final DataSource dataSource) throws SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(UserTableModule.CREATE_SCHEMA_SQL); } } diff --git a/table-module/src/main/java/com/iluwatar/tablemodule/User.java b/table-module/src/main/java/com/iluwatar/tablemodule/User.java index 1acdc93dbd7b..4af0f73dcee1 100644 --- a/table-module/src/main/java/com/iluwatar/tablemodule/User.java +++ b/table-module/src/main/java/com/iluwatar/tablemodule/User.java @@ -30,10 +30,7 @@ import lombok.Setter; import lombok.ToString; - -/** - * A user POJO that represents the data that will be read from the data source. - */ +/** A user POJO that represents the data that will be read from the data source. */ @Setter @Getter @ToString @@ -43,5 +40,4 @@ public class User { private int id; private String username; private String password; - } diff --git a/table-module/src/main/java/com/iluwatar/tablemodule/UserTableModule.java b/table-module/src/main/java/com/iluwatar/tablemodule/UserTableModule.java index 92ffb6db6766..ea0b58fc3402 100644 --- a/table-module/src/main/java/com/iluwatar/tablemodule/UserTableModule.java +++ b/table-module/src/main/java/com/iluwatar/tablemodule/UserTableModule.java @@ -29,26 +29,21 @@ import javax.sql.DataSource; import lombok.extern.slf4j.Slf4j; - /** - * This class organizes domain logic with the user table in the - * database. A single instance of this class contains the various - * procedures that will act on the data. + * This class organizes domain logic with the user table in the database. A single instance of this + * class contains the various procedures that will act on the data. */ @Slf4j public class UserTableModule { - /** - * Public element for creating schema. - */ + /** Public element for creating schema. */ public static final String CREATE_SCHEMA_SQL = - "CREATE TABLE IF NOT EXISTS USERS (ID NUMBER, USERNAME VARCHAR(30) " - + "UNIQUE,PASSWORD VARCHAR(30))"; - /** - * Public element for deleting schema. - */ + "CREATE TABLE IF NOT EXISTS USERS (ID NUMBER, USERNAME VARCHAR(30) " + + "UNIQUE,PASSWORD VARCHAR(30))"; + + /** Public element for deleting schema. */ public static final String DELETE_SCHEMA_SQL = "DROP TABLE USERS IF EXISTS"; - private final DataSource dataSource; + private final DataSource dataSource; /** * Public constructor. @@ -59,7 +54,6 @@ public UserTableModule(final DataSource userDataSource) { this.dataSource = userDataSource; } - /** * Login using username and password. * @@ -68,14 +62,11 @@ public UserTableModule(final DataSource userDataSource) { * @return the execution result of the method * @throws SQLException if any error */ - public int login(final String username, final String password) - throws SQLException { + public int login(final String username, final String password) throws SQLException { var sql = "select count(*) from USERS where username=? and password=?"; ResultSet resultSet = null; try (var connection = dataSource.getConnection(); - var preparedStatement = - connection.prepareStatement(sql) - ) { + var preparedStatement = connection.prepareStatement(sql)) { var result = 0; preparedStatement.setString(1, username); preparedStatement.setString(2, password); @@ -106,9 +97,7 @@ public int login(final String username, final String password) public int registerUser(final User user) throws SQLException { var sql = "insert into USERS (username, password) values (?,?)"; try (var connection = dataSource.getConnection(); - var preparedStatement = - connection.prepareStatement(sql) - ) { + var preparedStatement = connection.prepareStatement(sql)) { preparedStatement.setString(1, user.getUsername()); preparedStatement.setString(2, user.getPassword()); var result = preparedStatement.executeUpdate(); diff --git a/table-module/src/test/java/com/iluwatar/tablemodule/AppTest.java b/table-module/src/test/java/com/iluwatar/tablemodule/AppTest.java index d401d36ca8f2..f17cd4722e6a 100644 --- a/table-module/src/test/java/com/iluwatar/tablemodule/AppTest.java +++ b/table-module/src/test/java/com/iluwatar/tablemodule/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.tablemodule; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that the table module example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that the table module example runs without errors. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/table-module/src/test/java/com/iluwatar/tablemodule/UserTableModuleTest.java b/table-module/src/test/java/com/iluwatar/tablemodule/UserTableModuleTest.java index f77067e6f649..f6b31a8589bb 100644 --- a/table-module/src/test/java/com/iluwatar/tablemodule/UserTableModuleTest.java +++ b/table-module/src/test/java/com/iluwatar/tablemodule/UserTableModuleTest.java @@ -24,16 +24,16 @@ */ package com.iluwatar.tablemodule; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.sql.DriverManager; +import java.sql.SQLException; +import javax.sql.DataSource; import org.h2.jdbcx.JdbcDataSource; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import javax.sql.DataSource; -import java.sql.DriverManager; -import java.sql.SQLException; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; class UserTableModuleTest { private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; @@ -47,7 +47,7 @@ private static DataSource createDataSource() { @BeforeEach void setUp() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(UserTableModule.DELETE_SCHEMA_SQL); statement.execute(UserTableModule.CREATE_SCHEMA_SQL); } @@ -56,7 +56,7 @@ void setUp() throws SQLException { @AfterEach void tearDown() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(UserTableModule.DELETE_SCHEMA_SQL); } } @@ -66,8 +66,7 @@ void loginShouldFail() throws SQLException { var dataSource = createDataSource(); var userTableModule = new UserTableModule(dataSource); var user = new User(1, "123456", "123456"); - assertEquals(0, userTableModule.login(user.getUsername(), - user.getPassword())); + assertEquals(0, userTableModule.login(user.getUsername(), user.getPassword())); } @Test @@ -76,8 +75,7 @@ void loginShouldSucceed() throws SQLException { var userTableModule = new UserTableModule(dataSource); var user = new User(1, "123456", "123456"); userTableModule.registerUser(user); - assertEquals(1, userTableModule.login(user.getUsername(), - user.getPassword())); + assertEquals(1, userTableModule.login(user.getUsername(), user.getPassword())); } @Test @@ -96,4 +94,4 @@ void registerShouldSucceed() throws SQLException { var user = new User(1, "123456", "123456"); assertEquals(1, userTableModule.registerUser(user)); } -} \ No newline at end of file +} diff --git a/table-module/src/test/java/com/iluwatar/tablemodule/UserTest.java b/table-module/src/test/java/com/iluwatar/tablemodule/UserTest.java index 51da1847c0ad..ca827f3a97a0 100644 --- a/table-module/src/test/java/com/iluwatar/tablemodule/UserTest.java +++ b/table-module/src/test/java/com/iluwatar/tablemodule/UserTest.java @@ -24,104 +24,89 @@ */ package com.iluwatar.tablemodule; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + class UserTest { @Test void testCanEqual() { - assertFalse((new User(1, "janedoe", "iloveyou")) - .canEqual("Other")); + assertFalse((new User(1, "janedoe", "iloveyou")).canEqual("Other")); } @Test void testCanEqual2() { var user = new User(1, "janedoe", "iloveyou"); - assertTrue(user.canEqual(new User(1, "janedoe", - "iloveyou"))); + assertTrue(user.canEqual(new User(1, "janedoe", "iloveyou"))); } @Test void testEquals1() { var user = new User(1, "janedoe", "iloveyou"); - assertNotEquals(user, new User(123, "abcd", - "qwerty")); + assertNotEquals(user, new User(123, "abcd", "qwerty")); } @Test void testEquals2() { var user = new User(1, "janedoe", "iloveyou"); - assertEquals(user, new User(1, "janedoe", - "iloveyou")); + assertEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals3() { var user = new User(123, "janedoe", "iloveyou"); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals4() { var user = new User(1, null, "iloveyou"); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals5() { var user = new User(1, "iloveyou", "iloveyou"); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals6() { var user = new User(1, "janedoe", "janedoe"); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals7() { var user = new User(1, "janedoe", null); - assertNotEquals(user, new User(1, "janedoe", - "iloveyou")); + assertNotEquals(user, new User(1, "janedoe", "iloveyou")); } @Test void testEquals8() { var user = new User(1, null, "iloveyou"); - assertEquals(user, new User(1, null, - "iloveyou")); + assertEquals(user, new User(1, null, "iloveyou")); } @Test void testEquals9() { var user = new User(1, "janedoe", null); - assertEquals(user, new User(1, "janedoe", - null)); + assertEquals(user, new User(1, "janedoe", null)); } @Test void testHashCode1() { - assertEquals(-1758941372, (new User(1, "janedoe", - "iloveyou")).hashCode()); - + assertEquals(-1758941372, (new User(1, "janedoe", "iloveyou")).hashCode()); } @Test void testHashCode2() { - assertEquals(-1332207447, (new User(1, null, - "iloveyou")).hashCode()); + assertEquals(-1332207447, (new User(1, null, "iloveyou")).hashCode()); } @Test void testHashCode3() { - assertEquals(-426522485, (new User(1, "janedoe", - null)).hashCode()); + assertEquals(-426522485, (new User(1, "janedoe", null)).hashCode()); } @Test @@ -148,9 +133,10 @@ void testSetUsername() { @Test void testToString() { var user = new User(1, "janedoe", "iloveyou"); - assertEquals(String.format("User(id=%s, username=%s, password=%s)", + assertEquals( + String.format( + "User(id=%s, username=%s, password=%s)", user.getId(), user.getUsername(), user.getPassword()), - user.toString()); + user.toString()); } } - diff --git a/template-method/README.md b/template-method/README.md index a05d77ff03be..187b5cd6dd0c 100644 --- a/template-method/README.md +++ b/template-method/README.md @@ -34,6 +34,10 @@ Wikipedia says > In object-oriented programming, the template method is one of the behavioral design patterns identified by Gamma et al. in the book Design Patterns. The template method is a method in a superclass, usually an abstract superclass, and defines the skeleton of an operation in terms of a number of high-level steps. These steps are themselves implemented by additional helper methods in the same class as the template method. +Sequence diagram + +![Template Method Pattern Sequence Diagram](./etc/template-method-sequence-diagram.png) + ## Programmatic Example of Template Method Pattern in Java Our programmatic example is about thieves and stealing. The general steps in stealing an item are the same. First, you pick the target, next you confuse him somehow and finally, you steal the item. However, there are many ways to implement these steps. diff --git a/template-method/etc/template-method-sequence-diagram.png b/template-method/etc/template-method-sequence-diagram.png new file mode 100644 index 000000000000..57cd0809f4ee Binary files /dev/null and b/template-method/etc/template-method-sequence-diagram.png differ diff --git a/template-method/pom.xml b/template-method/pom.xml index dc75fdef0212..401af5c7270f 100644 --- a/template-method/pom.xml +++ b/template-method/pom.xml @@ -34,6 +34,14 @@ template-method + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -41,7 +49,7 @@ org.mockito - mockito-inline + mockito-core test diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java b/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java index 06dff72fa154..d7d14d52bded 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/HalflingThief.java @@ -24,9 +24,7 @@ */ package com.iluwatar.templatemethod; -/** - * Halfling thief uses {@link StealingMethod} to steal. - */ +/** Halfling thief uses {@link StealingMethod} to steal. */ public class HalflingThief { private StealingMethod method; diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java b/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java index 24c8bed104c5..a809fa688d16 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/HitAndRunMethod.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * HitAndRunMethod implementation of {@link StealingMethod}. - */ +/** HitAndRunMethod implementation of {@link StealingMethod}. */ @Slf4j public class HitAndRunMethod extends StealingMethod { diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java b/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java index db8813fa2bb9..f2479b07514d 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/StealingMethod.java @@ -1,50 +1,46 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.templatemethod; - -import lombok.extern.slf4j.Slf4j; - -/** - * StealingMethod defines skeleton for the algorithm. - */ -@Slf4j -public abstract class StealingMethod { - - protected abstract String pickTarget(); - - protected abstract void confuseTarget(String target); - - protected abstract void stealTheItem(String target); - - /** - * Steal. - */ - public final void steal() { - var target = pickTarget(); - LOGGER.info("The target has been chosen as {}.", target); - confuseTarget(target); - stealTheItem(target); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templatemethod; + +import lombok.extern.slf4j.Slf4j; + +/** StealingMethod defines skeleton for the algorithm. */ +@Slf4j +public abstract class StealingMethod { + + protected abstract String pickTarget(); + + protected abstract void confuseTarget(String target); + + protected abstract void stealTheItem(String target); + + /** Steal. */ + public final void steal() { + var target = pickTarget(); + LOGGER.info("The target has been chosen as {}.", target); + confuseTarget(target); + stealTheItem(target); + } +} diff --git a/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java b/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java index 0ca294f6640c..4843fae27056 100644 --- a/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java +++ b/template-method/src/main/java/com/iluwatar/templatemethod/SubtleMethod.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * SubtleMethod implementation of {@link StealingMethod}. - */ +/** SubtleMethod implementation of {@link StealingMethod}. */ @Slf4j public class SubtleMethod extends StealingMethod { diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java index 5e8023e64c5a..e9faed224ad2 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.templatemethod; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java index 2e97da42b8ef..da39b5fdb756 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/HalflingThiefTest.java @@ -26,55 +26,32 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; import org.junit.jupiter.api.Test; -/** - * HalflingThiefTest - * - */ +/** HalflingThiefTest */ class HalflingThiefTest { - /** - * Verify if the thief uses the provided stealing method - */ + /** Verify if the thief uses the provided stealing method */ @Test void testSteal() { final var method = spy(StealingMethod.class); final var thief = new HalflingThief(method); - thief.steal(); verify(method).steal(); - String target = verify(method).pickTarget(); - verify(method).confuseTarget(target); - verify(method).stealTheItem(target); - - verifyNoMoreInteractions(method); } - /** - * Verify if the thief uses the provided stealing method, and the new method after changing it - */ + /** Verify if the thief uses the provided stealing method, and the new method after changing it */ @Test void testChangeMethod() { final var initialMethod = spy(StealingMethod.class); final var thief = new HalflingThief(initialMethod); - thief.steal(); verify(initialMethod).steal(); - String target = verify(initialMethod).pickTarget(); - verify(initialMethod).confuseTarget(target); - verify(initialMethod).stealTheItem(target); final var newMethod = spy(StealingMethod.class); thief.changeMethod(newMethod); - thief.steal(); verify(newMethod).steal(); - String newTarget = verify(newMethod).pickTarget(); - verify(newMethod).confuseTarget(newTarget); - verify(newMethod).stealTheItem(newTarget); - verifyNoMoreInteractions(initialMethod, newMethod); } -} \ No newline at end of file +} diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java index d066d2bedbf3..85666667ca31 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/HitAndRunMethodTest.java @@ -24,23 +24,16 @@ */ package com.iluwatar.templatemethod; -/** - * HitAndRunMethodTest - * - */ +/** HitAndRunMethodTest */ class HitAndRunMethodTest extends StealingMethodTest { - /** - * Create a new test for the {@link HitAndRunMethod} - */ + /** Create a new test for the {@link HitAndRunMethod} */ public HitAndRunMethodTest() { super( new HitAndRunMethod(), "old goblin woman", "The target has been chosen as old goblin woman.", "Approach the old goblin woman from behind.", - "Grab the handbag and run away fast!" - ); + "Grab the handbag and run away fast!"); } - -} \ No newline at end of file +} diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java index 565a6fce41fc..ee01df1cb384 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/StealingMethodTest.java @@ -56,42 +56,36 @@ void tearDown() { appender.stop(); } - /** - * The tested stealing method - */ + /** The tested stealing method */ private final M method; - /** - * The expected target - */ + /** The expected target */ private final String expectedTarget; - /** - * The expected target picking result - */ + /** The expected target picking result */ private final String expectedTargetResult; - /** - * The expected confusion method - */ + /** The expected confusion method */ private final String expectedConfuseMethod; - /** - * The expected stealing method - */ + /** The expected stealing method */ private final String expectedStealMethod; /** * Create a new test for the given stealing method, together with the expected results * - * @param method The tested stealing method - * @param expectedTarget The expected target name - * @param expectedTargetResult The expected target picking result + * @param method The tested stealing method + * @param expectedTarget The expected target name + * @param expectedTargetResult The expected target picking result * @param expectedConfuseMethod The expected confusion method - * @param expectedStealMethod The expected stealing method + * @param expectedStealMethod The expected stealing method */ - public StealingMethodTest(final M method, String expectedTarget, final String expectedTargetResult, - final String expectedConfuseMethod, final String expectedStealMethod) { + public StealingMethodTest( + final M method, + String expectedTarget, + final String expectedTargetResult, + final String expectedConfuseMethod, + final String expectedStealMethod) { this.method = method; this.expectedTarget = expectedTarget; @@ -100,17 +94,13 @@ public StealingMethodTest(final M method, String expectedTarget, final String ex this.expectedStealMethod = expectedStealMethod; } - /** - * Verify if the thief picks the correct target - */ + /** Verify if the thief picks the correct target */ @Test void testPickTarget() { assertEquals(expectedTarget, this.method.pickTarget()); } - /** - * Verify if the target confusing step goes as planned - */ + /** Verify if the target confusing step goes as planned */ @Test void testConfuseTarget() { assertEquals(0, appender.getLogSize()); @@ -120,9 +110,7 @@ void testConfuseTarget() { assertEquals(1, appender.getLogSize()); } - /** - * Verify if the stealing step goes as planned - */ + /** Verify if the stealing step goes as planned */ @Test void testStealTheItem() { assertEquals(0, appender.getLogSize()); @@ -132,9 +120,7 @@ void testStealTheItem() { assertEquals(1, appender.getLogSize()); } - /** - * Verify if the complete steal process goes as planned - */ + /** Verify if the complete steal process goes as planned */ @Test void testSteal() { this.method.steal(); diff --git a/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java b/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java index bcdabe8ffd40..2288a9f8bab7 100644 --- a/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java +++ b/template-method/src/test/java/com/iluwatar/templatemethod/SubtleMethodTest.java @@ -24,23 +24,16 @@ */ package com.iluwatar.templatemethod; -/** - * SubtleMethodTest - * - */ +/** SubtleMethodTest */ class SubtleMethodTest extends StealingMethodTest { - /** - * Create a new test for the {@link SubtleMethod} - */ + /** Create a new test for the {@link SubtleMethod} */ public SubtleMethodTest() { super( new SubtleMethod(), "shop keeper", "The target has been chosen as shop keeper.", "Approach the shop keeper with tears running and hug him!", - "While in close contact grab the shop keeper's wallet." - ); + "While in close contact grab the shop keeper's wallet."); } - -} \ No newline at end of file +} diff --git a/templateview/README.md b/templateview/README.md new file mode 100644 index 000000000000..6708fa2274a7 --- /dev/null +++ b/templateview/README.md @@ -0,0 +1,148 @@ +--- +title: "Template View Pattern in Java: Streamlining Dynamic Webpage Rendering" +shortTitle: Template View +description: "Learn about the Template View design pattern in Java, which simplifies webpage rendering by separating static and dynamic content. Ideal for developers building reusable and maintainable UI components." +category: Behavioral +language: en +tag: + - Abstraction + - Code simplification + - Decoupling + - Extensibility + - Gang of Four + - Inheritance + - Polymorphism + - Reusability +--- + +## Intent of Template View Design Pattern + +Separate the structure and static parts of a webpage (or view) from its dynamic content. Template View ensures a consistent layout while allowing flexibility for different types of views. + +## Detailed Explanation of Template View Pattern with Real-World Examples + +Real-World Example + +> Think of a blog website where each post page follows the same layout with a header, footer, and main content area. While the header and footer remain consistent, the main content differs for each blog post. The Template View pattern encapsulates the shared layout (header and footer) in a base class while delegating the rendering of the main content to subclasses. + +In Plain Words + +> The Template View pattern provides a way to define a consistent layout in a base class while letting subclasses implement the specific, dynamic content for different views. + +Wikipedia Says + +> While not a classic Gang of Four pattern, Template View aligns closely with the Template Method pattern, applied specifically to rendering webpages or views. It defines a skeleton for rendering, delegating dynamic parts to subclasses while keeping the structure consistent. + +Flowchart + +![Template View Pattern Flowchart](./etc/template-view-flowchart.png) + +## Programmatic Example of Template View Pattern in Java + +Our example involves rendering different types of views (`HomePageView` and `ContactPageView`) with a common structure consisting of a header, dynamic content, and a footer. + +### The Abstract Base Class: TemplateView + +The `TemplateView` class defines the skeleton for rendering a view. Subclasses provide implementations for rendering dynamic content. + +```java +@Slf4j +public abstract class TemplateView { + + public final void render() { + printHeader(); + renderDynamicContent(); + printFooter(); + } + + protected void printHeader() { + LOGGER.info("Rendering header..."); + } + + protected abstract void renderDynamicContent(); + + protected void printFooter() { + LOGGER.info("Rendering footer..."); + } +} +``` +### Concrete Class: HomePageView +```java +@Slf4j +public class HomePageView extends TemplateView { + + @Override + protected void renderDynamicContent() { + LOGGER.info("Welcome to the Home Page!"); + } +} +``` +### Concrete Class: ContactPageView +```java +@Slf4j +public class ContactPageView extends TemplateView { + + @Override + protected void renderDynamicContent() { + LOGGER.info("Contact us at: contact@example.com"); + } +} +``` +### Application Class: App +The `App` class demonstrates rendering different views using the Template View pattern. +```java +@Slf4j +public class App { + + public static void main(String[] args) { + TemplateView homePage = new HomePageView(); + LOGGER.info("Rendering HomePage:"); + homePage.render(); + + TemplateView contactPage = new ContactPageView(); + LOGGER.info("\nRendering ContactPage:"); + contactPage.render(); + } +} +``` +## Output of the Program +```lessRendering HomePage: +Rendering header... +Welcome to the Home Page! +Rendering footer... + +Rendering ContactPage: +Rendering header... +Contact us at: contact@example.com +Rendering footer... +``` +## When to Use the Template View Pattern in Java +- When you want to enforce a consistent structure for rendering views while allowing flexibility in dynamic content. +- When you need to separate the static layout (header, footer) from the dynamic parts of a view (main content). +- To enhance code reusability and reduce duplication in rendering logic. + +## Benefits and Trade-offs of Template View Pattern +**Benefits:** +- Code Reusability: Centralizes shared layout logic in the base class. +- Maintainability: Reduces duplication, making updates easier. +- Flexibility: Allows subclasses to customize dynamic content. + +**Trade-offs:** +- Increased Number of Classes: Requires creating separate classes for each type of view. +- Design Overhead: Might be overkill for simple applications with few views. + +## Related Java Design Patterns +- [Template Method](https://java-design-patterns.com/patterns/template-method/): A similar pattern focusing on defining a skeleton algorithm, allowing subclasses to implement specific steps. +- [Strategy Pattern](https://java-design-patterns.com/patterns/strategy/): Offers flexibility in choosing dynamic behaviors at runtime instead of hardcoding them in subclasses. +- [Decorator Pattern](https://java-design-patterns.com/patterns/decorator/): Can complement Template View for dynamically adding responsibilities to views. + +## Real World Applications of Template View Pattern +- Web frameworks like Spring MVC and Django use this concept to render views consistently. +- CMS platforms like WordPress follow this pattern for theme templates, separating layout from content. + +## References and Credits +- [Effective Java](https://amzn.to/4cGk2Jz) +- [Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software](https://amzn.to/49NGldq) +- [Refactoring to Patterns](https://amzn.to/3VOO4F5) +- [Template Method Pattern](https://refactoring.guru/design-patterns/template-method) +- [Basics of Django: Model-View-Template (MVT) Architecture](https://angelogentileiii.medium.com/basics-of-django-model-view-template-mvt-architecture-8585aecffbf6) diff --git a/templateview/etc/template-view-flowchart.png b/templateview/etc/template-view-flowchart.png new file mode 100644 index 000000000000..0887ac8bce4e Binary files /dev/null and b/templateview/etc/template-view-flowchart.png differ diff --git a/templateview/etc/template-view.urm.puml b/templateview/etc/template-view.urm.puml new file mode 100644 index 000000000000..503737b51686 --- /dev/null +++ b/templateview/etc/template-view.urm.puml @@ -0,0 +1,33 @@ +@startuml +package com.iluwater.templateview { + class App { + + App() + + main(args : String[]) {static} + } + + abstract class TemplateView { + - LOGGER : Logger {static} + + TemplateView() + + render() : void {final} + # printHeader() : void + # renderDynamicContent() : void {abstract} + # printFooter() : void + } + + class HomePageView { + - LOGGER : Logger {static} + + HomePageView() + + renderDynamicContent() : void + } + + class ContactPageView { + - LOGGER : Logger {static} + + ContactPageView() + + renderDynamicContent() : void + } +} + +App --> TemplateView +TemplateView <|-- HomePageView +TemplateView <|-- ContactPageView +@enduml diff --git a/templateview/etc/template_view_urm.png b/templateview/etc/template_view_urm.png new file mode 100644 index 000000000000..0c3e54132fd7 Binary files /dev/null and b/templateview/etc/template_view_urm.png differ diff --git a/templateview/etc/templateview.urm.puml b/templateview/etc/templateview.urm.puml new file mode 100644 index 000000000000..b4abc22749ca --- /dev/null +++ b/templateview/etc/templateview.urm.puml @@ -0,0 +1,29 @@ +@startuml +package com.iluwatar.templateview { + class App { + - LOGGER : Logger {static} + + App() + + main(args : String[]) {static} + } + class ContactPageView { + - LOGGER : Logger {static} + + ContactPageView() + # renderDynamicContent() + } + class HomePageView { + - LOGGER : Logger {static} + + HomePageView() + # renderDynamicContent() + } + abstract class TemplateView { + - LOGGER : Logger {static} + + TemplateView() + # printFooter() + # printHeader() + + render() + # renderDynamicContent() {abstract} + } +} +ContactPageView --|> TemplateView +HomePageView --|> TemplateView +@enduml \ No newline at end of file diff --git a/templateview/pom.xml b/templateview/pom.xml new file mode 100644 index 000000000000..6474b8362b2f --- /dev/null +++ b/templateview/pom.xml @@ -0,0 +1,75 @@ + + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + templateview + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.templateview.App + + + + + + + + + diff --git a/templateview/src/main/java/com/iluwatar/templateview/App.java b/templateview/src/main/java/com/iluwatar/templateview/App.java new file mode 100644 index 000000000000..6f99598b44af --- /dev/null +++ b/templateview/src/main/java/com/iluwatar/templateview/App.java @@ -0,0 +1,59 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import lombok.extern.slf4j.Slf4j; + +/** + * Template View defines a consistent layout for rendering views, delegating dynamic content + * rendering to subclasses. + * + *

      In this example, the {@link TemplateView} class provides the skeleton for rendering views with + * a header, dynamic content, and a footer. Subclasses {@link HomePageView} and {@link + * ContactPageView} define the specific dynamic content for their respective views. + * + *

      The {@link App} class demonstrates the usage of the Template View Pattern by rendering + * instances of {@link HomePageView} and {@link ContactPageView}. + */ +@Slf4j +public class App { + + /** + * Program entry point. + * + * @param args command line args + */ + public static void main(String[] args) { + // Create and render the HomePageView + TemplateView homePage = new HomePageView(); + LOGGER.info("Rendering HomePage:"); + homePage.render(); + + // Create and render the ContactPageView + TemplateView contactPage = new ContactPageView(); + LOGGER.info("\nRendering ContactPage:"); + contactPage.render(); + } +} diff --git a/templateview/src/main/java/com/iluwatar/templateview/ContactPageView.java b/templateview/src/main/java/com/iluwatar/templateview/ContactPageView.java new file mode 100644 index 000000000000..f44e0a496951 --- /dev/null +++ b/templateview/src/main/java/com/iluwatar/templateview/ContactPageView.java @@ -0,0 +1,41 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import lombok.extern.slf4j.Slf4j; + +/** + * ContactPageView implements the TemplateView and provides dynamic content specific to the contact + * page. + */ +@Slf4j +public class ContactPageView extends TemplateView { + + /** Renders dynamic content for the contact page. */ + @Override + protected void renderDynamicContent() { + LOGGER.info("Contact us at: contact@example.com"); + } +} diff --git a/templateview/src/main/java/com/iluwatar/templateview/HomePageView.java b/templateview/src/main/java/com/iluwatar/templateview/HomePageView.java new file mode 100644 index 000000000000..b8a3cbe656f9 --- /dev/null +++ b/templateview/src/main/java/com/iluwatar/templateview/HomePageView.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import lombok.extern.slf4j.Slf4j; + +/** + * HomePageView implements the TemplateView and provides dynamic content specific to the homepage. + */ +@Slf4j +public class HomePageView extends TemplateView { + /** Renders dynamic content for the homepage. */ + @Override + protected void renderDynamicContent() { + LOGGER.info("Welcome to the Home Page!"); + } +} diff --git a/templateview/src/main/java/com/iluwatar/templateview/TemplateView.java b/templateview/src/main/java/com/iluwatar/templateview/TemplateView.java new file mode 100644 index 000000000000..58291ffc9f47 --- /dev/null +++ b/templateview/src/main/java/com/iluwatar/templateview/TemplateView.java @@ -0,0 +1,55 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import lombok.extern.slf4j.Slf4j; + +/** + * TemplateView defines the skeleton for rendering views. Concrete subclasses will provide the + * dynamic content for specific views. + */ +@Slf4j +public abstract class TemplateView { + + /** Render the common structure of the view, delegating dynamic content to subclasses. */ + public final void render() { + printHeader(); + renderDynamicContent(); + printFooter(); + } + + /** Prints the common header of the view. */ + protected void printHeader() { + LOGGER.info("Rendering header..."); + } + + /** Subclasses must provide the implementation for rendering dynamic content. */ + protected abstract void renderDynamicContent(); + + /** Prints the common footer of the view. */ + protected void printFooter() { + LOGGER.info("Rendering footer..."); + } +} diff --git a/templateview/src/test/java/com/iluwatar/templateview/AppTest.java b/templateview/src/test/java/com/iluwatar/templateview/AppTest.java new file mode 100644 index 000000000000..785b093f8956 --- /dev/null +++ b/templateview/src/test/java/com/iluwatar/templateview/AppTest.java @@ -0,0 +1,39 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +/** Application test */ +class AppTest { + + @Test + void shouldExecuteWithoutException() { + // Verify that main() method executes without throwing exceptions + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/templateview/src/test/java/com/iluwatar/templateview/ContactPageViewTest.java b/templateview/src/test/java/com/iluwatar/templateview/ContactPageViewTest.java new file mode 100644 index 000000000000..f906df724704 --- /dev/null +++ b/templateview/src/test/java/com/iluwatar/templateview/ContactPageViewTest.java @@ -0,0 +1,44 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; + +class ContactPageViewTest { + + @Test + void testRenderDynamicContent() { + // Create a spy for ContactPageView + ContactPageView contactPage = spy(ContactPageView.class); + + // Render dynamic content for ContactPageView + contactPage.renderDynamicContent(); + + // Verify that the correct message is logged + verify(contactPage).renderDynamicContent(); + } +} diff --git a/templateview/src/test/java/com/iluwatar/templateview/HomePageViewTest.java b/templateview/src/test/java/com/iluwatar/templateview/HomePageViewTest.java new file mode 100644 index 000000000000..1f546c66d8cc --- /dev/null +++ b/templateview/src/test/java/com/iluwatar/templateview/HomePageViewTest.java @@ -0,0 +1,44 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; + +class HomePageViewTest { + + @Test + void testRenderDynamicContent() { + // Create a spy for HomePageView + HomePageView homePage = spy(HomePageView.class); + + // Render dynamic content for HomePageView + homePage.renderDynamicContent(); + + // Verify that the correct message is logged + verify(homePage).renderDynamicContent(); + } +} diff --git a/templateview/src/test/java/com/iluwatar/templateview/TemplateViewTest.java b/templateview/src/test/java/com/iluwatar/templateview/TemplateViewTest.java new file mode 100644 index 000000000000..67335e728283 --- /dev/null +++ b/templateview/src/test/java/com/iluwatar/templateview/TemplateViewTest.java @@ -0,0 +1,60 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.templateview; + +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.Test; + +class TemplateViewTest { + + @Test + void testRenderHomePage() { + // Create a spy for HomePageView + TemplateView homePage = spy(HomePageView.class); + + // Call the render method + homePage.render(); + + // Verify that the steps of rendering are executed in the correct order + verify(homePage).printHeader(); // Header is printed + verify(homePage).renderDynamicContent(); // Dynamic content specific to home page + verify(homePage).printFooter(); // Footer is printed + } + + @Test + void testRenderContactPage() { + // Create a spy for ContactPageView + TemplateView contactPage = spy(ContactPageView.class); + + // Call the render method + contactPage.render(); + + // Verify that the steps of rendering are executed in the correct order + verify(contactPage).printHeader(); // Header is printed + verify(contactPage).renderDynamicContent(); // Dynamic content specific to contact page + verify(contactPage).printFooter(); // Footer is printed + } +} diff --git a/thread-pool-executor/README.md b/thread-pool-executor/README.md new file mode 100644 index 000000000000..fa1d5a2c8062 --- /dev/null +++ b/thread-pool-executor/README.md @@ -0,0 +1,200 @@ +--- +title: "Thread-Pool Executor Pattern in Java: Efficient Concurrent Task Management" +shortTitle: Thread-Pool Executor +description: "Learn the Thread-Pool Executor pattern in Java with practical examples, class +diagrams, and implementation details. Understand how to manage concurrent tasks efficiently, +improving resource utilization and application performance." +category: Concurrency +language: en +tag: + +- Performance +- Resource Management +- Concurrency +- Multithreading +- Scalability + +--- + +## Intent of Thread-Pool Executor Design Pattern + +The Thread-Pool Executor pattern maintains a pool of worker threads to execute tasks concurrently, +optimizing resource usage by reusing existing threads instead of creating new ones for each task. + +## Detailed Explanation of Thread-Pool Executor Pattern with Real-World Examples + +### Real-world example + +> Imagine a busy airport security checkpoint where instead of opening a new lane for each traveler, +> a fixed number of security lanes (threads) are open to process all passengers. Each security +> officer (thread) processes one passenger (task) at a time, and when finished, immediately calls the +> next passenger in line. During peak travel times, passengers wait in a queue, but the system is much +> more efficient than trying to open a new security lane for each individual traveler. The airport can +> handle fluctuating passenger traffic throughout the day with consistent staffing, optimizing both +> resource utilization and passenger throughput. + +### In plain words + +> Thread-Pool Executor keeps a set of reusable threads that process multiple tasks throughout their +> lifecycle, rather than creating a new thread for each task. + +### Wikipedia says + +> A thread pool is a software design pattern for achieving concurrency of execution in a computer +> program. Often also called a replicated workers or worker-crew model, a thread pool maintains +> multiple threads waiting for tasks to be allocated for concurrent execution by the supervising +> program. + +### Class diagram + +![Thread-pool-executor Class diagram](./etc/thread-pool-executor.urm.png) + +## Programmatic Example of Thread-Pool Executor Pattern in Java + +Imagine a hotel front desk. + +The number of employees (thread pool) is limited, but guests (tasks) keep arriving endlessly. + +The Thread-Pool Executor pattern efficiently handles a large number of requests by reusing a small +set of threads. + +```java +@Slf4j +public class HotelFrontDesk { + public static void main(String[] args) throws InterruptedException, ExecutionException { + // Hire 3 front desk employees (threads) + ExecutorService frontDesk = Executors.newFixedThreadPool(3); + + LOGGER.info("Hotel front desk operation started!"); + + // 7 regular guests checking in (Runnable) + for (int i = 1; i <= 7; i++) { + String guestName = "Guest-" + i; + frontDesk.submit(() -> { + String employeeName = Thread.currentThread().getName(); + LOGGER.info("{} is checking in {}...", employeeName, guestName); + try { + Thread.sleep(2000); // Simulate check-in time + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + LOGGER.info("{} has been successfully checked in!", guestName); + }); + } + + // 3 VIP guests checking in (Callable with result) + Callable vipGuest1 = createVipGuest("VIP-Guest-1"); + Callable vipGuest2 = createVipGuest("VIP-Guest-2"); + Callable vipGuest3 = createVipGuest("VIP-Guest-3"); + + Future vipResult1 = frontDesk.submit(vipGuest1); + Future vipResult2 = frontDesk.submit(vipGuest2); + Future vipResult3 = frontDesk.submit(vipGuest3); + + // Shutdown after submitting all tasks + frontDesk.shutdown(); + + if (frontDesk.awaitTermination(1, TimeUnit.HOURS)) { + // Print VIP guests' check-in results + LOGGER.info("VIP Check-in Results:"); + LOGGER.info(vipResult1.get()); + LOGGER.info(vipResult2.get()); + LOGGER.info(vipResult3.get()); + LOGGER.info("All guests have been successfully checked in. Front desk is now closed."); + } else { + LOGGER.info("Check-in timeout. Forcefully shutting down the front desk."); + } + } + + private static Callable createVipGuest(String vipGuestName) { + return () -> { + String employeeName = Thread.currentThread().getName(); + LOGGER.info("{} is checking in VIP guest {}...", employeeName, vipGuestName); + Thread.sleep(1000); // VIPs are faster to check in + return vipGuestName + " has been successfully checked in!"; + }; + } +} +``` + +Here's the console output: + +```markdown +Hotel front desk operation started! +pool-1-thread-3 is checking in Guest-3... +pool-1-thread-2 is checking in Guest-2... +pool-1-thread-1 is checking in Guest-1... +Guest-2 has been successfully checked in! +Guest-1 has been successfully checked in! +Guest-3 has been successfully checked in! +pool-1-thread-2 is checking in Guest-5... +pool-1-thread-3 is checking in Guest-4... +pool-1-thread-1 is checking in Guest-6... +Guest-5 has been successfully checked in! +pool-1-thread-2 is checking in Guest-7... +Guest-4 has been successfully checked in! +pool-1-thread-3 is checking in VIP guest VIP-Guest-1... +Guest-6 has been successfully checked in! +pool-1-thread-1 is checking in VIP guest VIP-Guest-2... +pool-1-thread-3 is checking in VIP guest VIP-Guest-3... +Guest-7 has been successfully checked in! +VIP Check-in Results: +VIP-Guest-1 has been successfully checked in! +VIP-Guest-2 has been successfully checked in! +VIP-Guest-3 has been successfully checked in! +All guests have been successfully checked in. Front desk is now closed. +``` + +**Note:** Since this example demonstrates asynchronous thread execution, **the actual output may vary between runs**. The order of execution and timing can differ due to thread scheduling, system load, and other factors that affect concurrent processing. The core behavior of the thread pool (limiting concurrent tasks to the number of threads and reusing threads) will remain consistent, but the exact sequence of log messages may change with each execution. + +## When to Use the Thread-Pool Executor Pattern in Java + +* When you need to limit the number of threads running simultaneously to avoid resource exhaustion +* For applications that process a large number of short-lived independent tasks +* To improve performance by reducing thread creation/destruction overhead +* When implementing server applications that handle multiple client requests concurrently +* To execute recurring tasks at fixed rates or with fixed delays + +## Thread-Pool Executor Pattern Java Tutorial + +* [Thread-Pool Executor Pattern Tutorial (Baeldung)](https://www.baeldung.com/thread-pool-java-and-guava) + +## Real-World Applications of Thread-Pool Executor Pattern in Java + +* Application servers like Tomcat and Jetty use thread pools to handle HTTP requests +* Database connection pools in JDBC implementations +* Background job processing frameworks like Spring Batch +* Task scheduling systems like Quartz Scheduler +* Java EE's Managed Executor Service for enterprise applications + +## Benefits and Trade-offs of Thread-Pool Executor Pattern + +### Benefits + +* Improves performance by reusing existing threads instead of creating new ones +* Provides better resource management by limiting the number of active threads +* Simplifies thread lifecycle management and cleanup +* Facilitates easy implementation of task prioritization and scheduling +* Enhances application stability by preventing resource exhaustion + +### Trade-offs + +* May lead to thread starvation if improperly configured (too few threads) +* Potential for resource underutilization if improperly sized (too many threads) +* Requires careful shutdown handling to prevent task loss or resource leaks + +## Related Java Design Patterns + +* [Master-Worker Pattern](https://java-design-patterns.com/patterns/master-worker/): Tasks between a + master and multiple workers. +* [Producer-Consumer Pattern](https://java-design-patterns.com/patterns/producer-consumer/): + Separates task production and task consumption, typically using a blocking queue. +* [Object Pool Pattern](https://java-design-patterns.com/patterns/object-pool/): Reuses a set of + objects (e.g., threads) instead of creating/destroying them repeatedly. + +## References and Credits + +* [Java Documentation for ThreadPoolExecutor](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ThreadPoolExecutor.html) +* [Java Concurrency in Practice](https://jcip.net/) by Brian Goetz +* [Effective Java](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/) by Joshua + Bloch diff --git a/thread-pool-executor/etc/thread-pool-executor.urm.png b/thread-pool-executor/etc/thread-pool-executor.urm.png new file mode 100644 index 000000000000..d426343f8171 Binary files /dev/null and b/thread-pool-executor/etc/thread-pool-executor.urm.png differ diff --git a/thread-pool-executor/etc/thread-pool-executor.urm.puml b/thread-pool-executor/etc/thread-pool-executor.urm.puml new file mode 100644 index 000000000000..ca83f40c1b40 --- /dev/null +++ b/thread-pool-executor/etc/thread-pool-executor.urm.puml @@ -0,0 +1,66 @@ +@startuml + +interface Runnable { + +run(): void +} + +interface Callable { + +call(): T +} + +interface ExecutorService { + +submit(task: Runnable): Future + +submit(task: Callable): Future + +shutdown(): void + +awaitTermination(timeout: long, unit: TimeUnit): boolean +} + +class ThreadPoolExecutor { + -corePoolSize: int + -maximumPoolSize: int + -keepAliveTime: long + -workQueue: BlockingQueue + +execute(task: Runnable): void + +submit(task: Callable): Future +} + +class ThreadPoolManager { + -executorService: ExecutorService + +ThreadPoolManager(numThreads: int) + +submitTask(task: Runnable): void + +submitCallable(task: Callable): Future + +shutdown(): void + +awaitTermination(timeout: long, unit: TimeUnit): boolean +} + +class Task { + -id: int + -name: String + -processingTime: long + +Task(id: int, name: String, processingTime: long) + +run(): void + +call(): TaskResult +} + +class TaskResult { + -taskId: int + -taskName: String + -executionTime: long + +TaskResult(taskId: int, taskName: String, executionTime: long) +} + +class App { + +main(args: String[]): void + -executeRunnableTasks(poolManager: ThreadPoolManager): void + -executeCallableTasks(poolManager: ThreadPoolManager): void +} + +ExecutorService <|-- ThreadPoolExecutor : implements +Task ..|> Runnable : implements +Task ..|> Callable : implements +Task --> TaskResult : produces +ThreadPoolManager --> ExecutorService : wraps +App --> ThreadPoolManager : uses +App --> Task : creates + +@enduml \ No newline at end of file diff --git a/thread-pool-executor/pom.xml b/thread-pool-executor/pom.xml new file mode 100644 index 000000000000..f77cd92c67aa --- /dev/null +++ b/thread-pool-executor/pom.xml @@ -0,0 +1,83 @@ + + + + + 4.0.0 + + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + thread-pool-executor + + + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + + + + com.iluwatar.threadpoolexecutor.App + + + + + + + + + diff --git a/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/App.java b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/App.java new file mode 100644 index 000000000000..0c1292b89c3a --- /dev/null +++ b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/App.java @@ -0,0 +1,90 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.threadpoolexecutor; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; + +/** + * The Thread-Pool Executor pattern demonstrates how a pool of worker threads can be used to execute + * tasks concurrently. This pattern is particularly useful in scenarios where you need to execute a + * large number of independent tasks and want to limit the number of threads used. + * + *

      In this example, a hotel front desk with a fixed number of employees processes guest + * check-ins. Each employee is represented by a thread, and each check-in is a task. + * + *

      Key benefits demonstrated: + * + *

        + *
      • Resource management - Limiting the number of concurrent threads + *
      • Efficiency - Reusing threads instead of creating new ones for each task + *
      • Responsiveness - Handling many requests with limited resources + *
      + */ +@Slf4j +public class App { + + /** + * Program main entry point. + * + * @param args program runtime arguments + */ + public static void main(String[] args) throws InterruptedException, ExecutionException { + + FrontDeskService frontDesk = new FrontDeskService(5); + LOGGER.info("Hotel front desk operation started!"); + + LOGGER.info("Processing 30 regular guest check-ins..."); + for (int i = 1; i <= 30; i++) { + frontDesk.submitGuestCheckIn(new GuestCheckInTask("Guest-" + i)); + Thread.sleep(100); + } + + LOGGER.info("Processing 3 VIP guest check-ins..."); + List> vipResults = new ArrayList<>(); + + for (int i = 1; i <= 3; i++) { + Future result = + frontDesk.submitVipGuestCheckIn(new VipGuestCheckInTask("VIP-Guest-" + i)); + vipResults.add(result); + } + + frontDesk.shutdown(); + + if (frontDesk.awaitTermination(1, TimeUnit.HOURS)) { + LOGGER.info("VIP Check-in Results:"); + for (Future result : vipResults) { + LOGGER.info(result.get()); + } + LOGGER.info("All guests have been successfully checked in. Front desk is now closed."); + } else { + LOGGER.warn("Check-in timeout. Forcefully shutting down the front desk."); + } + } +} diff --git a/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/FrontDeskService.java b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/FrontDeskService.java new file mode 100644 index 000000000000..b80236ee5ecf --- /dev/null +++ b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/FrontDeskService.java @@ -0,0 +1,108 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.threadpoolexecutor; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import lombok.extern.slf4j.Slf4j; + +/** + * FrontDeskService represents the hotel's front desk with a fixed number of employees. This class + * demonstrates the Thread-Pool Executor pattern using Java's ExecutorService. + */ +@Slf4j +public class FrontDeskService { + + private final ExecutorService executorService; + private final int numberOfEmployees; + + /** + * Creates a new front desk with the specified number of employees. + * + * @param numberOfEmployees the number of employees (threads) at the front desk + */ + public FrontDeskService(int numberOfEmployees) { + this.numberOfEmployees = numberOfEmployees; + this.executorService = Executors.newFixedThreadPool(numberOfEmployees); + LOGGER.info("Front desk initialized with {} employees.", numberOfEmployees); + } + + /** + * Submits a regular guest check-in task to an available employee. + * + * @param task the check-in task to submit + * @return a Future representing pending completion of the task + */ + public Future submitGuestCheckIn(Runnable task) { + LOGGER.debug("Submitting regular guest check-in task"); + return executorService.submit(task, null); + } + + /** + * Submits a VIP guest check-in task to an available employee. + * + * @param task the VIP check-in task to submit + * @param the type of the task's result + * @return a Future representing pending completion of the task + */ + public Future submitVipGuestCheckIn(Callable task) { + LOGGER.debug("Submitting VIP guest check-in task"); + return executorService.submit(task); + } + + /** + * Closes the front desk after all currently checked-in guests are processed. No new check-ins + * will be accepted. + */ + public void shutdown() { + LOGGER.info("Front desk is closing - no new guests will be accepted."); + executorService.shutdown(); + } + + /** + * Waits for all check-in processes to complete or until timeout. + * + * @param timeout the maximum time to wait + * @param unit the time unit of the timeout argument + * @return true if all tasks completed, false if timeout elapsed + * @throws InterruptedException if interrupted while waiting + */ + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + LOGGER.info("Waiting for all check-ins to complete (max wait: {} {})", timeout, unit); + return executorService.awaitTermination(timeout, unit); + } + + /** + * Gets the number of employees at the front desk. + * + * @return the number of employees + */ + public int getNumberOfEmployees() { + return numberOfEmployees; + } +} diff --git a/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/GuestCheckInTask.java b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/GuestCheckInTask.java new file mode 100644 index 000000000000..d8a33fdfc8d2 --- /dev/null +++ b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/GuestCheckInTask.java @@ -0,0 +1,52 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.threadpoolexecutor; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * GuestCheckInTask represents a regular guest check-in process. Implements Runnable because it + * performs an action without returning a result. + */ +@Slf4j +@AllArgsConstructor +public class GuestCheckInTask implements Runnable { + + private final String guestName; + + @Override + public void run() { + String employeeName = Thread.currentThread().getName(); + LOGGER.info("{} is checking in {}...", employeeName, guestName); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.error("Check-in for {} was interrupted", guestName); + } + LOGGER.info("{} has been successfully checked in!", guestName); + } +} diff --git a/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTask.java b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTask.java new file mode 100644 index 000000000000..3948c114f0d6 --- /dev/null +++ b/thread-pool-executor/src/main/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTask.java @@ -0,0 +1,52 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.threadpoolexecutor; + +import java.util.concurrent.Callable; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** + * VipGuestCheckInTask represents a VIP guest check-in process. Implements Callable because it + * returns a result (check-in confirmation). + */ +@Slf4j +@AllArgsConstructor +public class VipGuestCheckInTask implements Callable { + + private final String vipGuestName; + + @Override + public String call() throws Exception { + String employeeName = Thread.currentThread().getName(); + LOGGER.info("{} is checking in VIP guest {}...", employeeName, vipGuestName); + + Thread.sleep(1000); + + String result = vipGuestName + " has been successfully checked in!"; + LOGGER.info("VIP check-in completed: {}", result); + return result; + } +} diff --git a/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/AppTest.java b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/AppTest.java new file mode 100644 index 000000000000..13e3a5beec3c --- /dev/null +++ b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/AppTest.java @@ -0,0 +1,38 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.iluwatar.threadpoolexecutor; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.Test; + +class AppTest { + + @Test + void appStartsWithoutException() { + assertDoesNotThrow(() -> App.main(new String[] {})); + } +} diff --git a/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/FrontDeskServiceTest.java b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/FrontDeskServiceTest.java new file mode 100644 index 000000000000..8d0396bf0541 --- /dev/null +++ b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/FrontDeskServiceTest.java @@ -0,0 +1,248 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.threadpoolexecutor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +class FrontDeskServiceTest { + + /** + * Tests that the constructor correctly sets the number of employees (threads). This verifies the + * basic initialization of the thread pool. + */ + @Test + void testConstructorSetsCorrectNumberOfEmployees() { + int expectedEmployees = 3; + + FrontDeskService frontDesk = new FrontDeskService(expectedEmployees); + + assertEquals(expectedEmployees, frontDesk.getNumberOfEmployees()); + } + + /** + * Tests that the submitGuestCheckIn method returns a non-null Future object. This verifies the + * basic task submission functionality. + */ + @Test + void testSubmitGuestCheckInReturnsNonNullFuture() { + FrontDeskService frontDesk = new FrontDeskService(1); + + Runnable task = + () -> { + // Task that completes quickly + }; + + Future future = frontDesk.submitGuestCheckIn(task); + + assertNotNull(future); + } + + /** + * Tests that the submitVipGuestCheckIn method returns a non-null Future object. This verifies + * that tasks with return values can be submitted correctly. + */ + @Test + void testSubmitVipGuestCheckInReturnsNonNullFuture() { + FrontDeskService frontDesk = new FrontDeskService(1); + Callable task = () -> "VIP Check-in complete"; + + Future future = frontDesk.submitVipGuestCheckIn(task); + + assertNotNull(future); + } + + /** + * Tests that the shutdown and awaitTermination methods work correctly. This verifies the basic + * shutdown functionality of the thread pool. + */ + @Test + void testShutdownAndAwaitTermination() throws InterruptedException { + FrontDeskService frontDesk = new FrontDeskService(2); + CountDownLatch taskLatch = new CountDownLatch(1); + + Runnable task = taskLatch::countDown; + + frontDesk.submitGuestCheckIn(task); + frontDesk.shutdown(); + boolean terminated = frontDesk.awaitTermination(1, TimeUnit.SECONDS); + + assertTrue(terminated); + assertTrue(taskLatch.await(100, TimeUnit.MILLISECONDS)); + } + + /** + * Tests the thread pool's behavior under load with multiple tasks. This verifies that the thread + * pool limits concurrent execution to the number of threads, all submitted tasks are eventually + * completed, and threads are reused for multiple tasks. + */ + @Test + void testMultipleTasksUnderLoad() throws InterruptedException { + FrontDeskService frontDesk = new FrontDeskService(2); + int taskCount = 10; + CountDownLatch tasksCompletedLatch = new CountDownLatch(taskCount); + AtomicInteger concurrentTasks = new AtomicInteger(0); + AtomicInteger maxConcurrentTasks = new AtomicInteger(0); + + for (int i = 0; i < taskCount; i++) { + frontDesk.submitGuestCheckIn( + () -> { + try { + int current = concurrentTasks.incrementAndGet(); + maxConcurrentTasks.updateAndGet(max -> Math.max(max, current)); + + Thread.sleep(100); + + concurrentTasks.decrementAndGet(); + tasksCompletedLatch.countDown(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } + + boolean allTasksCompleted = tasksCompletedLatch.await(2, TimeUnit.SECONDS); + + frontDesk.shutdown(); + frontDesk.awaitTermination(1, TimeUnit.SECONDS); + + assertTrue(allTasksCompleted); + assertEquals(2, maxConcurrentTasks.get()); + assertEquals(0, concurrentTasks.get()); + } + + /** + * Tests proper shutdown behavior under load. This verifies that after shutdown no new tasks are + * accepted, all previously submitted tasks are completed, and the executor terminates properly + * after all tasks complete. + */ + @Test + void testProperShutdownUnderLoad() throws InterruptedException { + FrontDeskService frontDesk = new FrontDeskService(2); + int taskCount = 5; + CountDownLatch startedTasksLatch = new CountDownLatch(2); + CountDownLatch tasksCompletionLatch = new CountDownLatch(taskCount); + + for (int i = 0; i < taskCount; i++) { + frontDesk.submitGuestCheckIn( + () -> { + try { + startedTasksLatch.countDown(); + Thread.sleep(100); + tasksCompletionLatch.countDown(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + } + + assertTrue(startedTasksLatch.await(1, TimeUnit.SECONDS)); + + frontDesk.shutdown(); + + assertThrows( + RejectedExecutionException.class, + () -> { + frontDesk.submitGuestCheckIn(() -> {}); + }); + + boolean allTasksCompleted = tasksCompletionLatch.await(2, TimeUnit.SECONDS); + + boolean terminated = frontDesk.awaitTermination(1, TimeUnit.SECONDS); + + assertTrue(allTasksCompleted); + assertTrue(terminated); + } + + /** + * Tests concurrent execution of different task types (regular and VIP). This verifies that both + * Runnable and Callable tasks can be processed concurrently, all tasks complete successfully, and + * Callable tasks return their results correctly. + */ + @Test + void testConcurrentRegularAndVipTasks() throws Exception { + FrontDeskService frontDesk = new FrontDeskService(3); + int regularTaskCount = 4; + int vipTaskCount = 3; + CountDownLatch allTasksLatch = new CountDownLatch(regularTaskCount + vipTaskCount); + + List> regularResults = new ArrayList<>(); + for (int i = 0; i < regularTaskCount; i++) { + Future result = + frontDesk.submitGuestCheckIn( + () -> { + try { + Thread.sleep(50); + allTasksLatch.countDown(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + }); + regularResults.add(result); + } + + List> vipResults = new ArrayList<>(); + for (int i = 0; i < vipTaskCount; i++) { + final int guestNum = i; + Future result = + frontDesk.submitVipGuestCheckIn( + () -> { + Thread.sleep(25); + allTasksLatch.countDown(); + return "VIP-" + guestNum + " checked in"; + }); + vipResults.add(result); + } + + boolean allCompleted = allTasksLatch.await(2, TimeUnit.SECONDS); + + frontDesk.shutdown(); + frontDesk.awaitTermination(1, TimeUnit.SECONDS); + + assertTrue(allCompleted); + + for (Future result : regularResults) { + assertTrue(result.isDone()); + } + + for (int i = 0; i < vipTaskCount; i++) { + Future result = vipResults.get(i); + assertTrue(result.isDone()); + assertEquals("VIP-" + i + " checked in", result.get()); + } + } +} diff --git a/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/GuestCheckInTaskTest.java b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/GuestCheckInTaskTest.java new file mode 100644 index 000000000000..27bb75efd2db --- /dev/null +++ b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/GuestCheckInTaskTest.java @@ -0,0 +1,55 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.threadpoolexecutor; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.Test; + +class GuestCheckInTaskTest { + + /** + * Tests that the task executes in the current thread when called directly. This verifies that the + * thread name inside the task matches the calling thread. + */ + @Test + void testThreadNameInTask() { + String guestName = "TestGuest"; + AtomicReference capturedThreadName = new AtomicReference<>(); + + GuestCheckInTask task = + new GuestCheckInTask(guestName) { + @Override + public void run() { + capturedThreadName.set(Thread.currentThread().getName()); + } + }; + + task.run(); + + assertEquals(Thread.currentThread().getName(), capturedThreadName.get()); + } +} diff --git a/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTaskTest.java b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTaskTest.java new file mode 100644 index 000000000000..d76d90625c95 --- /dev/null +++ b/thread-pool-executor/src/test/java/com/iluwatar/threadpoolexecutor/VipGuestCheckInTaskTest.java @@ -0,0 +1,48 @@ +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.threadpoolexecutor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +class VipGuestCheckInTaskTest { + + /** + * Tests that the call method returns the expected result string. This verifies that the VIP + * check-in task correctly formats its result message. + */ + @Test + void testCallReturnsExpectedResult() throws Exception { + String vipGuestName = "TestVipGuest"; + VipGuestCheckInTask task = new VipGuestCheckInTask(vipGuestName); + + String result = task.call(); + + assertNotNull(result); + assertEquals("TestVipGuest has been successfully checked in!", result); + } +} diff --git a/throttling/README.md b/throttling/README.md index d35c346deea0..ab5f92fbeeeb 100644 --- a/throttling/README.md +++ b/throttling/README.md @@ -34,6 +34,10 @@ In plain words > Control the consumption of resources used by an instance of an application, an individual tenant, or an entire service. This can allow the system to continue to function and meet service level agreements, even when an increase in demand places an extreme load on resources. +Flowchart + +![Throttling Pattern Flowchart](./etc/throttling-flowchart.png) + ## Programmatic Example of Throttling Pattern in Java In this Java example, we demonstrate throttling. A young human and an old dwarf walk into a bar. They start ordering beers from the bartender. The bartender immediately sees that the young human shouldn't consume too many drinks too fast and refuses to serve if enough time has not passed. For the old dwarf, the serving rate can be higher. diff --git a/throttling/etc/throttling-flowchart.png b/throttling/etc/throttling-flowchart.png new file mode 100644 index 000000000000..0c7d6cc329b0 Binary files /dev/null and b/throttling/etc/throttling-flowchart.png differ diff --git a/throttling/pom.xml b/throttling/pom.xml index e6bda0867414..91551e01d6e5 100644 --- a/throttling/pom.xml +++ b/throttling/pom.xml @@ -34,6 +34,14 @@ 4.0.0 throttling + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/throttling/src/main/java/com/iluwatar/throttling/App.java b/throttling/src/main/java/com/iluwatar/throttling/App.java index f66d75fb53bc..7b25b0a6b1a9 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/App.java +++ b/throttling/src/main/java/com/iluwatar/throttling/App.java @@ -34,12 +34,11 @@ * Throttling pattern is a design pattern to throttle or limit the use of resources or even a * complete service by users or a particular tenant. This can allow systems to continue to function * and meet service level agreements, even when an increase in demand places load on resources. - *

      - * In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a time - * based throttling, i.e. only a certain number of calls are allowed per second. - *

      - * ({@link BarCustomer}) is the service tenant class having a name and the number of calls allowed. - * ({@link Bartender}) is the service which is consumed by the tenants and is throttled. + * + *

      In this example there is a {@link Bartender} serving beer to {@link BarCustomer}s. This is a + * time based throttling, i.e. only a certain number of calls are allowed per second. ({@link + * BarCustomer}) is the service tenant class having a name and the number of calls allowed. ({@link + * Bartender}) is the service which is consumed by the tenants and is throttled. */ @Slf4j public class App { @@ -69,20 +68,20 @@ public static void main(String[] args) { } } - /** - * Make calls to the bartender. - */ + /** Make calls to the bartender. */ private static void makeServiceCalls(BarCustomer barCustomer, CallsCount callsCount) { var timer = new ThrottleTimerImpl(1000, callsCount); var service = new Bartender(timer, callsCount); // Sleep is introduced to keep the output in check and easy to view and analyze the results. - IntStream.range(0, 50).forEach(i -> { - service.orderDrink(barCustomer); - try { - Thread.sleep(100); - } catch (InterruptedException e) { - LOGGER.error("Thread interrupted: {}", e.getMessage()); - } - }); + IntStream.range(0, 50) + .forEach( + i -> { + service.orderDrink(barCustomer); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted: {}", e.getMessage()); + } + }); } } diff --git a/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java b/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java index 5d9e5d2f3f43..da0dc8eda53c 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java +++ b/throttling/src/main/java/com/iluwatar/throttling/BarCustomer.java @@ -27,9 +27,7 @@ import java.security.InvalidParameterException; import lombok.Getter; -/** - * BarCustomer is a tenant with a name and a number of allowed calls per second. - */ +/** BarCustomer is a tenant with a name and a number of allowed calls per second. */ @Getter public class BarCustomer { diff --git a/throttling/src/main/java/com/iluwatar/throttling/Bartender.java b/throttling/src/main/java/com/iluwatar/throttling/Bartender.java index 7653fcec07d0..a89a80c678cf 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/Bartender.java +++ b/throttling/src/main/java/com/iluwatar/throttling/Bartender.java @@ -30,8 +30,8 @@ import org.slf4j.LoggerFactory; /** - * Bartender is a service which accepts a BarCustomer (tenant) and throttles - * the resource based on the time given to the tenant. + * Bartender is a service which accepts a BarCustomer (tenant) and throttles the resource based on + * the time given to the tenant. */ class Bartender { @@ -45,6 +45,7 @@ public Bartender(Throttler timer, CallsCount callsCount) { /** * Orders a drink from the bartender. + * * @return customer id which is randomly generated */ public int orderDrink(BarCustomer barCustomer) { diff --git a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java index 4776f0e9ba2a..2545a9943087 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java +++ b/throttling/src/main/java/com/iluwatar/throttling/CallsCount.java @@ -29,10 +29,7 @@ import java.util.concurrent.atomic.AtomicLong; import lombok.extern.slf4j.Slf4j; -/** - * A class to keep track of the counter of different Tenants. - * - */ +/** A class to keep track of the counter of different Tenants. */ @Slf4j public final class CallsCount { private final Map tenantCallsCount = new ConcurrentHashMap<>(); @@ -65,9 +62,7 @@ public long getCount(String tenantName) { return tenantCallsCount.get(tenantName).get(); } - /** - * Resets the count of all the tenants in the map. - */ + /** Resets the count of all the tenants in the map. */ public void reset() { tenantCallsCount.replaceAll((k, v) -> new AtomicLong(0)); LOGGER.info("reset counters"); diff --git a/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java b/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java index 32facaaf91c5..5eff99486000 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java +++ b/throttling/src/main/java/com/iluwatar/throttling/timer/ThrottleTimerImpl.java @@ -28,10 +28,7 @@ import java.util.Timer; import java.util.TimerTask; -/** - * Implementation of throttler interface. This class resets the counter every second. - * - */ +/** Implementation of throttler interface. This class resets the counter every second. */ public class ThrottleTimerImpl implements Throttler { private final int throttlePeriod; @@ -42,17 +39,18 @@ public ThrottleTimerImpl(int throttlePeriod, CallsCount callsCount) { this.callsCount = callsCount; } - /** - * A timer is initiated with this method. The timer runs every second and resets the - * counter. - */ + /** A timer is initiated with this method. The timer runs every second and resets the counter. */ @Override public void start() { - new Timer(true).schedule(new TimerTask() { - @Override - public void run() { - callsCount.reset(); - } - }, 0, throttlePeriod); + new Timer(true) + .schedule( + new TimerTask() { + @Override + public void run() { + callsCount.reset(); + } + }, + 0, + throttlePeriod); } } diff --git a/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java b/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java index 5f833619d66e..2600155bed2c 100644 --- a/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java +++ b/throttling/src/main/java/com/iluwatar/throttling/timer/Throttler.java @@ -24,10 +24,7 @@ */ package com.iluwatar.throttling.timer; -/** - * An interface for defining the structure of different types of throttling ways. - * - */ +/** An interface for defining the structure of different types of throttling ways. */ public interface Throttler { void start(); diff --git a/throttling/src/test/java/com/iluwatar/throttling/AppTest.java b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java index 73663ea46c51..3cff5c201481 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/AppTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.throttling; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java b/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java index b1107ec1a2eb..a2eb12ba0ea4 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/BarCustomerTest.java @@ -24,18 +24,17 @@ */ package com.iluwatar.throttling; -import org.junit.jupiter.api.Test; -import java.security.InvalidParameterException; - import static org.junit.jupiter.api.Assertions.assertThrows; -/** - * TenantTest to test the creation of Tenant with valid parameters. - */ +import java.security.InvalidParameterException; +import org.junit.jupiter.api.Test; + +/** TenantTest to test the creation of Tenant with valid parameters. */ class BarCustomerTest { @Test void constructorTest() { - assertThrows(InvalidParameterException.class, () -> new BarCustomer("sirBrave", -1, new CallsCount())); + assertThrows( + InvalidParameterException.class, () -> new BarCustomer("sirBrave", -1, new CallsCount())); } } diff --git a/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java b/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java index 9d241eb6de1e..0fcb034a1563 100644 --- a/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java +++ b/throttling/src/test/java/com/iluwatar/throttling/BartenderTest.java @@ -30,9 +30,7 @@ import java.util.stream.IntStream; import org.junit.jupiter.api.Test; -/** - * B2BServiceTest class to test the B2BService - */ +/** B2BServiceTest class to test the B2BService */ class BartenderTest { private final CallsCount callsCount = new CallsCount(); @@ -40,7 +38,8 @@ class BartenderTest { @Test void dummyCustomerApiTest() { var tenant = new BarCustomer("pirate", 2, callsCount); - // In order to assure that throttling limits will not be reset, we use an empty throttling implementation + // In order to assure that throttling limits will not be reset, we use an empty throttling + // implementation var timer = (Throttler) () -> {}; var service = new Bartender(timer, callsCount); diff --git a/tolerant-reader/README.md b/tolerant-reader/README.md index 720764214dd4..a6c21a64a44a 100644 --- a/tolerant-reader/README.md +++ b/tolerant-reader/README.md @@ -33,6 +33,10 @@ In plain words > Be conservative in what you do, be liberal in what you accept from others. +Sequence diagram + +![Tolerant Reader sequence diagrams](./etc/tolerant-reader-sequence-diagram.png) + ## Programmatic Example of Tolerant Reader Pattern in Java We are persisting `RainbowFish` objects to file. Later on they need to be restored. What makes it problematic is that `RainbowFish` data structure is versioned and evolves over time. New version of `RainbowFish` needs to be able to restore old versions as well. diff --git a/tolerant-reader/etc/tolerant-reader-sequence-diagram.png b/tolerant-reader/etc/tolerant-reader-sequence-diagram.png new file mode 100644 index 000000000000..26e68ccffcaf Binary files /dev/null and b/tolerant-reader/etc/tolerant-reader-sequence-diagram.png differ diff --git a/tolerant-reader/pom.xml b/tolerant-reader/pom.xml index c6716bb845cd..0053bf282249 100644 --- a/tolerant-reader/pom.xml +++ b/tolerant-reader/pom.xml @@ -34,6 +34,14 @@ tolerant-reader + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java index 16c636eea430..c7ced392c662 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/App.java @@ -43,31 +43,44 @@ @Slf4j public class App { - /** - * Program entry point. - */ + /** Program entry point. */ public static void main(String[] args) throws IOException, ClassNotFoundException { // Write V1 var fishV1 = new RainbowFish("Zed", 10, 11, 12); - LOGGER.info("fishV1 name={} age={} length={} weight={}", fishV1.getName(), - fishV1.getAge(), fishV1.getLengthMeters(), fishV1.getWeightTons()); + LOGGER.info( + "fishV1 name={} age={} length={} weight={}", + fishV1.getName(), + fishV1.getAge(), + fishV1.getLengthMeters(), + fishV1.getWeightTons()); RainbowFishSerializer.writeV1(fishV1, "fish1.out"); // Read V1 var deserializedRainbowFishV1 = RainbowFishSerializer.readV1("fish1.out"); - LOGGER.info("deserializedFishV1 name={} age={} length={} weight={}", - deserializedRainbowFishV1.getName(), deserializedRainbowFishV1.getAge(), - deserializedRainbowFishV1.getLengthMeters(), deserializedRainbowFishV1.getWeightTons()); + LOGGER.info( + "deserializedFishV1 name={} age={} length={} weight={}", + deserializedRainbowFishV1.getName(), + deserializedRainbowFishV1.getAge(), + deserializedRainbowFishV1.getLengthMeters(), + deserializedRainbowFishV1.getWeightTons()); // Write V2 var fishV2 = new RainbowFishV2("Scar", 5, 12, 15, true, true, true); LOGGER.info( "fishV2 name={} age={} length={} weight={} sleeping={} hungry={} angry={}", - fishV2.getName(), fishV2.getAge(), fishV2.getLengthMeters(), fishV2.getWeightTons(), - fishV2.isHungry(), fishV2.isAngry(), fishV2.isSleeping()); + fishV2.getName(), + fishV2.getAge(), + fishV2.getLengthMeters(), + fishV2.getWeightTons(), + fishV2.isHungry(), + fishV2.isAngry(), + fishV2.isSleeping()); RainbowFishSerializer.writeV2(fishV2, "fish2.out"); // Read V2 with V1 method var deserializedFishV2 = RainbowFishSerializer.readV1("fish2.out"); - LOGGER.info("deserializedFishV2 name={} age={} length={} weight={}", - deserializedFishV2.getName(), deserializedFishV2.getAge(), - deserializedFishV2.getLengthMeters(), deserializedFishV2.getWeightTons()); + LOGGER.info( + "deserializedFishV2 name={} age={} length={} weight={}", + deserializedFishV2.getName(), + deserializedFishV2.getAge(), + deserializedFishV2.getLengthMeters(), + deserializedFishV2.getWeightTons()); } } diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java index 16f8c2d1db3c..d6eae6a443fe 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFish.java @@ -29,19 +29,15 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * RainbowFish is the initial schema. - */ +/** RainbowFish is the initial schema. */ @Getter @RequiredArgsConstructor public class RainbowFish implements Serializable { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private final String name; private final int age; private final int lengthMeters; private final int weightTons; - } diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java index 3a71cf466eab..d96697df43e7 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishSerializer.java @@ -44,51 +44,56 @@ public final class RainbowFishSerializer { public static final String LENGTH_METERS = "lengthMeters"; public static final String WEIGHT_TONS = "weightTons"; - /** - * Write V1 RainbowFish to file. - */ + /** Write V1 RainbowFish to file. */ public static void writeV1(RainbowFish rainbowFish, String filename) throws IOException { - var map = Map.of( - "name", rainbowFish.getName(), - "age", String.format("%d", rainbowFish.getAge()), - LENGTH_METERS, String.format("%d", rainbowFish.getLengthMeters()), - WEIGHT_TONS, String.format("%d", rainbowFish.getWeightTons()) - ); + var map = + Map.of( + "name", + rainbowFish.getName(), + "age", + String.format("%d", rainbowFish.getAge()), + LENGTH_METERS, + String.format("%d", rainbowFish.getLengthMeters()), + WEIGHT_TONS, + String.format("%d", rainbowFish.getWeightTons())); try (var fileOut = new FileOutputStream(filename); - var objOut = new ObjectOutputStream(fileOut)) { + var objOut = new ObjectOutputStream(fileOut)) { objOut.writeObject(map); } } - /** - * Write V2 RainbowFish to file. - */ + /** Write V2 RainbowFish to file. */ public static void writeV2(RainbowFishV2 rainbowFish, String filename) throws IOException { - var map = Map.of( - "name", rainbowFish.getName(), - "age", String.format("%d", rainbowFish.getAge()), - LENGTH_METERS, String.format("%d", rainbowFish.getLengthMeters()), - WEIGHT_TONS, String.format("%d", rainbowFish.getWeightTons()), - "angry", Boolean.toString(rainbowFish.isAngry()), - "hungry", Boolean.toString(rainbowFish.isHungry()), - "sleeping", Boolean.toString(rainbowFish.isSleeping()) - ); + var map = + Map.of( + "name", + rainbowFish.getName(), + "age", + String.format("%d", rainbowFish.getAge()), + LENGTH_METERS, + String.format("%d", rainbowFish.getLengthMeters()), + WEIGHT_TONS, + String.format("%d", rainbowFish.getWeightTons()), + "angry", + Boolean.toString(rainbowFish.isAngry()), + "hungry", + Boolean.toString(rainbowFish.isHungry()), + "sleeping", + Boolean.toString(rainbowFish.isSleeping())); try (var fileOut = new FileOutputStream(filename); - var objOut = new ObjectOutputStream(fileOut)) { + var objOut = new ObjectOutputStream(fileOut)) { objOut.writeObject(map); } } - /** - * Read V1 RainbowFish from file. - */ + /** Read V1 RainbowFish from file. */ public static RainbowFish readV1(String filename) throws IOException, ClassNotFoundException { Map map; try (var fileIn = new FileInputStream(filename); - var objIn = new ObjectInputStream(fileIn)) { + var objIn = new ObjectInputStream(fileIn)) { map = (Map) objIn.readObject(); } @@ -96,7 +101,6 @@ public static RainbowFish readV1(String filename) throws IOException, ClassNotFo map.get("name"), Integer.parseInt(map.get("age")), Integer.parseInt(map.get(LENGTH_METERS)), - Integer.parseInt(map.get(WEIGHT_TONS)) - ); + Integer.parseInt(map.get(WEIGHT_TONS))); } } diff --git a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java index c039f332f07f..78c49c16a369 100644 --- a/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java +++ b/tolerant-reader/src/main/java/com/iluwatar/tolerantreader/RainbowFishV2.java @@ -27,14 +27,11 @@ import java.io.Serial; import lombok.Getter; -/** - * RainbowFishV2 is the evolved schema. - */ +/** RainbowFishV2 is the evolved schema. */ @Getter public class RainbowFishV2 extends RainbowFish { - @Serial - private static final long serialVersionUID = 1L; + @Serial private static final long serialVersionUID = 1L; private boolean sleeping; private boolean hungry; @@ -44,15 +41,18 @@ public RainbowFishV2(String name, int age, int lengthMeters, int weightTons) { super(name, age, lengthMeters, weightTons); } - /** - * Constructor. - */ - public RainbowFishV2(String name, int age, int lengthMeters, int weightTons, boolean sleeping, - boolean hungry, boolean angry) { + /** Constructor. */ + public RainbowFishV2( + String name, + int age, + int lengthMeters, + int weightTons, + boolean sleeping, + boolean hungry, + boolean angry) { this(name, age, lengthMeters, weightTons); this.sleeping = sleeping; this.hungry = hungry; this.angry = angry; } - } diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java index 50adcdeda3aa..c9c2533f43fe 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/AppTest.java @@ -31,14 +31,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Application test - */ +/** Application test */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } @BeforeEach diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java index b23816a621f5..8b1ebda936a8 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishSerializerTest.java @@ -34,37 +34,24 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -/** - * RainbowFishSerializerTest - * - */ - +/** RainbowFishSerializerTest */ class RainbowFishSerializerTest { - /** - * Create a temporary folder, used to generate files in during this test - */ - @TempDir - static Path testFolder; + /** Create a temporary folder, used to generate files in during this test */ + @TempDir static Path testFolder; @BeforeEach void beforeEach() { assertTrue(Files.isDirectory(testFolder)); } - /** - * Rainbow fish version 1 used during the tests - */ + /** Rainbow fish version 1 used during the tests */ private static final RainbowFish V1 = new RainbowFish("version1", 1, 2, 3); - /** - * Rainbow fish version 2 used during the tests - */ + /** Rainbow fish version 2 used during the tests */ private static final RainbowFishV2 V2 = new RainbowFishV2("version2", 4, 5, 6, true, false, true); - /** - * Verify if a fish, written as version 1 can be read back as version 1 - */ + /** Verify if a fish, written as version 1 can be read back as version 1 */ @Test void testWriteV1ReadV1() throws Exception { final var outputPath = Files.createFile(testFolder.resolve("outputFile")); @@ -76,12 +63,9 @@ void testWriteV1ReadV1() throws Exception { assertEquals(V1.getAge(), fish.getAge()); assertEquals(V1.getLengthMeters(), fish.getLengthMeters()); assertEquals(V1.getWeightTons(), fish.getWeightTons()); - } - /** - * Verify if a fish, written as version 2 can be read back as version 1 - */ + /** Verify if a fish, written as version 2 can be read back as version 1 */ @Test void testWriteV2ReadV1() throws Exception { final var outputPath = Files.createFile(testFolder.resolve("outputFile2")); @@ -94,5 +78,4 @@ void testWriteV2ReadV1() throws Exception { assertEquals(V2.getLengthMeters(), fish.getLengthMeters()); assertEquals(V2.getWeightTons(), fish.getWeightTons()); } - } diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java index 437026b9ba2f..bb250edc56cd 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishTest.java @@ -28,15 +28,10 @@ import org.junit.jupiter.api.Test; -/** - * RainbowFishTest - * - */ +/** RainbowFishTest */ class RainbowFishTest { - /** - * Verify if the getters of a {@link RainbowFish} return the expected values - */ + /** Verify if the getters of a {@link RainbowFish} return the expected values */ @Test void testValues() { final var fish = new RainbowFish("name", 1, 2, 3); @@ -45,5 +40,4 @@ void testValues() { assertEquals(2, fish.getLengthMeters()); assertEquals(3, fish.getWeightTons()); } - -} \ No newline at end of file +} diff --git a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java index 79a42df8c92f..1c478f2a31ab 100644 --- a/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java +++ b/tolerant-reader/src/test/java/com/iluwatar/tolerantreader/RainbowFishV2Test.java @@ -30,15 +30,10 @@ import org.junit.jupiter.api.Test; -/** - * RainbowFishV2Test - * - */ +/** RainbowFishV2Test */ class RainbowFishV2Test { - /** - * Verify if the getters of a {@link RainbowFish} return the expected values - */ + /** Verify if the getters of a {@link RainbowFish} return the expected values */ @Test void testValues() { final var fish = new RainbowFishV2("name", 1, 2, 3, false, true, false); @@ -50,5 +45,4 @@ void testValues() { assertTrue(fish.isHungry()); assertFalse(fish.isAngry()); } - -} \ No newline at end of file +} diff --git a/trampoline/README.md b/trampoline/README.md index cb4162bafe6d..706b58d24eb1 100644 --- a/trampoline/README.md +++ b/trampoline/README.md @@ -36,6 +36,10 @@ Wikipedia says > In Java, trampoline refers to using reflection to avoid using inner classes, for example in event listeners. The time overhead of a reflection call is traded for the space overhead of an inner class. Trampolines in Java usually involve the creation of a GenericListener to pass events to an outer class. +Flowchart + +![Trampoline Pattern Flowchart](./etc/trampoline-flowchart.png) + ## Programmatic Example of Trampoline Pattern in Java Here's the `Trampoline` implementation in Java. diff --git a/trampoline/etc/trampoline-flowchart.png b/trampoline/etc/trampoline-flowchart.png new file mode 100644 index 000000000000..99387567ba16 Binary files /dev/null and b/trampoline/etc/trampoline-flowchart.png differ diff --git a/trampoline/pom.xml b/trampoline/pom.xml index 61ddcc0401fb..6f58141557c0 100644 --- a/trampoline/pom.xml +++ b/trampoline/pom.xml @@ -34,6 +34,14 @@ trampoline + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java b/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java index 2dd0bc171ff6..e73f54f2e697 100644 --- a/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java +++ b/trampoline/src/main/java/com/iluwatar/trampoline/Trampoline.java @@ -29,19 +29,18 @@ /** * Trampoline pattern allows to define recursive algorithms by iterative loop. * - *

      When get is called on the returned Trampoline, internally it will iterate calling ‘jump’ - * on the returned Trampoline as long as the concrete instance returned is {@link - * #more(Trampoline)}, stopping once the returned instance is {@link #done(Object)}. + *

      When get is called on the returned Trampoline, internally it will iterate calling ‘jump’ on + * the returned Trampoline as long as the concrete instance returned is {@link #more(Trampoline)}, + * stopping once the returned instance is {@link #done(Object)}. * - *

      Essential we convert looping via recursion into iteration, - * the key enabling mechanism is the fact that {@link #more(Trampoline)} is a lazy operation. + *

      Essential we convert looping via recursion into iteration, the key enabling mechanism is the + * fact that {@link #more(Trampoline)} is a lazy operation. * - * @param is type for returning result. + * @param is type for returning result. */ public interface Trampoline { T get(); - /** * Jump to next stage. * @@ -51,7 +50,6 @@ default Trampoline jump() { return this; } - default T result() { return get(); } @@ -75,7 +73,6 @@ static Trampoline done(final T result) { return () -> result; } - /** * Create a Trampoline that has more work to do. * diff --git a/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java b/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java index 37c83c998cfb..a6b6a60e8a71 100644 --- a/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java +++ b/trampoline/src/main/java/com/iluwatar/trampoline/TrampolineApp.java @@ -29,26 +29,20 @@ /** * Trampoline pattern allows to define recursive algorithms by iterative loop. * - *

      It is possible to implement algorithms recursively in Java without blowing the stack - * and to interleave the execution of functions without hard coding them together or even using - * threads. + *

      It is possible to implement algorithms recursively in Java without blowing the stack and to + * interleave the execution of functions without hard coding them together or even using threads. */ @Slf4j public class TrampolineApp { - /** - * Main program for showing pattern. It does loop with factorial function. - */ + /** Main program for showing pattern. It does loop with factorial function. */ public static void main(String[] args) { LOGGER.info("Start calculating war casualties"); var result = loop(10, 1).result(); LOGGER.info("The number of orcs perished in the war: {}", result); - } - /** - * Manager for pattern. Define it with a factorial function. - */ + /** Manager for pattern. Define it with a factorial function. */ public static Trampoline loop(int times, int prod) { if (times == 0) { return Trampoline.done(prod); diff --git a/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java b/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java index 71db6c582d57..6b55849463d3 100644 --- a/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java +++ b/trampoline/src/test/java/com/iluwatar/trampoline/TrampolineAppTest.java @@ -28,9 +28,7 @@ import org.junit.jupiter.api.Test; -/** - * Test for trampoline pattern. - */ +/** Test for trampoline pattern. */ class TrampolineAppTest { @Test @@ -38,5 +36,4 @@ void testTrampolineWithFactorialFunction() { long result = TrampolineApp.loop(10, 1).result(); assertEquals(3_628_800, result); } - -} \ No newline at end of file +} diff --git a/transaction-script/README.md b/transaction-script/README.md index dacc95a0449a..2e4b89a64577 100644 --- a/transaction-script/README.md +++ b/transaction-script/README.md @@ -34,6 +34,10 @@ Wikipedia says > The Transaction Script design pattern is a straightforward way to organize business logic in applications, particularly suitable for scenarios where each request from the presentation layer can be handled by a single procedure. This pattern is often used in simple applications or in systems where rapid development and ease of understanding are crucial. Each transaction script is responsible for a particular task, such as processing an order or calculating a result, and typically interacts directly with the database. +Flowchart + +![Transaction Script Pattern Flowchart](./etc/transaction-script-flowchart.png) + ## Programmatic Example of Transaction Script Pattern in Java Our Transaction Script pattern in Java example is about booking hotel rooms. diff --git a/transaction-script/etc/transaction-script-flowchart.png b/transaction-script/etc/transaction-script-flowchart.png new file mode 100644 index 000000000000..33e7319eb3b9 Binary files /dev/null and b/transaction-script/etc/transaction-script-flowchart.png differ diff --git a/transaction-script/pom.xml b/transaction-script/pom.xml index af6d4571a4f3..9246dbd32aaf 100644 --- a/transaction-script/pom.xml +++ b/transaction-script/pom.xml @@ -34,6 +34,14 @@ 4.0.0 transaction-script + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + com.h2database h2 diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java index ee9db58fc4df..41d8cc4982f6 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/App.java @@ -31,18 +31,18 @@ import org.slf4j.LoggerFactory; /** - * Transaction Script (TS) is one of the simplest domain logic pattern. - * It needs less work to implement than other domain logic patterns, and therefore - * it’s perfect fit for smaller applications that don't need big architecture behind them. + * Transaction Script (TS) is one of the simplest domain logic pattern. It needs less work to + * implement than other domain logic patterns, and therefore it’s perfect fit for smaller + * applications that don't need big architecture behind them. * - *

      In this example we will use the TS pattern to implement booking and cancellation - * methods for a Hotel management App. The main method will initialise an instance of - * {@link Hotel} and add rooms to it. After that it will book and cancel a couple of rooms - * and that will be printed by the logger.

      + *

      In this example we will use the TS pattern to implement booking and cancellation methods for a + * Hotel management App. The main method will initialise an instance of {@link Hotel} and add rooms + * to it. After that it will book and cancel a couple of rooms and that will be printed by the + * logger. * - *

      The thing we have to note here is that all the operations related to booking or cancelling - * a room like checking the database if the room exists, checking the booking status or the - * room, calculating refund price are all clubbed inside a single transaction script method.

      + *

      The thing we have to note here is that all the operations related to booking or cancelling a + * room like checking the database if the room exists, checking the booking status or the room, + * calculating refund price are all clubbed inside a single transaction script method. */ public class App { @@ -50,9 +50,9 @@ public class App { private static final Logger LOGGER = LoggerFactory.getLogger(App.class); /** - * Program entry point. - * Initialises an instance of Hotel and adds rooms to it. - * Carries out booking and cancel booking transactions. + * Program entry point. Initialises an instance of Hotel and adds rooms to it. Carries out booking + * and cancel booking transactions. + * * @param args command line arguments * @throws Exception if any error occurs */ @@ -87,7 +87,6 @@ public static void main(String[] args) throws Exception { getRoomStatus(dao); deleteSchema(dataSource); - } private static void getRoomStatus(HotelDaoImpl dao) throws Exception { @@ -98,14 +97,14 @@ private static void getRoomStatus(HotelDaoImpl dao) throws Exception { private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); } } private static void createSchema(DataSource dataSource) throws Exception { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); } catch (Exception e) { throw new Exception(e.getMessage(), e); diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java index f0d077952c89..0e1ae0d7b94e 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/Hotel.java @@ -26,9 +26,7 @@ import lombok.extern.slf4j.Slf4j; -/** - * Hotel class to implement TS pattern. - */ +/** Hotel class to implement TS pattern. */ @Slf4j public class Hotel { diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java index b698ab114aa0..8675c50989e0 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDao.java @@ -27,9 +27,7 @@ import java.util.Optional; import java.util.stream.Stream; -/** - * DAO interface for hotel transactions. - */ +/** DAO interface for hotel transactions. */ public interface HotelDao { Stream getAll() throws Exception; diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java index e64ad15489b8..13173a01f041 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/HotelDaoImpl.java @@ -36,9 +36,7 @@ import javax.sql.DataSource; import lombok.extern.slf4j.Slf4j; -/** - * Implementation of database operations for Hotel class. - */ +/** Implementation of database operations for Hotel class. */ @Slf4j public class HotelDaoImpl implements HotelDao { @@ -54,28 +52,31 @@ public Stream getAll() throws Exception { var connection = getConnection(); var statement = connection.prepareStatement("SELECT * FROM ROOMS"); // NOSONAR var resultSet = statement.executeQuery(); // NOSONAR - return StreamSupport.stream(new Spliterators.AbstractSpliterator(Long.MAX_VALUE, - Spliterator.ORDERED) { - - @Override - public boolean tryAdvance(Consumer action) { - try { - if (!resultSet.next()) { - return false; - } - action.accept(createRoom(resultSet)); - return true; - } catch (Exception e) { - throw new RuntimeException(e); // NOSONAR - } - } - }, false).onClose(() -> { - try { - mutedClose(connection, statement, resultSet); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - }); + return StreamSupport.stream( + new Spliterators.AbstractSpliterator(Long.MAX_VALUE, Spliterator.ORDERED) { + + @Override + public boolean tryAdvance(Consumer action) { + try { + if (!resultSet.next()) { + return false; + } + action.accept(createRoom(resultSet)); + return true; + } catch (Exception e) { + throw new RuntimeException(e); // NOSONAR + } + } + }, + false) + .onClose( + () -> { + try { + mutedClose(connection, statement, resultSet); + } catch (Exception e) { + LOGGER.error(e.getMessage()); + } + }); } catch (Exception e) { throw new Exception(e.getMessage(), e); } @@ -86,7 +87,7 @@ public Optional getById(int id) throws Exception { ResultSet resultSet = null; try (var connection = getConnection(); - var statement = connection.prepareStatement("SELECT * FROM ROOMS WHERE ID = ?")) { + var statement = connection.prepareStatement("SELECT * FROM ROOMS WHERE ID = ?")) { statement.setInt(1, id); resultSet = statement.executeQuery(); @@ -111,7 +112,7 @@ public Boolean add(Room room) throws Exception { } try (var connection = getConnection(); - var statement = connection.prepareStatement("INSERT INTO ROOMS VALUES (?,?,?,?)")) { + var statement = connection.prepareStatement("INSERT INTO ROOMS VALUES (?,?,?,?)")) { statement.setInt(1, room.getId()); statement.setString(2, room.getRoomType()); statement.setInt(3, room.getPrice()); @@ -126,10 +127,9 @@ public Boolean add(Room room) throws Exception { @Override public Boolean update(Room room) throws Exception { try (var connection = getConnection(); - var statement = - connection - .prepareStatement("UPDATE ROOMS SET ROOM_TYPE = ?, PRICE = ?, BOOKED = ?" - + " WHERE ID = ?")) { + var statement = + connection.prepareStatement( + "UPDATE ROOMS SET ROOM_TYPE = ?, PRICE = ?, BOOKED = ?" + " WHERE ID = ?")) { statement.setString(1, room.getRoomType()); statement.setInt(2, room.getPrice()); statement.setBoolean(3, room.isBooked()); @@ -143,7 +143,7 @@ public Boolean update(Room room) throws Exception { @Override public Boolean delete(Room room) throws Exception { try (var connection = getConnection(); - var statement = connection.prepareStatement("DELETE FROM ROOMS WHERE ID = ?")) { + var statement = connection.prepareStatement("DELETE FROM ROOMS WHERE ID = ?")) { statement.setInt(1, room.getId()); return statement.executeUpdate() > 0; } catch (Exception e) { @@ -167,7 +167,8 @@ private void mutedClose(Connection connection, PreparedStatement statement, Resu } private Room createRoom(ResultSet resultSet) throws Exception { - return new Room(resultSet.getInt("ID"), + return new Room( + resultSet.getInt("ID"), resultSet.getString("ROOM_TYPE"), resultSet.getInt("PRICE"), resultSet.getBoolean("BOOKED")); diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java index 93716d2a0ba2..ea0a8e1d3b11 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/Room.java @@ -30,9 +30,7 @@ import lombok.Setter; import lombok.ToString; -/** - * A room POJO that represents the data that will be read from the data source. - */ +/** A room POJO that represents the data that will be read from the data source. */ @Setter @Getter @ToString @@ -44,5 +42,4 @@ public class Room { private String roomType; private int price; private boolean booked; - } diff --git a/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java b/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java index 29cad58620ef..08d581fc7699 100644 --- a/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java +++ b/transaction-script/src/main/java/com/iluwatar/transactionscript/RoomSchemaSql.java @@ -24,16 +24,12 @@ */ package com.iluwatar.transactionscript; -/** - * Customer Schema SQL Class. - */ +/** Customer Schema SQL Class. */ public final class RoomSchemaSql { public static final String CREATE_SCHEMA_SQL = "CREATE TABLE ROOMS (ID NUMBER, ROOM_TYPE VARCHAR(100), PRICE INT, BOOKED VARCHAR(100))"; public static final String DELETE_SCHEMA_SQL = "DROP TABLE ROOMS IF EXISTS"; - private RoomSchemaSql() { - } - + private RoomSchemaSql() {} } diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java index ac33b852dc63..291c6847bf31 100644 --- a/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.transactionscript; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -/** - * Tests that Transaction script example runs without errors. - */ +import org.junit.jupiter.api.Test; + +/** Tests that Transaction script example runs without errors. */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java index 87c8f58ad625..cce65f594170 100644 --- a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelDaoImplTest.java @@ -44,9 +44,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -/** - * Tests {@link HotelDaoImpl}. - */ +/** Tests {@link HotelDaoImpl}. */ class HotelDaoImplTest { private static final String DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; @@ -61,15 +59,13 @@ class HotelDaoImplTest { @BeforeEach void createSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); } } - /** - * Represents the scenario where DB connectivity is present. - */ + /** Represents the scenario where DB connectivity is present. */ @Nested class ConnectionSuccess { @@ -87,9 +83,7 @@ void setUp() throws Exception { Assertions.assertTrue(result); } - /** - * Represents the scenario when DAO operations are being performed on a non-existing room. - */ + /** Represents the scenario when DAO operations are being performed on a non-existing room. */ @Nested class NonExistingRoom { @@ -135,8 +129,7 @@ void retrieveShouldReturnNoRoom() throws Exception { } /** - * Represents a scenario where DAO operations are being performed on an already existing - * room. + * Represents a scenario where DAO operations are being performed on an already existing room. */ @Nested class ExistingRoom { @@ -161,8 +154,8 @@ void deletionShouldBeSuccessAndRoomShouldBeNonAccessible() throws Exception { } @Test - void updationShouldBeSuccessAndAccessingTheSameRoomShouldReturnUpdatedInformation() throws - Exception { + void updationShouldBeSuccessAndAccessingTheSameRoomShouldReturnUpdatedInformation() + throws Exception { final var newRoomType = "Double"; final var newPrice = 80; final var newBookingStatus = false; @@ -222,7 +215,10 @@ void updatingARoomFailsWithFeedbackToTheClient() { final var newRoomType = "Double"; final var newPrice = 80; final var newBookingStatus = false; - assertThrows(Exception.class, () -> dao.update(new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus))); + assertThrows( + Exception.class, + () -> + dao.update(new Room(existingRoom.getId(), newRoomType, newPrice, newBookingStatus))); } @Test @@ -234,7 +230,6 @@ void retrievingARoomByIdFailsWithExceptionAsFeedbackToClient() { void retrievingAllRoomsFailsWithExceptionAsFeedbackToClient() { assertThrows(Exception.class, () -> dao.getAll()); } - } /** @@ -245,7 +240,7 @@ void retrievingAllRoomsFailsWithExceptionAsFeedbackToClient() { @AfterEach void deleteSchema() throws SQLException { try (var connection = DriverManager.getConnection(DB_URL); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); } } diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java index 4045ac3e3370..b555cce8ca98 100644 --- a/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/HotelTest.java @@ -35,9 +35,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests {@link Hotel} - */ +/** Tests {@link Hotel} */ class HotelTest { private static final String H2_DB_URL = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"; @@ -53,7 +51,6 @@ void setUp() throws Exception { dao = new HotelDaoImpl(dataSource); addRooms(dao); hotel = new Hotel(dao); - } @Test @@ -68,7 +65,6 @@ void bookingRoomWithInvalidIdShouldRaiseException() { assertThrows(Exception.class, () -> hotel.bookRoom(getNonExistingRoomId())); } - @Test @SneakyThrows void bookingRoomAgainShouldRaiseException() { @@ -103,17 +99,16 @@ void cancelRoomBookingForUnbookedRoomShouldRaiseException() { assertThrows(Exception.class, () -> hotel.cancelRoomBooking(1)); } - private static void deleteSchema(DataSource dataSource) throws java.sql.SQLException { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.DELETE_SCHEMA_SQL); } } private static void createSchema(DataSource dataSource) throws Exception { try (var connection = dataSource.getConnection(); - var statement = connection.createStatement()) { + var statement = connection.createStatement()) { statement.execute(RoomSchemaSql.CREATE_SCHEMA_SQL); } catch (Exception e) { throw new Exception(e.getMessage(), e); @@ -150,4 +145,4 @@ public static List generateSampleRooms() { private int getNonExistingRoomId() { return 999; } -} \ No newline at end of file +} diff --git a/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java b/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java index a1c9fa7ff829..e567d393e6b7 100644 --- a/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java +++ b/transaction-script/src/test/java/com/iluwatar/transactionscript/RoomTest.java @@ -30,9 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -/** - * Tests {@link Room}. - */ +/** Tests {@link Room}. */ class RoomTest { private Room room; @@ -90,7 +88,10 @@ void equalsWithSameObjects() { @Test void testToString() { - assertEquals(String.format("Room(id=%s, roomType=%s, price=%s, booked=%s)", - room.getId(), room.getRoomType(), room.getPrice(), room.isBooked()), room.toString()); + assertEquals( + String.format( + "Room(id=%s, roomType=%s, price=%s, booked=%s)", + room.getId(), room.getRoomType(), room.getPrice(), room.isBooked()), + room.toString()); } } diff --git a/twin/README.md b/twin/README.md index b2913e0a963f..f6fb6923c069 100644 --- a/twin/README.md +++ b/twin/README.md @@ -31,6 +31,10 @@ Wikipedia says > The Twin pattern is a software design pattern that allows developers to simulate multiple inheritance in languages that don't support it. Instead of creating a single class inheriting from multiple parents, two closely linked subclasses are created, each inheriting from one of the parents. These subclasses are mutually dependent, working together as a pair to achieve the desired functionality. This approach avoids the complications and inefficiencies often associated with multiple inheritance, while still allowing the reuse of functionalities from different classes. +Sequence diagram + +![Twin Pattern Sequence Diagram](./etc/twin-sequence-diagram.png) + ## Programmatic Example of Twin Pattern in Java Consider a game where a ball needs to function as both a `GameItem` and a `Thread`. Instead of inheriting from both, we use the Twin pattern with two closely linked objects: `BallItem` and `BallThread`. diff --git a/twin/etc/twin-sequence-diagram.png b/twin/etc/twin-sequence-diagram.png new file mode 100644 index 000000000000..9dc73a8a08d2 Binary files /dev/null and b/twin/etc/twin-sequence-diagram.png differ diff --git a/twin/pom.xml b/twin/pom.xml index 6e76aa318473..e5bb9755230b 100644 --- a/twin/pom.xml +++ b/twin/pom.xml @@ -34,6 +34,14 @@ twin + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/twin/src/main/java/com/iluwatar/twin/App.java b/twin/src/main/java/com/iluwatar/twin/App.java index 7804a5f7ead2..65ffadf36a3b 100644 --- a/twin/src/main/java/com/iluwatar/twin/App.java +++ b/twin/src/main/java/com/iluwatar/twin/App.java @@ -32,7 +32,6 @@ * BallThread} class represent the twin objects to coordinate with each other (via the twin * reference) like a single class inheriting from {@link GameItem} and {@link Thread}. */ - public class App { /** diff --git a/twin/src/main/java/com/iluwatar/twin/BallItem.java b/twin/src/main/java/com/iluwatar/twin/BallItem.java index c1a0a2ddfb49..04d6b97c3483 100644 --- a/twin/src/main/java/com/iluwatar/twin/BallItem.java +++ b/twin/src/main/java/com/iluwatar/twin/BallItem.java @@ -37,8 +37,7 @@ public class BallItem extends GameItem { private boolean isSuspended; - @Setter - private BallThread twin; + @Setter private BallThread twin; @Override public void doDraw() { @@ -62,4 +61,3 @@ public void click() { } } } - diff --git a/twin/src/main/java/com/iluwatar/twin/BallThread.java b/twin/src/main/java/com/iluwatar/twin/BallThread.java index 9d4d9cf71a76..7768d3ebbb99 100644 --- a/twin/src/main/java/com/iluwatar/twin/BallThread.java +++ b/twin/src/main/java/com/iluwatar/twin/BallThread.java @@ -31,20 +31,16 @@ * This class is a UI thread for drawing the {@link BallItem}, and provide the method for suspend * and resume. It holds the reference of {@link BallItem} to delegate the draw task. */ - @Slf4j public class BallThread extends Thread { - @Setter - private BallItem twin; + @Setter private BallItem twin; private volatile boolean isSuspended; private volatile boolean isRunning = true; - /** - * Run the thread. - */ + /** Run the thread. */ public void run() { while (isRunning) { @@ -75,4 +71,3 @@ public void stopMe() { this.isSuspended = true; } } - diff --git a/twin/src/main/java/com/iluwatar/twin/GameItem.java b/twin/src/main/java/com/iluwatar/twin/GameItem.java index d557fc4abef8..1cdb025ff0f6 100644 --- a/twin/src/main/java/com/iluwatar/twin/GameItem.java +++ b/twin/src/main/java/com/iluwatar/twin/GameItem.java @@ -26,15 +26,11 @@ import lombok.extern.slf4j.Slf4j; -/** - * GameItem is a common class which provides some common methods for game object. - */ +/** GameItem is a common class which provides some common methods for game object. */ @Slf4j public abstract class GameItem { - /** - * Template method, do some common logic before draw. - */ + /** Template method, do some common logic before draw. */ public void draw() { LOGGER.info("draw"); doDraw(); @@ -42,6 +38,5 @@ public void draw() { public abstract void doDraw(); - public abstract void click(); } diff --git a/twin/src/test/java/com/iluwatar/twin/AppTest.java b/twin/src/test/java/com/iluwatar/twin/AppTest.java index 029d217610b7..8835f9185fbe 100644 --- a/twin/src/test/java/com/iluwatar/twin/AppTest.java +++ b/twin/src/test/java/com/iluwatar/twin/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.twin; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/twin/src/test/java/com/iluwatar/twin/BallItemTest.java b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java index 2b2f6223caf6..df869e92c9d2 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallItemTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallItemTest.java @@ -41,10 +41,7 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -/** - * BallItemTest - * - */ +/** BallItemTest */ class BallItemTest { private InMemoryAppender appender; @@ -67,12 +64,14 @@ void testClick() { final var inOrder = inOrder(ballThread); - IntStream.range(0, 10).forEach(i -> { - ballItem.click(); - inOrder.verify(ballThread).suspendMe(); - ballItem.click(); - inOrder.verify(ballThread).resumeMe(); - }); + IntStream.range(0, 10) + .forEach( + i -> { + ballItem.click(); + inOrder.verify(ballThread).suspendMe(); + ballItem.click(); + inOrder.verify(ballThread).resumeMe(); + }); inOrder.verifyNoMoreInteractions(); } @@ -104,9 +103,7 @@ void testMove() { assertEquals(1, appender.getLogSize()); } - /** - * Logging Appender Implementation - */ + /** Logging Appender Implementation */ static class InMemoryAppender extends AppenderBase { private final List log = new LinkedList<>(); @@ -128,5 +125,4 @@ public int getLogSize() { return log.size(); } } - } diff --git a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java index 26cf78509dcf..6ad431ff649e 100644 --- a/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java +++ b/twin/src/test/java/com/iluwatar/twin/BallThreadTest.java @@ -37,84 +37,81 @@ import org.junit.jupiter.api.Test; -/** - * BallThreadTest - * - */ +/** BallThreadTest */ class BallThreadTest { - /** - * Verify if the {@link BallThread} can be resumed - */ + /** Verify if the {@link BallThread} can be resumed */ @Test void testSuspend() { - assertTimeout(ofMillis(5000), () -> { - final var ballThread = new BallThread(); + assertTimeout( + ofMillis(5000), + () -> { + final var ballThread = new BallThread(); - final var ballItem = mock(BallItem.class); - ballThread.setTwin(ballItem); + final var ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); - ballThread.start(); - sleep(200); - verify(ballItem, atLeastOnce()).draw(); - verify(ballItem, atLeastOnce()).move(); - ballThread.suspendMe(); + ballThread.start(); + sleep(200); + verify(ballItem, atLeastOnce()).draw(); + verify(ballItem, atLeastOnce()).move(); + ballThread.suspendMe(); - sleep(1000); + sleep(1000); - ballThread.stopMe(); - ballThread.join(); + ballThread.stopMe(); + ballThread.join(); - verifyNoMoreInteractions(ballItem); - }); + verifyNoMoreInteractions(ballItem); + }); } - /** - * Verify if the {@link BallThread} can be resumed - */ + /** Verify if the {@link BallThread} can be resumed */ @Test void testResume() { - assertTimeout(ofMillis(5000), () -> { - final var ballThread = new BallThread(); + assertTimeout( + ofMillis(5000), + () -> { + final var ballThread = new BallThread(); - final var ballItem = mock(BallItem.class); - ballThread.setTwin(ballItem); + final var ballItem = mock(BallItem.class); + ballThread.setTwin(ballItem); - ballThread.suspendMe(); - ballThread.start(); + ballThread.suspendMe(); + ballThread.start(); - sleep(1000); + sleep(1000); - verifyNoMoreInteractions(ballItem); + verifyNoMoreInteractions(ballItem); - ballThread.resumeMe(); - sleep(300); - verify(ballItem, atLeastOnce()).draw(); - verify(ballItem, atLeastOnce()).move(); + ballThread.resumeMe(); + sleep(300); + verify(ballItem, atLeastOnce()).draw(); + verify(ballItem, atLeastOnce()).move(); - ballThread.stopMe(); - ballThread.join(); + ballThread.stopMe(); + ballThread.join(); - verifyNoMoreInteractions(ballItem); - }); + verifyNoMoreInteractions(ballItem); + }); } - /** - * Verify if the {@link BallThread} is interruptible - */ + /** Verify if the {@link BallThread} is interruptible */ @Test void testInterrupt() { - assertTimeout(ofMillis(5000), () -> { - final var ballThread = new BallThread(); - final var exceptionHandler = mock(UncaughtExceptionHandler.class); - ballThread.setUncaughtExceptionHandler(exceptionHandler); - ballThread.setTwin(mock(BallItem.class)); - ballThread.start(); - ballThread.interrupt(); - ballThread.join(); - - verify(exceptionHandler).uncaughtException(eq(ballThread), any(RuntimeException.class)); - verifyNoMoreInteractions(exceptionHandler); - }); + assertTimeout( + ofMillis(5000), + () -> { + final var ballThread = new BallThread(); + final var exceptionHandler = mock(UncaughtExceptionHandler.class); + ballThread.setUncaughtExceptionHandler(exceptionHandler); + ballThread.setTwin(mock(BallItem.class)); + ballThread.start(); + ballThread.interrupt(); + ballThread.join(); + + verify(exceptionHandler).uncaughtException(eq(ballThread), any(RuntimeException.class)); + verifyNoMoreInteractions(exceptionHandler); + }); } -} \ No newline at end of file +} diff --git a/type-object/README.md b/type-object/README.md index dbdd6a405c65..e28c495e0364 100644 --- a/type-object/README.md +++ b/type-object/README.md @@ -32,12 +32,16 @@ Real-world example In plain words -> Explore how the Java Type Object pattern enables dynamic creation and management of flexible and extensible sets of related classes, ideal for Java developers seeking modularity without modifying existing codebase. +> Type Object pattern enables dynamic creation and management of flexible and extensible sets of related classes, ideal for Java developers seeking modularity without modifying existing codebase. gameprogrammingpatterns.com says > Define a type object class and a typed object class. Each type object instance represents a different logical type. Each typed object stores a reference to the type object that describes its type. +Flowchart + +![Type Object Pattern Flowchart](./etc/type-object-flowchart.png) + ## Programmatic Example of Type Object Pattern in Java The Type Object pattern is a design pattern that allows for the creation of flexible and reusable objects by creating a class with a field that represents the 'type' of the object. This design pattern proves invaluable for scenarios where anticipated Java types are undefined upfront, or when modifications or additions are required, ensuring efficient Java development without frequent recompilations. diff --git a/type-object/etc/type-object-flowchart.png b/type-object/etc/type-object-flowchart.png new file mode 100644 index 000000000000..6c38e929fcf4 Binary files /dev/null and b/type-object/etc/type-object-flowchart.png differ diff --git a/type-object/pom.xml b/type-object/pom.xml index bf667b91df5b..a0809f9a8425 100644 --- a/type-object/pom.xml +++ b/type-object/pom.xml @@ -34,6 +34,14 @@ type-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + com.google.code.gson gson diff --git a/type-object/src/main/java/com/iluwatar/typeobject/App.java b/type-object/src/main/java/com/iluwatar/typeobject/App.java index d120baca0f2c..15b262a60ca3 100644 --- a/type-object/src/main/java/com/iluwatar/typeobject/App.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/App.java @@ -27,23 +27,22 @@ import lombok.extern.slf4j.Slf4j; /** - *

      Type object pattern is the pattern we use when the OOP concept of creating a base class and + * Type object pattern is the pattern we use when the OOP concept of creating a base class and * inheriting from it just doesn't work for the case in hand. This happens when we either don't know * what types we will need upfront, or want to be able to modify or add new types conveniently w/o * recompiling repeatedly. The pattern provides a solution by allowing flexible creation of required - * objects by creating one class, which has a field which represents the 'type' of the object.

      - *

      In this example, we have a mini candy-crush game in action. There are many different candies - * in the game, which may change over time, as we may want to upgrade the game. To make the object - * creation convenient, we have a class {@link Candy} which has a field name, parent, points and - * Type. We have a json file {@link candy} which contains the details about the candies, and this is - * parsed to get all the different candies in {@link JsonParser}. The {@link Cell} class is what the - * game matrix is made of, which has the candies that are to be crushed, and contains information on - * how crushing can be done, how the matrix is to be reconfigured and how points are to be gained. - * The {@link CellPool} class is a pool which reuses the candy cells that have been crushed instead - * of making new ones repeatedly. The {@link CandyGame} class has the rules for the continuation of - * the game and the {@link App} class has the game itself.

      + * objects by creating one class, which has a field which represents the 'type' of the object. In + * this example, we have a mini candy-crush game in action. There are many different candies in the + * game, which may change over time, as we may want to upgrade the game. To make the object creation + * convenient, we have a class {@link Candy} which has a field name, parent, points and Type. We + * have a json file {@link candy} which contains the details about the candies, and this is parsed + * to get all the different candies in {@link JsonParser}. The {@link Cell} class is what the game + * matrix is made of, which has the candies that are to be crushed, and contains information on how + * crushing can be done, how the matrix is to be reconfigured and how points are to be gained. The + * {@link CellPool} class is a pool which reuses the candy cells that have been crushed instead of + * making new ones repeatedly. The {@link CandyGame} class has the rules for the continuation of the + * game and the {@link App} class has the game itself. */ - @Slf4j public class App { @@ -53,8 +52,8 @@ public class App { * @param args command line args */ public static void main(String[] args) { - var givenTime = 50; //50ms - var toWin = 500; //points + var givenTime = 50; // 50ms + var toWin = 500; // points var pointsWon = 0; var numOfRows = 3; var start = System.currentTimeMillis(); diff --git a/type-object/src/main/java/com/iluwatar/typeobject/Candy.java b/type-object/src/main/java/com/iluwatar/typeobject/Candy.java index 78a613da4394..0edaea9f77fc 100644 --- a/type-object/src/main/java/com/iluwatar/typeobject/Candy.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/Candy.java @@ -44,8 +44,7 @@ enum Type { Candy parent; String parentName; - @Setter - private int points; + @Setter private int points; private final Type type; Candy(String name, String parentName, Type type, int points) { @@ -55,5 +54,4 @@ enum Type { this.points = points; this.parentName = parentName; } - } diff --git a/type-object/src/main/java/com/iluwatar/typeobject/CandyGame.java b/type-object/src/main/java/com/iluwatar/typeobject/CandyGame.java index 85d39c8c79c3..40412fbbd170 100644 --- a/type-object/src/main/java/com/iluwatar/typeobject/CandyGame.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/CandyGame.java @@ -33,11 +33,9 @@ * The CandyGame class contains the rules for the continuation of the game and has the game matrix * (field 'cells') and totalPoints gained during the game. */ - @Slf4j -@SuppressWarnings("java:S3776") //"Cognitive Complexity of methods should not be too high" +@SuppressWarnings("java:S3776") // "Cognitive Complexity of methods should not be too high" public class CandyGame { - Cell[][] cells; CellPool pool; int totalPoints; @@ -66,8 +64,11 @@ void printGameStatus() { var candyName = cell[j].candy.name; if (candyName.length() < 20) { var totalSpaces = 20 - candyName.length(); - LOGGER.info(numOfSpaces(totalSpaces / 2) + cell[j].candy.name - + numOfSpaces(totalSpaces - totalSpaces / 2) + "|"); + LOGGER.info( + numOfSpaces(totalSpaces / 2) + + cell[j].candy.name + + numOfSpaces(totalSpaces - totalSpaces / 2) + + "|"); } else { LOGGER.info(candyName + "|"); } @@ -85,17 +86,19 @@ List adjacentCells(int y, int x) { if (x == 0) { adjacent.add(this.cells[y][1]); } - if (y == cells.length - 1) { + if (y == cells.length - 1 && cells.length > 1) { adjacent.add(this.cells[cells.length - 2][x]); } - if (x == cells.length - 1) { + + if (x == cells.length - 1 && cells.length > 1) { adjacent.add(this.cells[y][cells.length - 2]); } + if (y > 0 && y < cells.length - 1) { adjacent.add(this.cells[y - 1][x]); adjacent.add(this.cells[y + 1][x]); } - if (x > 0 && x < cells.length - 1) { + if (y >= 0 && y < cells.length && x > 0 && x < cells[y].length - 1) { adjacent.add(this.cells[y][x - 1]); adjacent.add(this.cells[y][x + 1]); } @@ -169,5 +172,4 @@ void round(int timeSoFar, int totalTime) { end = System.currentTimeMillis(); } } - -} \ No newline at end of file +} diff --git a/type-object/src/main/java/com/iluwatar/typeobject/Cell.java b/type-object/src/main/java/com/iluwatar/typeobject/Cell.java index 7437e5eb10a6..d66eec8eed03 100644 --- a/type-object/src/main/java/com/iluwatar/typeobject/Cell.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/Cell.java @@ -40,7 +40,7 @@ public class Cell { int positionY; void crush(CellPool pool, Cell[][] cellMatrix) { - //take out from this position and put back in pool + // take out from this position and put back in pool pool.addNewCell(this); this.fillThisSpace(pool, cellMatrix); } @@ -67,8 +67,8 @@ void handleCrush(Cell c, CellPool pool, Cell[][] cellMatrix) { } int interact(Cell c, CellPool pool, Cell[][] cellMatrix) { - if (this.candy.getType().equals(Type.REWARD_FRUIT) || c.candy.getType() - .equals(Type.REWARD_FRUIT)) { + if (this.candy.getType().equals(Type.REWARD_FRUIT) + || c.candy.getType().equals(Type.REWARD_FRUIT)) { return 0; } else { if (this.candy.name.equals(c.candy.name)) { diff --git a/type-object/src/main/java/com/iluwatar/typeobject/CellPool.java b/type-object/src/main/java/com/iluwatar/typeobject/CellPool.java index 37c98a6d5326..18f94fe7416c 100644 --- a/type-object/src/main/java/com/iluwatar/typeobject/CellPool.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/CellPool.java @@ -51,7 +51,7 @@ public class CellPool { this.randomCode = assignRandomCandytypes(); } catch (Exception e) { LOGGER.error("Error occurred: ", e); - //manually initialising this.randomCode + // manually initialising this.randomCode this.randomCode = new Candy[5]; randomCode[0] = new Candy("cherry", FRUIT, Type.REWARD_FRUIT, 20); randomCode[1] = new Candy("mango", FRUIT, Type.REWARD_FRUIT, 20); @@ -74,7 +74,7 @@ Cell getNewCell() { } void addNewCell(Cell c) { - c.candy = randomCode[RANDOM.nextInt(randomCode.length)]; //changing candytype to new + c.candy = randomCode[RANDOM.nextInt(randomCode.length)]; // changing candytype to new this.pool.add(c); pointer++; } @@ -82,12 +82,12 @@ void addNewCell(Cell c) { Candy[] assignRandomCandytypes() throws JsonParseException { var jp = new JsonParser(); jp.parse(); - var randomCode = new Candy[jp.candies.size() - 2]; //exclude generic types 'fruit' and 'candy' + var randomCode = new Candy[jp.candies.size() - 2]; // exclude generic types 'fruit' and 'candy' var i = 0; for (var e = jp.candies.keys(); e.hasMoreElements(); ) { var s = e.nextElement(); if (!s.equals(FRUIT) && !s.equals(CANDY)) { - //not generic + // not generic randomCode[i] = jp.candies.get(s); i++; } diff --git a/type-object/src/main/java/com/iluwatar/typeobject/JsonParser.java b/type-object/src/main/java/com/iluwatar/typeobject/JsonParser.java index 80d079f29075..a7c66bd9477b 100644 --- a/type-object/src/main/java/com/iluwatar/typeobject/JsonParser.java +++ b/type-object/src/main/java/com/iluwatar/typeobject/JsonParser.java @@ -31,10 +31,7 @@ import java.io.InputStreamReader; import java.util.Hashtable; -/** - * The JsonParser class helps parse the json file candy.json to get all the different candies. - */ - +/** The JsonParser class helps parse the json file candy.json to get all the different candies. */ public class JsonParser { Hashtable candies; @@ -76,5 +73,4 @@ void setParentAndPoints() { } } } - } diff --git a/type-object/src/test/java/com/iluwatar/typeobject/CandyGameTest.java b/type-object/src/test/java/com/iluwatar/typeobject/CandyGameTest.java index 8ae3feb4b8f7..07310771df81 100644 --- a/type-object/src/test/java/com/iluwatar/typeobject/CandyGameTest.java +++ b/type-object/src/test/java/com/iluwatar/typeobject/CandyGameTest.java @@ -29,10 +29,7 @@ import com.iluwatar.typeobject.Candy.Type; import org.junit.jupiter.api.Test; -/** - * The CandyGameTest class tests the methods in the {@link CandyGame} class. - */ - +/** The CandyGameTest class tests the methods in the {@link CandyGame} class. */ class CandyGameTest { @Test @@ -66,5 +63,4 @@ void continueRoundTest() { var noneLeft = cg.continueRound(); assertTrue(fruitInLastRow && matchingCandy && !noneLeft); } - } diff --git a/type-object/src/test/java/com/iluwatar/typeobject/CellPoolTest.java b/type-object/src/test/java/com/iluwatar/typeobject/CellPoolTest.java index 9496db769ff1..ae73fa0f6046 100644 --- a/type-object/src/test/java/com/iluwatar/typeobject/CellPoolTest.java +++ b/type-object/src/test/java/com/iluwatar/typeobject/CellPoolTest.java @@ -29,10 +29,7 @@ import java.util.Hashtable; import org.junit.jupiter.api.Test; -/** - * The CellPoolTest class tests the methods in the {@link CellPool} class. - */ - +/** The CellPoolTest class tests the methods in the {@link CellPool} class. */ class CellPoolTest { @Test @@ -48,5 +45,4 @@ void assignRandomCandyTypesTest() { } assertTrue(ht.size() == 5 && parentTypes == 0); } - } diff --git a/type-object/src/test/java/com/iluwatar/typeobject/CellTest.java b/type-object/src/test/java/com/iluwatar/typeobject/CellTest.java index 28e51d860832..58236f5e6e65 100644 --- a/type-object/src/test/java/com/iluwatar/typeobject/CellTest.java +++ b/type-object/src/test/java/com/iluwatar/typeobject/CellTest.java @@ -30,9 +30,7 @@ import com.iluwatar.typeobject.Candy.Type; import org.junit.jupiter.api.Test; -/** - * The CellTest class tests the methods in the {@link Cell} class. - */ +/** The CellTest class tests the methods in the {@link Cell} class. */ class CellTest { @Test diff --git a/unit-of-work/README.md b/unit-of-work/README.md index 5b490d2962bd..ab2a16ec659e 100644 --- a/unit-of-work/README.md +++ b/unit-of-work/README.md @@ -29,6 +29,10 @@ In plain words > Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems. +Flowchart + +![Unit of Work Pattern Flowchart](./etc/unit-of-work-flowchart.png) + ## Programmatic Example of Unit of Work Pattern in Java Arms dealer has a database containing weapon information. Merchants all over the town are constantly updating this information causing a high load on the database server. To make the load more manageable we apply to Unit of Work pattern to send many small updates in batches. @@ -180,7 +184,7 @@ Here is the console output. ## When to Use the Unit Of Work Pattern in Java -* he Unit of Work pattern is ideal for managing multiple database operations in Java that must be executed as a single transaction, ensuring data consistency and integrity. +* The Unit of Work pattern is ideal for managing multiple database operations in Java that must be executed as a single transaction, ensuring data consistency and integrity. * Ideal in scenarios where changes to the business objects must be tracked and saved in a coordinated manner. * Useful when working with object-relational mapping (ORM) frameworks in Java such as Hibernate. diff --git a/unit-of-work/etc/unit-of-work-flowchart.png b/unit-of-work/etc/unit-of-work-flowchart.png new file mode 100644 index 000000000000..3d94c5a17424 Binary files /dev/null and b/unit-of-work/etc/unit-of-work-flowchart.png differ diff --git a/unit-of-work/pom.xml b/unit-of-work/pom.xml index 670c97e8baaa..357ffdf854ec 100644 --- a/unit-of-work/pom.xml +++ b/unit-of-work/pom.xml @@ -34,6 +34,14 @@ 4.0.0 unit-of-work + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java index ca1bf66dbbcd..5713d4144a81 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/App.java @@ -25,18 +25,14 @@ package com.iluwatar.unitofwork; import java.util.HashMap; -import java.util.List; -/** - * {@link App} Application demonstrating unit of work pattern. - */ +/** {@link App} Application demonstrating unit of work pattern. */ public class App { /** * Program entry point. * * @param args no argument sent */ - public static void main(String[] args) { // create some weapons var enchantedHammer = new Weapon(1, "enchanted hammer"); @@ -44,8 +40,7 @@ public static void main(String[] args) { var silverTrident = new Weapon(3, "silver trident"); // create repository - var weaponRepository = new ArmsDealer(new HashMap<>(), - new WeaponDatabase()); + var weaponRepository = new ArmsDealer(new HashMap<>(), new WeaponDatabase()); // perform operations on the weapons weaponRepository.registerNew(enchantedHammer); diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java index 9d10cce7da4a..c6a05026f9bf 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/ArmsDealer.java @@ -30,9 +30,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -/** - * {@link ArmsDealer} Weapon repository that supports unit of work for weapons. - */ +/** {@link ArmsDealer} Weapon repository that supports unit of work for weapons. */ @Slf4j @RequiredArgsConstructor public class ArmsDealer implements UnitOfWork { @@ -50,7 +48,6 @@ public void registerNew(Weapon weapon) { public void registerModified(Weapon weapon) { LOGGER.info("Registering {} for modify in context.", weapon.getName()); register(weapon, UnitActions.MODIFY.getActionValue()); - } @Override @@ -68,9 +65,7 @@ private void register(Weapon weapon, String operation) { context.put(operation, weaponsToOperate); } - /** - * All UnitOfWork operations are batched and executed together on commit only. - */ + /** All UnitOfWork operations are batched and executed together on commit only. */ @Override public void commit() { if (context == null || context.isEmpty()) { diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java index ce69e96939ba..d81ea3996284 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitActions.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * Enum representing unit actions. - */ +/** Enum representing unit actions. */ @Getter @RequiredArgsConstructor public enum UnitActions { diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitOfWork.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitOfWork.java index 5b7a9413a735..648ec36024ef 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitOfWork.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/UnitOfWork.java @@ -31,9 +31,7 @@ */ public interface UnitOfWork { - /** - * Any register new operation occurring on UnitOfWork is only going to be performed on commit. - */ + /** Any register new operation occurring on UnitOfWork is only going to be performed on commit. */ void registerNew(T entity); /** @@ -46,9 +44,6 @@ public interface UnitOfWork { */ void registerDeleted(T entity); - /** - * All UnitOfWork operations batched together executed in commit only. - */ + /** All UnitOfWork operations batched together executed in commit only. */ void commit(); - -} \ No newline at end of file +} diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java index d91e73e20bcc..5ceb55ad9e8a 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/Weapon.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -/** - * {@link Weapon} is an entity. - */ +/** {@link Weapon} is an entity. */ @Getter @RequiredArgsConstructor public class Weapon { diff --git a/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java b/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java index d6c5422c0f5b..4ea15d46b075 100644 --- a/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java +++ b/unit-of-work/src/main/java/com/iluwatar/unitofwork/WeaponDatabase.java @@ -24,20 +24,18 @@ */ package com.iluwatar.unitofwork; -/** - * Act as database for weapon records. - */ +/** Act as database for weapon records. */ public class WeaponDatabase { public void insert(Weapon weapon) { - //Some insert logic to DB + // Some insert logic to DB } public void modify(Weapon weapon) { - //Some modify logic to DB + // Some modify logic to DB } public void delete(Weapon weapon) { - //Some delete logic to DB + // Some delete logic to DB } } diff --git a/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java b/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java index 342035db7b57..6f4557698164 100644 --- a/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java +++ b/unit-of-work/src/test/java/com/iluwatar/unitofwork/AppTest.java @@ -28,13 +28,11 @@ import org.junit.jupiter.api.Test; -/** - * AppTest - */ +/** AppTest */ class AppTest { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java b/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java index b4d93d99d5ea..da06e2d76974 100644 --- a/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java +++ b/unit-of-work/src/test/java/com/iluwatar/unitofwork/ArmsDealerTest.java @@ -36,10 +36,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; -/** - * tests {@link ArmsDealer} - */ - +/** tests {@link ArmsDealer} */ class ArmsDealerTest { private final Weapon weapon1 = new Weapon(1, "battle ram"); private final Weapon weapon2 = new Weapon(1, "wooden lance"); diff --git a/update-header.sh b/update-header.sh deleted file mode 100755 index 48da4dcd6125..000000000000 --- a/update-header.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -# Find all README.md files in subdirectories one level deep -# and replace "### " with "## " at the beginning of lines -find . -maxdepth 2 -type f -name "README.md" -exec sed -i '' 's/^### /## /' {} \; - -echo "Headers updated in README.md files." diff --git a/update-method/README.md b/update-method/README.md index bd1fa2f75995..918abbaecec2 100644 --- a/update-method/README.md +++ b/update-method/README.md @@ -35,6 +35,10 @@ gameprogrammingpatterns.com says > The game world maintains a collection of objects. Each object implements an update method that simulates one frame of the object’s behavior. Each frame, the game updates every object in the collection. +Sequence diagram + +![Update Method Sequence Diagram](./etc/update-method-sequence-diagram.png) + ## Programmatic Example of Update Method Pattern in Java The Update Method design pattern is a behavioral pattern that simulates a collection of independent game or application objects by telling each to process one frame of behavior at a time. This pattern is commonly used in game development, where each object in the game world needs to be updated once per frame. diff --git a/update-method/etc/update-method-sequence-diagram.png b/update-method/etc/update-method-sequence-diagram.png new file mode 100644 index 000000000000..d8543d8db800 Binary files /dev/null and b/update-method/etc/update-method-sequence-diagram.png differ diff --git a/update-method/etc/update-method.urm.puml b/update-method/etc/update-method.urm.puml index 53d2a6eb6a2e..7be0440f74bc 100644 --- a/update-method/etc/update-method.urm.puml +++ b/update-method/etc/update-method.urm.puml @@ -20,7 +20,7 @@ package com.iluwatar.updatemethod { - PATROLLING_RIGHT_BOUNDING : int {static} # patrollingLeft : boolean + Skeleton(id : int) - + Skeleton(id : int, postition : int) + + Skeleton(id : int, position : int) + update() } class Statue { @@ -48,4 +48,4 @@ package com.iluwatar.updatemethod { World --> "-entities" Entity Skeleton --|> Entity Statue --|> Entity -@enduml \ No newline at end of file +@enduml diff --git a/update-method/pom.xml b/update-method/pom.xml index 84e0c7664dfa..c48003975f3c 100644 --- a/update-method/pom.xml +++ b/update-method/pom.xml @@ -34,6 +34,14 @@ 4.0.0 update-method + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/App.java b/update-method/src/main/java/com/iluwatar/updatemethod/App.java index 9d9456d31959..f51538842610 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/App.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/App.java @@ -27,10 +27,10 @@ import lombok.extern.slf4j.Slf4j; /** - * This pattern simulate a collection of independent objects by telling each to - * process one frame of behavior at a time. The game world maintains a collection - * of objects. Each object implements an update method that simulates one frame of - * the object’s behavior. Each frame, the game updates every object in the collection. + * This pattern simulate a collection of independent objects by telling each to process one frame of + * behavior at a time. The game world maintains a collection of objects. Each object implements an + * update method that simulates one frame of the object’s behavior. Each frame, the game updates + * every object in the collection. */ @Slf4j public class App { @@ -39,6 +39,7 @@ public class App { /** * Program entry point. + * * @param args runtime arguments */ public static void main(String[] args) { diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java b/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java index f7a8a0ff36d9..02de2638e6a7 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/Entity.java @@ -29,18 +29,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * Abstract class for all the entity types. - */ +/** Abstract class for all the entity types. */ public abstract class Entity { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); protected int id; - @Getter - @Setter - protected int position; + @Getter @Setter protected int position; public Entity(int id) { this.id = id; @@ -48,5 +44,4 @@ public Entity(int id) { } public abstract void update(); - } diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java b/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java index f6e6f25d44e7..26d861d784fc 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/Skeleton.java @@ -25,10 +25,9 @@ package com.iluwatar.updatemethod; /** - * Skeletons are always patrolling on the game map. Initially all the skeletons - * patrolling to the right, and after them reach the bounding, it will start - * patrolling to the left. For each frame, one skeleton will move 1 position - * step. + * Skeletons are always patrolling on the game map. Initially all the skeletons patrolling to the + * right, and after them reach the bounding, it will start patrolling to the left. For each frame, + * one skeleton will move 1 position step. */ public class Skeleton extends Entity { @@ -76,4 +75,3 @@ public void update() { logger.info("Skeleton {} is on position {}.", id, position); } } - diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java b/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java index 7212a7a1b6d3..d67baf2d0711 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/Statue.java @@ -24,9 +24,7 @@ */ package com.iluwatar.updatemethod; -/** - * Statues shoot lightning at regular intervals. - */ +/** Statues shoot lightning at regular intervals. */ public class Statue extends Entity { protected int frames; diff --git a/update-method/src/main/java/com/iluwatar/updatemethod/World.java b/update-method/src/main/java/com/iluwatar/updatemethod/World.java index d67f97e0d32f..ef93c82f8c3d 100644 --- a/update-method/src/main/java/com/iluwatar/updatemethod/World.java +++ b/update-method/src/main/java/com/iluwatar/updatemethod/World.java @@ -29,9 +29,7 @@ import java.util.List; import lombok.extern.slf4j.Slf4j; -/** - * The game world class. Maintain all the objects existed in the game frames. - */ +/** The game world class. Maintain all the objects existed in the game frames. */ @Slf4j public class World { @@ -45,9 +43,9 @@ public World() { } /** - * Main game loop. This loop will always run until the game is over. For - * each loop it will process user input, update internal status, and render - * the next frames. For more detail please refer to the game-loop pattern. + * Main game loop. This loop will always run until the game is over. For each loop it will process + * user input, update internal status, and render the next frames. For more detail please refer to + * the game-loop pattern. */ private void gameLoop() { while (isRunning) { @@ -58,9 +56,8 @@ private void gameLoop() { } /** - * Handle any user input that has happened since the last call. In order to - * simulate the situation in real-life game, here we add a random time lag. - * The time lag ranges from 50 ms to 250 ms. + * Handle any user input that has happened since the last call. In order to simulate the situation + * in real-life game, here we add a random time lag. The time lag ranges from 50 ms to 250 ms. */ private void processInput() { try { @@ -73,8 +70,8 @@ private void processInput() { } /** - * Update internal status. The update method pattern invoke update method for - * each entity in the game. + * Update internal status. The update method pattern invoke update method for each entity in the + * game. */ private void update() { for (var entity : entities) { @@ -82,17 +79,12 @@ private void update() { } } - /** - * Render the next frame. Here we do nothing since it is not related to the - * pattern. - */ + /** Render the next frame. Here we do nothing since it is not related to the pattern. */ private void render() { // Does Nothing } - /** - * Run game loop. - */ + /** Run game loop. */ public void run() { LOGGER.info("Start game."); isRunning = true; @@ -100,9 +92,7 @@ public void run() { thread.start(); } - /** - * Stop game loop. - */ + /** Stop game loop. */ public void stop() { LOGGER.info("Stop game."); isRunning = false; @@ -111,5 +101,4 @@ public void stop() { public void addEntity(Entity entity) { entities.add(entity); } - } diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java index e684cfef0134..53a9d2334d4d 100644 --- a/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java +++ b/update-method/src/test/java/com/iluwatar/updatemethod/AppTest.java @@ -32,6 +32,6 @@ class AppTest { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java index c5672ae95a63..6f602245acd4 100644 --- a/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java +++ b/update-method/src/test/java/com/iluwatar/updatemethod/SkeletonTest.java @@ -24,14 +24,14 @@ */ package com.iluwatar.updatemethod; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + class SkeletonTest { private static Skeleton skeleton; diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java index 7df1b9755fba..45b062d5389d 100644 --- a/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java +++ b/update-method/src/test/java/com/iluwatar/updatemethod/StatueTest.java @@ -24,12 +24,12 @@ */ package com.iluwatar.updatemethod; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; - class StatueTest { private static Statue statue; diff --git a/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java b/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java index d4284d80109e..3c94d5dd54ea 100644 --- a/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java +++ b/update-method/src/test/java/com/iluwatar/updatemethod/WorldTest.java @@ -24,14 +24,14 @@ */ package com.iluwatar.updatemethod; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + class WorldTest { private static World world; diff --git a/value-object/README.md b/value-object/README.md index 88478b9314bf..84b8e029debd 100644 --- a/value-object/README.md +++ b/value-object/README.md @@ -43,6 +43,10 @@ Wikipedia says > In computer science, a value object is a small object that represents a simple entity whose equality is not based on identity: i.e. two value objects are equal when they have the same value, not necessarily being the same object. +Flowchart + +![Value Object Pattern Flowchart](./etc/value-object-flowchart.png) + ## Programmatic Example of Value Object Pattern in Java There is a class for hero statistics in a role-playing game. The statistics contain attributes such as strength, intelligence, and luck. The statistics of different heroes should be equal when all the attributes are equal. diff --git a/value-object/etc/value-object-flowchart.png b/value-object/etc/value-object-flowchart.png new file mode 100644 index 000000000000..b2feb6959cb2 Binary files /dev/null and b/value-object/etc/value-object-flowchart.png differ diff --git a/value-object/pom.xml b/value-object/pom.xml index 2ffba3b64820..b8b2d3433fdc 100644 --- a/value-object/pom.xml +++ b/value-object/pom.xml @@ -34,6 +34,14 @@ value-object + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/value-object/src/main/java/com/iluwatar/value/object/App.java b/value-object/src/main/java/com/iluwatar/value/object/App.java index 03add5a89478..0082fae44f56 100644 --- a/value-object/src/main/java/com/iluwatar/value/object/App.java +++ b/value-object/src/main/java/com/iluwatar/value/object/App.java @@ -43,9 +43,7 @@ @Slf4j public class App { - /** - * This example creates three HeroStats (value objects) and checks equality between those. - */ + /** This example creates three HeroStats (value objects) and checks equality between those. */ public static void main(String[] args) { var statA = HeroStat.valueOf(10, 5, 0); var statB = HeroStat.valueOf(10, 5, 0); diff --git a/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java index a639e741fcf2..fbeeadd75a86 100644 --- a/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java +++ b/value-object/src/main/java/com/iluwatar/value/object/HeroStat.java @@ -31,8 +31,7 @@ * HeroStat is a value object. * * @see - * http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html - * + * http://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html */ @Value(staticConstructor = "valueOf") @ToString diff --git a/value-object/src/test/java/com/iluwatar/value/object/AppTest.java b/value-object/src/test/java/com/iluwatar/value/object/AppTest.java index a0a76eac230b..8e957df0cdda 100644 --- a/value-object/src/test/java/com/iluwatar/value/object/AppTest.java +++ b/value-object/src/test/java/com/iluwatar/value/object/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.value.object; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java b/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java index 82c66322d910..fe3238b3f076 100644 --- a/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java +++ b/value-object/src/test/java/com/iluwatar/value/object/HeroStatTest.java @@ -29,17 +29,16 @@ import org.junit.jupiter.api.Test; -/** - * Unit test for HeroStat. - */ +/** Unit test for HeroStat. */ class HeroStatTest { /** * Tester for equals() and hashCode() methods of a class. Using guava's EqualsTester. * - * @see - * http://static.javadoc.io/com.google.guava/guava-testlib/19.0/com/google/common/testing/EqualsTester.html - * + * @see + * http://static.javadoc.io/com.google.guava/guava-testlib/19.0/com/google/common/testing/EqualsTester.html + * */ @Test void testEquals() { @@ -60,5 +59,4 @@ void testToString() { assertEquals(heroStatA.toString(), heroStatB.toString()); assertNotEquals(heroStatA.toString(), heroStatC.toString()); } - } diff --git a/version-number/README.md b/version-number/README.md index 6178f2317a89..8a18046505b9 100644 --- a/version-number/README.md +++ b/version-number/README.md @@ -35,6 +35,10 @@ Wikipedia says > The Version Number pattern is a technique used to manage concurrent access to data in databases and other data stores. It involves associating a version number with each record, which is incremented every time the record is updated. This pattern helps ensure that when multiple users or processes attempt to update the same data simultaneously, conflicts can be detected and resolved. +Flowchart + +![Version Number Pattern Flowchart](./etc/version-number-flowchart.png) + ## Programmatic Example of Version Number Pattern in Java Alice and Bob are working on the book, which stored in the database. Our heroes are making changes simultaneously, and we need some mechanism to prevent them from overwriting each other. diff --git a/version-number/etc/version-number-flowchart.png b/version-number/etc/version-number-flowchart.png new file mode 100644 index 000000000000..2d60aa30f7bf Binary files /dev/null and b/version-number/etc/version-number-flowchart.png differ diff --git a/version-number/pom.xml b/version-number/pom.xml index 54128114ebf2..d3fab29bd77e 100644 --- a/version-number/pom.xml +++ b/version-number/pom.xml @@ -34,6 +34,14 @@ version-number + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/App.java b/version-number/src/main/java/com/iluwatar/versionnumber/App.java index 8b1818e296c9..98e12228c822 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/App.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/App.java @@ -28,16 +28,15 @@ import org.slf4j.LoggerFactory; /** - * The Version Number pattern helps to resolve concurrency conflicts in applications. - * Usually these conflicts arise in database operations, when multiple clients are trying - * to update the same record simultaneously. - * Resolving such conflicts requires determining whether an object has changed. - * For this reason we need a version number that is incremented with each change - * to the underlying data, e.g. database. The version number can be used by repositories - * to check for external changes and to report concurrency issues to the users. + * The Version Number pattern helps to resolve concurrency conflicts in applications. Usually these + * conflicts arise in database operations, when multiple clients are trying to update the same + * record simultaneously. Resolving such conflicts requires determining whether an object has + * changed. For this reason we need a version number that is incremented with each change to the + * underlying data, e.g. database. The version number can be used by repositories to check for + * external changes and to report concurrency issues to the users. * - *

      In this example we show how Alice and Bob will try to update the {@link Book} - * and save it simultaneously to {@link BookRepository}, which represents a typical database. + *

      In this example we show how Alice and Bob will try to update the {@link Book} and save it + * simultaneously to {@link BookRepository}, which represents a typical database. * *

      As in real databases, each client operates with copy of the data instead of original data * passed by reference, that's why we are using {@link Book} copy-constructor here. @@ -50,10 +49,8 @@ public class App { * * @param args command line args */ - public static void main(String[] args) throws - BookDuplicateException, - BookNotFoundException, - VersionMismatchException { + public static void main(String[] args) + throws BookDuplicateException, BookNotFoundException, VersionMismatchException { var bookId = 1; var bookRepository = new BookRepository(); diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/Book.java b/version-number/src/main/java/com/iluwatar/versionnumber/Book.java index 774ceac67d9a..067e08291546 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/Book.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/Book.java @@ -27,9 +27,7 @@ import lombok.Getter; import lombok.Setter; -/** - * Model class for Book entity. - */ +/** Model class for Book entity. */ @Getter @Setter public class Book { @@ -40,9 +38,7 @@ public class Book { public Book() {} - /** - * We need this copy constructor to copy book representation in {@link BookRepository}. - */ + /** We need this copy constructor to copy book representation in {@link BookRepository}. */ public Book(Book book) { this.id = book.id; this.title = book.title; diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java index 9763e1fa5362..7651a7bb573e 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookDuplicateException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.versionnumber; -/** - * When someone has tried to add a book which repository already have. - */ +/** When someone has tried to add a book which repository already have. */ public class BookDuplicateException extends Exception { public BookDuplicateException(String message) { super(message); diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java index 9f45ad0cb5d0..deceebdd106c 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookNotFoundException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.versionnumber; -/** - * Client has tried to make an operation with book which repository does not have. - */ +/** Client has tried to make an operation with book which repository does not have. */ public class BookNotFoundException extends Exception { public BookNotFoundException(String message) { super(message); diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java b/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java index a1baa3665512..087b28934f75 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/BookRepository.java @@ -27,18 +27,17 @@ import java.util.concurrent.ConcurrentHashMap; /** - * This repository represents simplified database. - * As a typical database do, repository operates with copies of object. - * So client and repo has different copies of book, which can lead to concurrency conflicts - * as much as in real databases. + * This repository represents simplified database. As a typical database do, repository operates + * with copies of object. So client and repo has different copies of book, which can lead to + * concurrency conflicts as much as in real databases. */ public class BookRepository { private final ConcurrentHashMap collection = new ConcurrentHashMap<>(); private final Object lock = new Object(); /** - * Adds book to collection. - * Actually we are putting copy of book (saving a book by value, not by reference); + * Adds book to collection. Actually we are putting copy of book (saving a book by value, not by + * reference); */ public void add(Book book) throws BookDuplicateException { if (collection.containsKey(book.getId())) { @@ -49,9 +48,7 @@ public void add(Book book) throws BookDuplicateException { collection.put(book.getId(), new Book(book)); } - /** - * Updates book in collection only if client has modified the latest version of the book. - */ + /** Updates book in collection only if client has modified the latest version of the book. */ public void update(Book book) throws BookNotFoundException, VersionMismatchException { if (!collection.containsKey(book.getId())) { throw new BookNotFoundException("Not found book with id: " + book.getId()); @@ -62,9 +59,10 @@ public void update(Book book) throws BookNotFoundException, VersionMismatchExcep var latestBook = collection.get(book.getId()); if (book.getVersion() != latestBook.getVersion()) { throw new VersionMismatchException( - "Tried to update stale version " + book.getVersion() - + " while actual version is " + latestBook.getVersion() - ); + "Tried to update stale version " + + book.getVersion() + + " while actual version is " + + latestBook.getVersion()); } // update version, including client representation - modify by reference here @@ -76,8 +74,8 @@ public void update(Book book) throws BookNotFoundException, VersionMismatchExcep } /** - * Returns book representation to the client. - * Representation means we are returning copy of the book. + * Returns book representation to the client. Representation means we are returning copy of the + * book. */ public Book get(long bookId) throws BookNotFoundException { if (!collection.containsKey(bookId)) { diff --git a/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java b/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java index da4d2916258b..68d878333c07 100644 --- a/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java +++ b/version-number/src/main/java/com/iluwatar/versionnumber/VersionMismatchException.java @@ -24,9 +24,7 @@ */ package com.iluwatar.versionnumber; -/** - * Client has tried to update a stale version of the book. - */ +/** Client has tried to update a stale version of the book. */ public class VersionMismatchException extends Exception { public VersionMismatchException(String message) { super(message); diff --git a/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java b/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java index a47da0e8366e..640fd95330fa 100644 --- a/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java +++ b/version-number/src/test/java/com/iluwatar/versionnumber/AppTest.java @@ -24,24 +24,21 @@ */ package com.iluwatar.versionnumber; -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#main(String[])} - * throws an exception. + *

      Solution: Inserted assertion to check whether the execution of the main method in {@link + * App#main(String[])} throws an exception. */ - @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java b/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java index b480cfd41f61..f97e67150c2b 100644 --- a/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java +++ b/version-number/src/test/java/com/iluwatar/versionnumber/BookRepositoryTest.java @@ -24,14 +24,12 @@ */ package com.iluwatar.versionnumber; +import static org.junit.jupiter.api.Assertions.*; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - -/** - * Tests for {@link BookRepository} - */ +/** Tests for {@link BookRepository} */ class BookRepositoryTest { private final long bookId = 1; private final BookRepository bookRepository = new BookRepository(); @@ -50,7 +48,8 @@ void testDefaultVersionRemainsZeroAfterAdd() throws BookNotFoundException { } @Test - void testAliceAndBobHaveDifferentVersionsAfterAliceUpdate() throws BookNotFoundException, VersionMismatchException { + void testAliceAndBobHaveDifferentVersionsAfterAliceUpdate() + throws BookNotFoundException, VersionMismatchException { final var aliceBook = bookRepository.get(bookId); final var bobBook = bookRepository.get(bookId); @@ -66,7 +65,8 @@ void testAliceAndBobHaveDifferentVersionsAfterAliceUpdate() throws BookNotFoundE } @Test - void testShouldThrowVersionMismatchExceptionOnStaleUpdate() throws BookNotFoundException, VersionMismatchException { + void testShouldThrowVersionMismatchExceptionOnStaleUpdate() + throws BookNotFoundException, VersionMismatchException { final var aliceBook = bookRepository.get(bookId); final var bobBook = bookRepository.get(bookId); diff --git a/virtual-proxy/README.md b/virtual-proxy/README.md index e5b32fd096a4..187eeec1dc55 100644 --- a/virtual-proxy/README.md +++ b/virtual-proxy/README.md @@ -35,6 +35,10 @@ Wikipedia says > A proxy that controls access to a resource that is expensive to create. +Sequence diagram + +![Virtual Proxy Sequence Diagram](./etc/virtual-proxy-sequence-diagram.png) + ## Programmatic Example of Virtual Proxy Pattern in Java The Virtual Proxy design pattern in Java can optimize resource utilization and system performance. diff --git a/virtual-proxy/etc/virtual-proxy-sequence-diagram.png b/virtual-proxy/etc/virtual-proxy-sequence-diagram.png new file mode 100644 index 000000000000..9ff38c0ad056 Binary files /dev/null and b/virtual-proxy/etc/virtual-proxy-sequence-diagram.png differ diff --git a/virtual-proxy/etc/virtual-proxy.urm.puml b/virtual-proxy/etc/virtual-proxy.urm.puml new file mode 100644 index 000000000000..e30149e3809e --- /dev/null +++ b/virtual-proxy/etc/virtual-proxy.urm.puml @@ -0,0 +1,26 @@ +@startuml +package com.iluwatar.virtual.proxy { + class App { + + App() + + main(args : String[]) {static} + } + interface ExpensiveObject { + + process() {abstract} + } + class RealVideoObject { + - LOGGER : Logger {static} + + RealVideoObject() + - heavyInitialConfiguration() + + process() + } + class VideoObjectProxy { + - realVideoObject : RealVideoObject + + VideoObjectProxy() + + getRealVideoObject() : RealVideoObject + + process() + } +} +VideoObjectProxy --> "-realVideoObject" RealVideoObject +RealVideoObject ..|> ExpensiveObject +VideoObjectProxy ..|> ExpensiveObject +@enduml \ No newline at end of file diff --git a/virtual-proxy/pom.xml b/virtual-proxy/pom.xml index 8f3fbc8f21fa..d9cf174f9a4e 100644 --- a/virtual-proxy/pom.xml +++ b/virtual-proxy/pom.xml @@ -35,6 +35,14 @@ virtual-proxy + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine @@ -46,8 +54,9 @@ test - junit - junit + org.hamcrest + hamcrest + 3.0 test diff --git a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/App.java b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/App.java index b29d6fd996b2..3f78bfe8178d 100644 --- a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/App.java +++ b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/App.java @@ -25,9 +25,7 @@ package com.iluwatar.virtual.proxy; -/** - * The main application class that sets up and runs the Virtual Proxy pattern demo. - */ +/** The main application class that sets up and runs the Virtual Proxy pattern demo. */ public class App { /** * The entry point of the application. @@ -36,7 +34,7 @@ public class App { */ public static void main(String[] args) { ExpensiveObject videoObject = new VideoObjectProxy(); - videoObject.process(); // The first call creates and plays the video - videoObject.process(); // Subsequent call uses the already created object + videoObject.process(); // The first call creates and plays the video + videoObject.process(); // Subsequent call uses the already created object } -} \ No newline at end of file +} diff --git a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/ExpensiveObject.java b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/ExpensiveObject.java index b5474e161850..12e183b4e2fa 100644 --- a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/ExpensiveObject.java +++ b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/ExpensiveObject.java @@ -24,9 +24,7 @@ */ package com.iluwatar.virtual.proxy; -/** - * Interface for expensive object and proxy object. - */ +/** Interface for expensive object and proxy object. */ public interface ExpensiveObject { void process(); -} \ No newline at end of file +} diff --git a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/RealVideoObject.java b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/RealVideoObject.java index dbdd4353f83c..afadf1df6549 100644 --- a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/RealVideoObject.java +++ b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/RealVideoObject.java @@ -28,9 +28,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -/** - * Represents a real video object that is expensive to create and manage. - */ +/** Represents a real video object that is expensive to create and manage. */ @Slf4j @Getter public class RealVideoObject implements ExpensiveObject { @@ -47,4 +45,4 @@ private void heavyInitialConfiguration() { public void process() { LOGGER.info("Processing and playing video content..."); } -} \ No newline at end of file +} diff --git a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/VideoObjectProxy.java b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/VideoObjectProxy.java index 52b0e5f3f7d8..9138fc95b883 100644 --- a/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/VideoObjectProxy.java +++ b/virtual-proxy/src/main/java/com/iluwatar/virtual/proxy/VideoObjectProxy.java @@ -28,7 +28,8 @@ import lombok.Getter; /** - * A proxy class for the real video object, providing a layer of control over the object instantiation. + * A proxy class for the real video object, providing a layer of control over the object + * instantiation. */ @Getter public class VideoObjectProxy implements ExpensiveObject { @@ -41,4 +42,4 @@ public void process() { } realVideoObject.process(); } -} \ No newline at end of file +} diff --git a/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/AppTest.java b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/AppTest.java index 1f7cf8b6df31..b4b424e94359 100644 --- a/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/AppTest.java +++ b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/AppTest.java @@ -25,17 +25,15 @@ package com.iluwatar.virtual.proxy; -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 { @Test void shouldExecuteApplicationWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } -} \ No newline at end of file +} diff --git a/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/RealVideoObjectTest.java b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/RealVideoObjectTest.java index 90f20355677b..d3d471ad0987 100644 --- a/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/RealVideoObjectTest.java +++ b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/RealVideoObjectTest.java @@ -23,15 +23,14 @@ * THE SOFTWARE. */ package com.iluwatar.virtual.proxy; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import org.junit.jupiter.api.Test; -/** - * Tests for RealVideoObject. - */ +/** Tests for RealVideoObject. */ class RealVideoObjectTest { @Test @@ -50,4 +49,4 @@ public void processDoesNotThrowException() { RealVideoObject realVideoObject = new RealVideoObject(); assertDoesNotThrow(realVideoObject::process, "Process method should not throw any exception"); } -} \ No newline at end of file +} diff --git a/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/VideoObjectProxyTest.java b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/VideoObjectProxyTest.java index 2ede2c8a412c..7d91b7c39fed 100644 --- a/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/VideoObjectProxyTest.java +++ b/virtual-proxy/src/test/java/com/iluwatar/virtual/proxy/VideoObjectProxyTest.java @@ -31,9 +31,7 @@ import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Test; -/** - * Tests for VideoObjectProxy. - */ +/** Tests for VideoObjectProxy. */ public class VideoObjectProxyTest { @Test void shouldBeInstanceOfExpensiveObject() { @@ -47,7 +45,7 @@ void constructorDoesNotThrowException() { @Test void processDoesNotThrowException() { - assertDoesNotThrow(() -> new VideoObjectProxy().process(), "Process method should not throw any exception"); + assertDoesNotThrow( + () -> new VideoObjectProxy().process(), "Process method should not throw any exception"); } - -} \ No newline at end of file +} diff --git a/visitor/README.md b/visitor/README.md index 674062606a31..bb5ee4e445ee 100644 --- a/visitor/README.md +++ b/visitor/README.md @@ -32,6 +32,10 @@ Wikipedia says > In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures. +Sequence diagram + +![Visitor sequence diagram](./etc/visitor-sequence-diagram.png) + ## Programmatic Example of Visitor Pattern in Java Consider a tree structure with army units. Commander has two sergeants under it and each sergeant has three soldiers under them. Given that the hierarchy implements the visitor pattern, we can easily create new objects that interact with the commander, sergeants, soldiers, or all of them. @@ -221,10 +225,6 @@ Program output: 14:58:06.118 [main] INFO com.iluwatar.visitor.CommanderVisitor -- Good to see you commander ``` -## Detailed Explanation of Visitor Pattern with Real-World Examples - -![Visitor](./etc/visitor_1.png "Visitor") - ## When to Use the Visitor Pattern in Java Use the Visitor pattern when diff --git a/visitor/etc/visitor-sequence-diagram.png b/visitor/etc/visitor-sequence-diagram.png new file mode 100644 index 000000000000..61264826d771 Binary files /dev/null and b/visitor/etc/visitor-sequence-diagram.png differ diff --git a/visitor/pom.xml b/visitor/pom.xml index 5825b7dbdf55..480fafd0350a 100644 --- a/visitor/pom.xml +++ b/visitor/pom.xml @@ -34,6 +34,14 @@ visitor + + org.slf4j + slf4j-api + + + ch.qos.logback + logback-classic + org.junit.jupiter junit-jupiter-engine diff --git a/visitor/src/main/java/com/iluwatar/visitor/App.java b/visitor/src/main/java/com/iluwatar/visitor/App.java index 6023649e6e8b..61c91fbb39fb 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/App.java +++ b/visitor/src/main/java/com/iluwatar/visitor/App.java @@ -25,12 +25,12 @@ package com.iluwatar.visitor; /** - *

      Visitor pattern defines a mechanism to apply operations on nodes in a hierarchy. New - * operations can be added without altering the node interface.

      + * Visitor pattern defines a mechanism to apply operations on nodes in a hierarchy. New operations + * can be added without altering the node interface. * *

      In this example there is a unit hierarchy beginning from {@link Commander}. This hierarchy is * traversed by visitors. {@link SoldierVisitor} applies its operation on {@link Soldier}s, {@link - * SergeantVisitor} on {@link Sergeant}s and so on.

      + * SergeantVisitor} on {@link Sergeant}s and so on. */ public class App { @@ -41,10 +41,10 @@ public class App { */ public static void main(String[] args) { - var commander = new Commander( - new Sergeant(new Soldier(), new Soldier(), new Soldier()), - new Sergeant(new Soldier(), new Soldier(), new Soldier()) - ); + var commander = + new Commander( + new Sergeant(new Soldier(), new Soldier(), new Soldier()), + new Sergeant(new Soldier(), new Soldier(), new Soldier())); commander.accept(new SoldierVisitor()); commander.accept(new SergeantVisitor()); commander.accept(new CommanderVisitor()); diff --git a/visitor/src/main/java/com/iluwatar/visitor/Commander.java b/visitor/src/main/java/com/iluwatar/visitor/Commander.java index 01c8a43ba8c8..b6d2af80d64d 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Commander.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Commander.java @@ -1,50 +1,49 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -/** - * Commander. - */ -public class Commander extends Unit { - - public Commander(Unit... children) { - super(children); - } - - /** - * Accept a Visitor. - * @param visitor UnitVisitor to be accepted - */ - @Override - public void accept(UnitVisitor visitor) { - visitor.visit(this); - super.accept(visitor); - } - - @Override - public String toString() { - return "commander"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +/** Commander. */ +public class Commander extends Unit { + + public Commander(Unit... children) { + super(children); + } + + /** + * Accept a Visitor. + * + * @param visitor UnitVisitor to be accepted + */ + @Override + public void accept(UnitVisitor visitor) { + visitor.visit(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "commander"; + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/CommanderVisitor.java b/visitor/src/main/java/com/iluwatar/visitor/CommanderVisitor.java index a0286163a618..326d28764030 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/CommanderVisitor.java +++ b/visitor/src/main/java/com/iluwatar/visitor/CommanderVisitor.java @@ -1,61 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -import lombok.extern.slf4j.Slf4j; - -/** - * CommanderVisitor. - */ -@Slf4j -public class CommanderVisitor implements UnitVisitor { - - /** - * Soldier Visitor method. - * @param soldier Soldier to be visited - */ - @Override - public void visit(Soldier soldier) { - // Do nothing - } - - /** - * Sergeant Visitor method. - * @param sergeant Sergeant to be visited - */ - @Override - public void visit(Sergeant sergeant) { - // Do nothing - } - - /** - * Commander Visitor method. - * @param commander Commander to be visited - */ - @Override - public void visit(Commander commander) { - LOGGER.info("Good to see you {}", commander); - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +import lombok.extern.slf4j.Slf4j; + +/** CommanderVisitor. */ +@Slf4j +public class CommanderVisitor implements UnitVisitor { + + /** + * Soldier Visitor method. + * + * @param soldier Soldier to be visited + */ + @Override + public void visit(Soldier soldier) { + // Do nothing + } + + /** + * Sergeant Visitor method. + * + * @param sergeant Sergeant to be visited + */ + @Override + public void visit(Sergeant sergeant) { + // Do nothing + } + + /** + * Commander Visitor method. + * + * @param commander Commander to be visited + */ + @Override + public void visit(Commander commander) { + LOGGER.info("Good to see you {}", commander); + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/Sergeant.java b/visitor/src/main/java/com/iluwatar/visitor/Sergeant.java index ed9fc98b5333..11ef218b22bd 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Sergeant.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Sergeant.java @@ -1,50 +1,49 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -/** - * Sergeant. - */ -public class Sergeant extends Unit { - - public Sergeant(Unit... children) { - super(children); - } - - /** - * Accept a Visitor. - * @param visitor UnitVisitor to be accepted - */ - @Override - public void accept(UnitVisitor visitor) { - visitor.visit(this); - super.accept(visitor); - } - - @Override - public String toString() { - return "sergeant"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +/** Sergeant. */ +public class Sergeant extends Unit { + + public Sergeant(Unit... children) { + super(children); + } + + /** + * Accept a Visitor. + * + * @param visitor UnitVisitor to be accepted + */ + @Override + public void accept(UnitVisitor visitor) { + visitor.visit(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "sergeant"; + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/SergeantVisitor.java b/visitor/src/main/java/com/iluwatar/visitor/SergeantVisitor.java index 3377806b94a6..09f4fd4bb1e1 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/SergeantVisitor.java +++ b/visitor/src/main/java/com/iluwatar/visitor/SergeantVisitor.java @@ -1,61 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -import lombok.extern.slf4j.Slf4j; - -/** - * SergeantVisitor. - */ -@Slf4j -public class SergeantVisitor implements UnitVisitor { - - /** - * Soldier Visitor method. - * @param soldier Soldier to be visited - */ - @Override - public void visit(Soldier soldier) { - // Do nothing - } - - /** - * Sergeant Visitor method. - * @param sergeant Sergeant to be visited - */ - @Override - public void visit(Sergeant sergeant) { - LOGGER.info("Hello {}", sergeant); - } - - /** - * Commander Visitor method. - * @param commander Commander to be visited - */ - @Override - public void visit(Commander commander) { - // Do nothing - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +import lombok.extern.slf4j.Slf4j; + +/** SergeantVisitor. */ +@Slf4j +public class SergeantVisitor implements UnitVisitor { + + /** + * Soldier Visitor method. + * + * @param soldier Soldier to be visited + */ + @Override + public void visit(Soldier soldier) { + // Do nothing + } + + /** + * Sergeant Visitor method. + * + * @param sergeant Sergeant to be visited + */ + @Override + public void visit(Sergeant sergeant) { + LOGGER.info("Hello {}", sergeant); + } + + /** + * Commander Visitor method. + * + * @param commander Commander to be visited + */ + @Override + public void visit(Commander commander) { + // Do nothing + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/Soldier.java b/visitor/src/main/java/com/iluwatar/visitor/Soldier.java index 30398374e3c1..565bcbdef535 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Soldier.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Soldier.java @@ -1,50 +1,49 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -/** - * Soldier. - */ -public class Soldier extends Unit { - - public Soldier(Unit... children) { - super(children); - } - - /** - * Accept a Visitor. - * @param visitor UnitVisitor to be accepted - */ - @Override - public void accept(UnitVisitor visitor) { - visitor.visit(this); - super.accept(visitor); - } - - @Override - public String toString() { - return "soldier"; - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +/** Soldier. */ +public class Soldier extends Unit { + + public Soldier(Unit... children) { + super(children); + } + + /** + * Accept a Visitor. + * + * @param visitor UnitVisitor to be accepted + */ + @Override + public void accept(UnitVisitor visitor) { + visitor.visit(this); + super.accept(visitor); + } + + @Override + public String toString() { + return "soldier"; + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/SoldierVisitor.java b/visitor/src/main/java/com/iluwatar/visitor/SoldierVisitor.java index 6827c82d5268..eb722ec861c2 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/SoldierVisitor.java +++ b/visitor/src/main/java/com/iluwatar/visitor/SoldierVisitor.java @@ -1,61 +1,62 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -import lombok.extern.slf4j.Slf4j; - -/** - * SoldierVisitor. - */ -@Slf4j -public class SoldierVisitor implements UnitVisitor { - - /** - * Soldier Visitor method. - * @param soldier Soldier to be visited - */ - @Override - public void visit(Soldier soldier) { - LOGGER.info("Greetings {}", soldier); - } - - /** - * Sergeant Visitor method. - * @param sergeant Sergeant to be visited - */ - @Override - public void visit(Sergeant sergeant) { - // Do nothing - } - - /** - * Commander Visitor method. - * @param commander Commander to be visited - */ - @Override - public void visit(Commander commander) { - // Do nothing - } -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +import lombok.extern.slf4j.Slf4j; + +/** SoldierVisitor. */ +@Slf4j +public class SoldierVisitor implements UnitVisitor { + + /** + * Soldier Visitor method. + * + * @param soldier Soldier to be visited + */ + @Override + public void visit(Soldier soldier) { + LOGGER.info("Greetings {}", soldier); + } + + /** + * Sergeant Visitor method. + * + * @param sergeant Sergeant to be visited + */ + @Override + public void visit(Sergeant sergeant) { + // Do nothing + } + + /** + * Commander Visitor method. + * + * @param commander Commander to be visited + */ + @Override + public void visit(Commander commander) { + // Do nothing + } +} diff --git a/visitor/src/main/java/com/iluwatar/visitor/Unit.java b/visitor/src/main/java/com/iluwatar/visitor/Unit.java index cd5fc0b35e3e..9bf3938da391 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/Unit.java +++ b/visitor/src/main/java/com/iluwatar/visitor/Unit.java @@ -26,9 +26,7 @@ import java.util.Arrays; -/** - * Interface for the nodes in hierarchy. - */ +/** Interface for the nodes in hierarchy. */ public abstract class Unit { private final Unit[] children; @@ -37,9 +35,7 @@ public Unit(Unit... children) { this.children = children; } - /** - * Accept visitor. - */ + /** Accept visitor. */ public void accept(UnitVisitor visitor) { Arrays.stream(children).forEach(child -> child.accept(visitor)); } diff --git a/visitor/src/main/java/com/iluwatar/visitor/UnitVisitor.java b/visitor/src/main/java/com/iluwatar/visitor/UnitVisitor.java index af60902289fc..c2f93d839b78 100644 --- a/visitor/src/main/java/com/iluwatar/visitor/UnitVisitor.java +++ b/visitor/src/main/java/com/iluwatar/visitor/UnitVisitor.java @@ -1,38 +1,35 @@ -/* - * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). - * - * The MIT License - * Copyright © 2014-2022 Ilkka Seppälä - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package com.iluwatar.visitor; - -/** - * Visitor interface. - */ -public interface UnitVisitor { - - void visit(Soldier soldier); - - void visit(Sergeant sergeant); - - void visit(Commander commander); - -} +/* + * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt). + * + * The MIT License + * Copyright © 2014-2022 Ilkka Seppälä + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package com.iluwatar.visitor; + +/** Visitor interface. */ +public interface UnitVisitor { + + void visit(Soldier soldier); + + void visit(Sergeant sergeant); + + void visit(Commander commander); +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/AppTest.java b/visitor/src/test/java/com/iluwatar/visitor/AppTest.java index 9fecce713a43..642b6b48972c 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/AppTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/AppTest.java @@ -24,17 +24,15 @@ */ package com.iluwatar.visitor; -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 { @Test void shouldExecuteWithoutException() { - assertDoesNotThrow(() -> App.main(new String[]{})); + assertDoesNotThrow(() -> App.main(new String[] {})); } } diff --git a/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java b/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java index 94301c7302dc..2961fc54e688 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/CommanderTest.java @@ -27,15 +27,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -/** - * CommanderTest - * - */ +/** CommanderTest */ class CommanderTest extends UnitTest { - /** - * Create a new test instance for the given {@link Commander}. - */ + /** Create a new test instance for the given {@link Commander}. */ public CommanderTest() { super(Commander::new); } @@ -44,5 +39,4 @@ public CommanderTest() { void verifyVisit(Commander unit, UnitVisitor mockedVisitor) { verify(mockedVisitor).visit(eq(unit)); } - -} \ No newline at end of file +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java index b3ff8da5b05b..96a1cd49d2a5 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/CommanderVisitorTest.java @@ -24,24 +24,11 @@ */ package com.iluwatar.visitor; -import java.util.Optional; - -/** - * CommanderVisitorTest - * - */ +/** CommanderVisitorTest */ class CommanderVisitorTest extends VisitorTest { - /** - * Create a new test instance for the given visitor. - */ + /** Create a new test instance for the given visitor. */ public CommanderVisitorTest() { - super( - new CommanderVisitor(), - ("Good to see you commander"), - null, - null - ); + super(new CommanderVisitor(), ("Good to see you commander"), null, null); } - } diff --git a/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java b/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java index 0d57cc5977de..cf3af93bb1c9 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/SergeantTest.java @@ -27,15 +27,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -/** - * SergeantTest - * - */ +/** SergeantTest */ class SergeantTest extends UnitTest { - /** - * Create a new test instance for the given {@link Sergeant}. - */ + /** Create a new test instance for the given {@link Sergeant}. */ public SergeantTest() { super(Sergeant::new); } @@ -44,5 +39,4 @@ public SergeantTest() { void verifyVisit(Sergeant unit, UnitVisitor mockedVisitor) { verify(mockedVisitor).visit(eq(unit)); } - -} \ No newline at end of file +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java index b35473e39302..a0468f6cbceb 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/SergeantVisitorTest.java @@ -24,24 +24,11 @@ */ package com.iluwatar.visitor; -import java.util.Optional; - -/** - * SergeantVisitorTest - * - */ +/** SergeantVisitorTest */ class SergeantVisitorTest extends VisitorTest { - /** - * Create a new test instance for the given visitor. - */ + /** Create a new test instance for the given visitor. */ public SergeantVisitorTest() { - super( - new SergeantVisitor(), - null, - ("Hello sergeant"), - null - ); + super(new SergeantVisitor(), null, ("Hello sergeant"), null); } - } diff --git a/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java b/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java index 82e71e0d7042..71226a394a73 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/SoldierTest.java @@ -27,15 +27,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; -/** - * SoldierTest - * - */ +/** SoldierTest */ class SoldierTest extends UnitTest { - /** - * Create a new test instance for the given {@link Soldier}. - */ + /** Create a new test instance for the given {@link Soldier}. */ public SoldierTest() { super(Soldier::new); } @@ -44,5 +39,4 @@ public SoldierTest() { void verifyVisit(Soldier unit, UnitVisitor mockedVisitor) { verify(mockedVisitor).visit(eq(unit)); } - -} \ No newline at end of file +} diff --git a/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java index 11efb124c7e8..6844257dcda9 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/SoldierVisitorTest.java @@ -24,24 +24,11 @@ */ package com.iluwatar.visitor; -import java.util.Optional; - -/** - * SoldierVisitorTest - * - */ +/** SoldierVisitorTest */ class SoldierVisitorTest extends VisitorTest { - /** - * Create a new test instance for the given visitor. - */ + /** Create a new test instance for the given visitor. */ public SoldierVisitorTest() { - super( - new SoldierVisitor(), - null, - null, - ("Greetings soldier") - ); + super(new SoldierVisitor(), null, null, ("Greetings soldier")); } - } diff --git a/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java b/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java index 6e0b34a3c4d0..57bfa4350131 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/UnitTest.java @@ -40,9 +40,7 @@ */ public abstract class UnitTest { - /** - * Factory to create new instances of the tested unit. - */ + /** Factory to create new instances of the tested unit. */ private final Function factory; /** @@ -73,9 +71,8 @@ void testAccept() { /** * Verify if the correct visit method is called on the mock, depending on the tested instance. * - * @param unit The tested unit instance + * @param unit The tested unit instance * @param mockedVisitor The mocked {@link UnitVisitor} who should have gotten a visit by the unit */ abstract void verifyVisit(final U unit, final UnitVisitor mockedVisitor); - } diff --git a/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java index b3dacb2912d3..374f1fef6246 100644 --- a/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java +++ b/visitor/src/test/java/com/iluwatar/visitor/VisitorTest.java @@ -55,39 +55,30 @@ void tearDown() { appender.stop(); } - /** - * The tested visitor instance. - */ + /** The tested visitor instance. */ private final V visitor; - /** - * The expected response when being visited by a commander. - */ + /** The expected response when being visited by a commander. */ private final String commanderResponse; - /** - * The expected response when being visited by a sergeant. - */ + /** The expected response when being visited by a sergeant. */ private final String sergeantResponse; - /** - * The expected response when being visited by a soldier. - */ + /** The expected response when being visited by a soldier. */ private final String soldierResponse; /** * Create a new test instance for the given visitor. * * @param commanderResponse The expected response when being visited by a commander - * @param sergeantResponse The expected response when being visited by a sergeant - * @param soldierResponse The expected response when being visited by a soldier + * @param sergeantResponse The expected response when being visited by a sergeant + * @param soldierResponse The expected response when being visited by a soldier */ public VisitorTest( final V visitor, final String commanderResponse, final String sergeantResponse, - final String soldierResponse - ) { + final String soldierResponse) { this.visitor = visitor; this.commanderResponse = commanderResponse; this.sergeantResponse = sergeantResponse;