diff --git a/.gitattributes b/.gitattributes
index 0d7972b32..ed55853a3 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,7 +4,14 @@
# Force the following filetypes to have unix eols, so Windows does not break them
*.* text eol=lf
+# Force the following filetypes to have Windows eols, so unix does not break them
+*.bat text eol=crlf
+
# Force images/fonts to be handled as binaries
*.jpg binary
*.jpeg binary
*.png binary
+
+# Force JARs to be handled as binaries
+*.jar binary
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ed8f4a432..d157b4521 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -1,9 +1,9 @@
version: 2
updates:
-
- # Keep dependencies for GitHub Actions up-to-date
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
- interval: 'daily'
+ interval: 'monthly'
+ labels:
+ - 'x:size/tiny'
diff --git a/.github/org-wide-files-config.toml b/.github/org-wide-files-config.toml
new file mode 100644
index 000000000..577840e35
--- /dev/null
+++ b/.github/org-wide-files-config.toml
@@ -0,0 +1,2 @@
+[configlet]
+fmt = true
diff --git a/.github/workflows/check-deprecated-exercises.yml b/.github/workflows/check-deprecated-exercises.yml
new file mode 100644
index 000000000..8defbd0cf
--- /dev/null
+++ b/.github/workflows/check-deprecated-exercises.yml
@@ -0,0 +1,21 @@
+name: Deprecated
+
+on:
+ pull_request:
+ paths:
+ - "exercises/**"
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ test-deprecated:
+ name: Check for deprecated exercises
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+ with:
+ fetch-depth: 0
+ - name: Test deprecated exercises using test-deprecated-exercises
+ run: bin/test-deprecated-exercises
diff --git a/.github/workflows/configlet.yml b/.github/workflows/configlet.yml
index 47eb87543..c730b5820 100644
--- a/.github/workflows/configlet.yml
+++ b/.github/workflows/configlet.yml
@@ -13,3 +13,5 @@ permissions:
jobs:
configlet:
uses: exercism/github-actions/.github/workflows/configlet.yml@main
+ with:
+ fmt: true
diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
deleted file mode 100644
index 9c620190a..000000000
--- a/.github/workflows/gradle.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-# This workflow will build a Java project with Gradle
-# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
-
-name: Java CI with Gradle
-
-on: [push, pull_request, workflow_dispatch]
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- - name: Set up JDK 1.17
- uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc
- with:
- java-version: 17
- distribution: 'temurin'
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
- - name: Compile and checkstyle with Gradle
- run: cd exercises && ../gradlew check compileStarterSourceJava --parallel --continue --info
-
- test:
-
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- - name: Set up JDK 1.17
- uses: actions/setup-java@de1bb2b0c5634f0fc4438d7aa9944e68f9bf86cc
- with:
- java-version: 17
- distribution: 'temurin'
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
- - name: Journey test
- run: bin/journey-test.sh
diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml
new file mode 100644
index 000000000..be8b3684a
--- /dev/null
+++ b/.github/workflows/java.yml
@@ -0,0 +1,98 @@
+name: Java
+
+on:
+ pull_request:
+ paths:
+ - "**/*.java"
+ - "**/*.gradle"
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: Check if tests compile cleanly with starter sources
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+ - name: Set up JDK 21
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654
+ with:
+ java-version: 21
+ distribution: "temurin"
+ - name: Check if tests compile cleanly with starter sources
+ run: ./gradlew compileStarterTestJava --continue
+ working-directory: exercises
+
+ lint:
+ name: Lint Java files using Checkstyle
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+ - name: Set up JDK 21
+ uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654
+ with:
+ java-version: 21
+ distribution: "temurin"
+ - name: Run checkstyle
+ run: ./gradlew check --exclude-task test --continue
+ working-directory: exercises
+
+ test-all:
+ name: Test all exercises using java-test-runner
+ if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+ - name: Test all exercises using java-test-runner
+ run: bin/test-with-test-runner
+ - name: Print summary
+ run: |
+ if [ -f exercises/build/summary.txt ]; then
+ echo "===== TEST SUMMARY ====="
+ cat exercises/build/summary.txt
+ echo "========================"
+ else
+ echo "===== ALL TESTS PASSED ====="
+ echo "No summary file was generated."
+ echo "============================="
+ fi
+ if: always()
+ - name: Archive test results
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
+ with:
+ name: test-results
+ path: exercises/**/build/results.json
+ if: failure()
+
+ test-changed:
+ name: Test changed exercises using gradlew
+ if: github.event_name == 'pull_request'
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+ with:
+ fetch-depth: 0
+ - name: Test changed exercises using java-test-runner
+ run: bin/test-changed-exercise
+ - name: Print summary
+ run: |
+ if [ -f exercises/build/summary.txt ]; then
+ echo "===== TEST SUMMARY ====="
+ cat exercises/build/summary.txt
+ echo "========================"
+ else
+ echo "===== ALL TESTS PASSED ====="
+ echo "No summary file was generated."
+ echo "============================="
+ fi
+ if: always()
+ - name: Archive test results
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
+ with:
+ name: test-results
+ path: |
+ exercises/**/build/results.txt
+ exercises/**/build/results.json
+ if: failure()
diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml
new file mode 100644
index 000000000..a120524cd
--- /dev/null
+++ b/.github/workflows/markdown.yml
@@ -0,0 +1,22 @@
+name: Markdown
+
+on:
+ pull_request:
+ paths:
+ - "**/*.md"
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ lint:
+ name: Lint Markdown files
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+ - name: Lint markdown
+ uses: DavidAnson/markdownlint-cli2-action@07035fd053f7be764496c0f8d8f9f41f98305101
diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml
new file mode 100644
index 000000000..812e91296
--- /dev/null
+++ b/.github/workflows/no-important-files-changed.yml
@@ -0,0 +1,23 @@
+name: No important files changed
+
+on:
+ pull_request_target:
+ types: [opened]
+ branches: [main]
+ paths:
+ - "exercises/concept/**"
+ - "exercises/practice/**"
+ - "!exercises/*/*/.approaches/**"
+ - "!exercises/*/*/.articles/**"
+ - "!exercises/*/*/.docs/**"
+ - "!exercises/*/*/.meta/**"
+
+permissions:
+ pull-requests: write
+
+jobs:
+ check:
+ uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main
+ with:
+ repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }}
+ ref: ${{ github.head_ref }}
diff --git a/.github/workflows/ping-cross-track-maintainers-team.yml b/.github/workflows/ping-cross-track-maintainers-team.yml
new file mode 100644
index 000000000..b6ec9c566
--- /dev/null
+++ b/.github/workflows/ping-cross-track-maintainers-team.yml
@@ -0,0 +1,16 @@
+name: Ping cross-track maintainers team
+
+on:
+ pull_request_target:
+ types:
+ - opened
+
+permissions:
+ pull-requests: write
+
+jobs:
+ ping:
+ if: github.repository_owner == 'exercism' # Stops this job from running on forks
+ uses: exercism/github-actions/.github/workflows/ping-cross-track-maintainers-team.yml@main
+ secrets:
+ github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }}
diff --git a/.github/workflows/run-configlet-sync.yml b/.github/workflows/run-configlet-sync.yml
new file mode 100644
index 000000000..b49cbffe8
--- /dev/null
+++ b/.github/workflows/run-configlet-sync.yml
@@ -0,0 +1,10 @@
+name: Run Configlet Sync
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 0 15 * *'
+
+jobs:
+ call-gha-workflow:
+ uses: exercism/github-actions/.github/workflows/configlet-sync.yml@main
diff --git a/.gitignore b/.gitignore
index f369fe77c..bb1cdb26f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,14 +16,8 @@ exercises/*/bin
exercises/*/.classpath
exercises/*/.project
exercises/*/.settings/
-exercises/gradle/
-exercises/gradlew
-exercises/gradlew.bat
-_template/bin
-_template/.classpath
-_template/.project
-_template/.settings/
-_template/gradle/
-_template/gradlew
-_template/gradlew.bat
-*.class
\ No newline at end of file
+resources/exercise-template/bin
+resources/exercise-template/.classpath
+resources/exercise-template/.project
+resources/exercise-template/.settings/
+*.class
diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc
new file mode 100644
index 000000000..d08e83a61
--- /dev/null
+++ b/.markdownlint-cli2.jsonc
@@ -0,0 +1,21 @@
+{
+ "globs": [
+ "concepts/**/*.md",
+ "docs/**/*.md",
+ "exercises/**/*.md",
+ "reference/**/*.md",
+ "*.md"
+ ],
+ "ignores": [
+ // Deprecated exercises should never be updated
+ "exercises/concept/blackjack/**/*.md",
+ "exercises/practice/accumulate/**/*.md",
+ "exercises/practice/beer-song/**/*.md",
+ "exercises/practice/binary/**/*.md",
+ "exercises/practice/diffie-hellman/**/*.md",
+ "exercises/practice/hexadecimal/**/*.md",
+ "exercises/practice/octal/**/*.md",
+ "exercises/practice/strain/**/*.md",
+ "exercises/practice/trinary/**/*.md"
+ ]
+}
diff --git a/.markdownlint.jsonc b/.markdownlint.jsonc
new file mode 100644
index 000000000..3d0e30020
--- /dev/null
+++ b/.markdownlint.jsonc
@@ -0,0 +1,14 @@
+// Configuration is based on the Exercism Markdown specification, see:
+// https://exercism.org/docs/building/markdown/markdown
+//
+// For information on writing markdownlint configuration see:
+// https://github.com/DavidAnson/markdownlint/blob/main/README.md#optionsconfig
+{
+ "MD004": {
+ "style": "dash" // Prefer hyphens for unordered lists
+ },
+ "MD013": false, // Exercism tends to break lines at sentence ends rather than at a column limit
+ "MD024": false, // The format for instructions.md requires repeated headings, e.g. "Task"
+ "MD033": false, // Inline HTML is allowed when used sparingly
+ "MD048": false // We want three backticks for codeblocks, but four tildes for special blocks.
+}
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index df8e36761..3f7813de1 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -90,4 +90,4 @@ This policy was initially adopted from the Front-end London Slack community and
A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md).
_This policy is a "living" document, and subject to refinement and expansion in the future.
-This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._
+This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Discord, Forum, Twitter, email) and any other Exercism entity or event._
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0af61c007..56151b2c9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,301 +3,229 @@
## Table of Contents
- * [Overview](#overview)
- * [Before Making Your Pull Request](#before-making-your-pull-request)
- * [Contributing With Minimal Setup](#contributing-with-minimal-setup)
- * [Contributing using Intellij IDEA](#contributing-using-intellij-idea)
- * [Getting Familiar With the Codebase](#getting-familiar-with-the-codebase)
- * [The `exercises` Module](#the-exercises-module)
- * [The Problem Submodules](#the-problem-submodules)
- * [Advanced: Complete Local Setup](#advanced--complete-local-setup)
- * [Prerequisites](#prerequisites)
- * [Debian Linux](#debian-linux)
- * [macOS](#macos)
- * [Adding a New Exercise](#adding-a-new-exercise)
- * [Updating the READMEs](#updating-the-readmes)
- * [Checking tests are up to date](#checking-tests-are-up-to-date)
- * [Checking tests are up to date and submit new issues](#checking-tests-are-up-to-date-and-submit-new-issues)
- * [Checking exercises are implemented and submit new issues](#checking-exercises-are-implemented-and-submit-new-issues)
-
+
+- [Contributing](#contributing)
+ - [Table of Contents](#table-of-contents)
+ - [Overview](#overview)
+ - [Before Making Your Pull Request](#before-making-your-pull-request)
+ - [Steps to your next contribution](#steps-to-your-next-contribution)
+ - [Install tooling](#install-tooling)
+ - [Create a new branch for your work](#create-a-new-branch-for-your-work)
+ - [Write some code](#write-some-code)
+ - [Check whether the reference implementation passes the tests](#check-whether-the-reference-implementation-passes-the-tests)
+ - [Check whether the reference implementation passes the Checkstyle validations](#check-whether-the-reference-implementation-passes-the-checkstyle-validations)
+ - [Check whether the starter implementation is able to compile with the tests](#check-whether-the-starter-implementation-is-able-to-compile-with-the-tests)
+ - [Open a Pull Request](#open-a-pull-request)
+ - [Contributing using IntelliJ IDEA](#contributing-using-intellij-idea)
+ - [Clone the repository](#clone-the-repository)
+ - [Importing the Gradle project](#importing-the-gradle-project)
+ - [Creating a new branch](#creating-a-new-branch)
+ - [Testing your changes](#testing-your-changes)
+ - [Committing your changes](#committing-your-changes)
+ - [Getting Familiar With the Codebase](#getting-familiar-with-the-codebase)
+ - [The `exercises` Module](#the-exercises-module)
+ - [The Problem Submodules](#the-problem-submodules)
+ - [Contributing to Concept Exercises](#contributing-to-concept-exercises)
+ - [Contributing to Practice Exercises](#contributing-to-practice-exercises)
## Overview
-This guide covers contributing to the Java track. If you are new to the exercism Java track, this guide is for you.
+This guide covers contributing to the Java track. If you are new to the Exercism Java track, this guide is for you.
If, at any point, you're having any trouble, pop in the [Exercism forum][forum] for help.
-For general guidelines about contributing to Exercism see the [Exercism contributing guide](https://exercism.org/docs/building).
+For general guidelines about contributing to Exercism see the [Exercism contributing guide][docs-building] and [Contributing via GitHub][docs-building-github].
## Before Making Your Pull Request
Hi! Thanks for contributing to the Exercism Java track!
-Before opening your pull request, please review the [track policies](https://github.com/exercism/java/blob/main/POLICIES.md) and make sure your changes comply with them all.
+Before opening your pull request, please review the [track policies](POLICIES.md) and make sure your changes comply with them all.
This helps us focus our review time on the more important aspects of your contributions.
-Also, please only address one issue per pull request and reference the issue in your pull request. This makes it easier for us to review it, and it means that if we request changes to the fix for one issue, it won't prevent to a fix for another issue being merged.
+Also, please only address one issue per pull request and reference the issue in your pull request.
+This makes it easier for us to review it, and it means that if we request changes to the fix for one issue, it won't prevent to a fix for another issue being merged.
It's perfectly fine to have more than one pull request open at a time.
-In that case it's important to keep the work for each pull request on a separate [branch](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) to prevent unrelated commits being added to your pull request. This is good practice to do always, even if you only have one pull request open.
-
-One last thing to note before you get started. When you fork the repository, and you want to [sync your fork](https://help.github.com/articles/syncing-a-fork/), you can perform a [`git rebase`](https://git-scm.com/docs/git-rebase). This is preferred over merging the changes because merging leads to a dirty commit history whereas performing a rebase adds in those changes without making extra commit messages. However, this is only preferred, so don't worry about it too much.
+In that case it's important to keep the work for each pull request on a separate [branch][git-branching] to prevent unrelated commits being added to your pull request.
+This is good practice to do always, even if you only have one pull request open.
-## Contributing With Minimal Setup
+One last thing to note before you get started.
+When you fork the repository, and you want to [sync your fork][github-sync-fork], you can perform a [`git rebase`][git-rebase].
+This is preferred over merging the changes because merging leads to a dirty commit history whereas performing a rebase adds in those changes without making extra commit messages.
+However, this is only preferred, so don't worry about it too much.
-First things first: by contributing to Exercism, you are making this learning tool that much better and improving our industry as a whole... thank you!!!
+## Steps to your next contribution
-To submit a fix for an existing exercise or port an exercise to Java with the least amount of setup:
+First things first: by contributing to Exercism, you are making this learning tool that much better and improving our industry as a whole, so thank you!
-1. **Ensure you have the basic Java tooling installed:** JDK 1.11+, an editor and Gradle 2.x.
+To submit a fix for an existing exercise or port an exercise to Java with the least amount of setup, follow these steps.
- (see [exercism.io: Installing Java](https://exercism.org/docs/tracks/java/installation))
-- **Set up a branch on a fork of [exercism/java](https://github.com/exercism/java) on your computer.**
+### Install tooling
- See [GitHub Help: Forking](https://help.github.com/articles/fork-a-repo/). Use those instructions (in conjunction with the [Contributing via GitHub](https://github.com/exercism/docs/tree/main/building/github#contributing-via-github)) to:
- * "fork" a repository on GitHub;
- - install `git`;
- - "clone" a copy of your fork;
- - configure an "upstream remote" (in this case, `exercism/java`);
- - create a branch to house your work
-- **Write the codes.** Do your work on that branch you just created.
+Make sure you have the latest Java tooling installed on your computer, see [exercism.org: Installing Java][docs-java-installation].
- The [Getting Familiar With the Codebase](#getting-familiar-with-the-codebase) section, below, is an orientation.
-- **Commit, push and create a pull request.**
+Also make sure you have `git` installed on your computer.
- Something like:
- ```
- $ git add .
- $ git commit -m "(An intention-revealing commit message)"
- $ git push
- ```
- The GitHub doc has a section on [pull requests](https://exercism.org/docs/building/github/contributors-pull-request-guide) that provides practical advices on how to create them.
-- **Verify that your work passes all tests.** When you create a pull request (PR), GitHub triggers a build on Travis CI. Your PR will not be merged unless those tests pass.
-- **Check the style of your code**. Running `gradle check` from the root folder of the exercise, the checkstyle plugin will show you every style violation of your code
+### Create a new branch for your work
-## Contributing using Intellij IDEA
+Create a fork of the [exercism/java][track-repo] repository in your GitHub account, see [GitHub Help: Forking][github-forking].
- Intellij IDEA is one of the more popular IDEs when working with Java, and it includes several tools to help simplify the process. The following steps outline how to import the git repository, make changes, and push
- them back to your fork (this is assuming you have already forked the repo...if you haven't, see the link about [forking](https://help.github.com/articles/fork-a-repo/)).
-
-- **Open the IDE and import the project** From the startup menu, select "Check out from Version Control". This will open a dialog where you can enter the URL of the git repository and specify the directory that you would
-like to clone the repo into.
+Clone the fork you created to your computer using `git`, and create a new branch from the `main` branch to start working on your contribution.
-
+### Write some code
-- Select "Import Project from External Model" and click the "Gradle" radio
+The [Getting Familiar With the Codebase](#getting-familiar-with-the-codebase) section will help you get familiar with the project.
-
+After making changes to one or more exercises, make sure that they pass all validations. Run the following commands from the root of the exercise directory.
-- Set the Gradle properties per the screenshot below. Ensure that the "exercises" folder is selected as the root of the project
+#### Check whether the reference implementation passes the tests
-
+```sh
+./gradlew test
+```
-- **Add the `java` folder as a module** Open the project settings and view the modules. Click the `+` button, select "Import Module". Select the `java` directory and accept the default values.
+#### Check whether the reference implementation passes the Checkstyle validations
-
+```sh
+./gradlew check
+```
-- **Create a feature branch** The git tools in IDEA are located in the VCS menu. To create a new branch, select VCS > Git > Branches and then click "New Branch". Give the branch a meaningful name and create.
+#### Check whether the starter implementation is able to compile with the tests
-
-
+```sh
+./gradlew compileStarterTestJava
+```
-- Make all of your changes, following the instructions in this guide.
+### Open a Pull Request
-- **Testing your changes** Each exercise will have gradle tasks that can be executed from the IDE. To test changes within an exercise, find the gradle task for that folder in the "Gradle" toolbar on the right,
-open the Tasks > Verification folder and double click `test`
+When you finished your changes and checked that all validations have passed, it's time to commit and push them to your fork:
- 
-
- - **Commit/Merge changes** Once all the changes have been made, you can look at the diffs and commit from the "Commit File" window, which can be reached by selecting VCS > Git > Commit File from the top menu.
- If all the changes are acceptable, checkmark all the files that are to be committed, enter a meaningful commit message, and then click "Commit and Push".
-
- 
-
- - Follow the instructions regarding creating a pull request into the upstream repo.
-
- **NOTE:** Git and gradle commands can still be run in the command line when using and IDE. The steps outlining how to perform using IDE tools are for convenience only.
-
-## Getting Familiar With the Codebase
+```sh
+git add .
+```
-There are two objectives to the design of this build:
+```sh
+git commit -m "(An intention-revealing commit message)"
+```
-1. when a problem is built from within the `exercism/java` repo (i.e. when you, the contributor, are developing the exercise), the tests run against the reference solution;
-2. when a problem is built outside the `exercism/java` repo (when a participant is solving the exercise), the tests run against the "main" code.
+```sh
+git push -u origin your-branch-name
+```
-This repo is a multi-project gradle build.
+Then, open a Pull Request on the [exercism/java][track-repo] repository.
+Check out the [Contributors Pull Request Guide][docs-building-github-prs] for some guidelines on what we expect in a Pull Request.
-### The `exercises` Module
+After opening a Pull Request, one of our maintainers will try to review it as soon as they are available.
+They will also trigger the GitHub Actions workflows which will build and test the project.
+Your Pull Request will not be merged unless those workflows pass.
-This is the top-level module, contained in the `exercises` directory. It is a container for the problem submodules.
-
- * its `build.gradle` points the "main" sourceset to the reference solution.
- * its `settings.gradle` names each of the subprojects, one for each problem in the set.
-
-### The Problem Submodules
-
-The `exercises` subdirectory contains all the problem submodules.
-Each problem/submodule is a subdirectory of the same name as its slug.
-
- * its `build.gradle` names dependencies required to work that problem.
- * its `README.md` describes the exercise.
-
-Each problem/submodule has three source sets:
+## Contributing using IntelliJ IDEA
-* `src/test/java/` — a test suite defining the edges of the problem
-* `.meta/src/reference/java/` — a reference solution that passes all the tests
-* `src/main/java/` — starter source file(s).
+IntelliJ IDEA is one of the more popular IDEs when working with Java, and it includes several tools to help simplify the process.
+The following steps outline how to import the git repository, make changes, and push them back to your [fork][github-forking].
-----
+### Clone the repository
-## Advanced: Complete Local Setup
+Open the IDE, and from the startup menu select "Check out from Version Control".
+This will open a dialog where you can enter the URL of your fork repository and specify the directory that you would like to clone the repo into.
-### Prerequisites
-Before you proceed, please ensure that you have `jq` (library that parses JSON) & `ruby` installed on your machine.
+
-#### Debian Linux
-`sudo apt-get install jq ruby-full`
+### Importing the Gradle project
-#### macOS
-`brew install jq ruby`
+Select "Import Project from External Model" and click the "Gradle" radio.
-If you are going to make significant contribution(s) to the track, you might find it handy to have a complete local installation of exercism on your computer. This way, you can run the full suite of tests without having to create/update a PR.
+
-The easiest way to achieve this is simply use the `bin/journey-test.sh` script. However, you may want to perform other tests, depending on what you are doing. You can do so by duplicating the setup performed by the `bin/journey-test.sh` script.
+Set the Gradle properties per the screenshot below. Ensure that the "exercises" folder is selected as the root of the project
-## Adding a New Exercise
+
-The easiest way to add a new exercise to the Java track is to port an exercise from another track.
-That means that you take an exercise that has already been implemented in another language, and you implement it in this track.
+**Add the `java` folder as a module**.
+Open the project settings and view the modules.
+Click the `+` button, select "Import Module".
+Select the `java` directory and accept the default values.
-To add a completely new exercise you need to open a pull request to the [problem specifications repository](https://github.com/exercism/problem-specifications/tree/main/exercises).
-Any completely new exercise needs to be added and accepted there before it can be added to the Java track.
+
-Before porting an exercise to the Java track, please review the [concept exercises guide](https://exercism.org/docs/building/tracks/concept-exercises) and/or the [practice exercise guide](https://exercism.org/docs/building/tracks/practice-exercises).
+### Creating a new branch
-Please make sure no one else has a pull request open to implement your chosen exercise before you start.
+The git tools in IDEA are located in the VCS menu.
+To create a new branch, select VCS > Git > Branches and then click "New Branch".
+Give the branch a meaningful name and create.
-It might also be a good idea to open a WIP pull request to make it clear to others that you are working on this exercise.
-This can just be a pull request with an empty commit that states which new exercise you're working on, with WIP (work in progress) in the title so that the maintainers know that it's not ready for review yet.
+
+
-The Java specific details you need to know about adding an exercise are:
+### Testing your changes
-* Please add an entry to the `exercises` array in `config.json`. You can find details about what should be in that entry [here](https://exercism.org/docs/building/tracks/config-json).
-You can also look at other entries in `config.json` as examples and try to mimic them.
+Each exercise will have gradle tasks that can be executed from the IDE.
+To test changes within an exercise, find the gradle task for that folder in the "Gradle" toolbar on the right, open the Tasks > Verification folder and double click `test`.
-* Please add an entry for your exercise to `settings.gradle`.
-This should just be `include 'exercise-name'`.
-This list is in alphabetical order so please add your exercise so that it maintains this order.
+
-* Please add an exercise submodule for your exercise.
-See [The Problem Submodules](#the-problem-submodules) section for what needs to be in this.
-See the [POLICIES doc](https://github.com/exercism/java/blob/main/POLICIES.md#starter-implementations) for an explanation of when you need to add a starter implementation.
-The `build.gradle` file can just be copied from any other exercise submodule.
-The `README.md` file can be generated using [configlet](https://github.com/exercism/configlet/releases).
-You can do this by:
+### Committing your changes
- 1. Download configlet and put it somewhere in your [PATH](https://en.wikipedia.org/wiki/PATH_(variable))
+Once all the changes have been made, you can look at the diffs and commit from the "Commit File" window, which can be reached by selecting VCS > Git > Commit File from the top menu.
+If all the changes are acceptable, checkmark all the files that are to be committed, enter a meaningful commit message, and then click "Commit and Push".
- 2. Clone [the problem-specifications repository](https://github.com/exercism/problem-specifications).
+
- 3. Run `configlet generate . --only name_of_new_exercise --spec-path path_to_problem_specifications` from the root of this repository.
+After pushing your changes, [Open a Pull Request](#open-a-pull-request) to contribute them to the Java track.
-* Check if there is canonical data for the exercise you're adding.
-This can be found at `https://github.com/exercism/problem-specifications/tree/master/exercises/EXERCISE-SLUG/canonical-data.json`.
-If there is canonical data for your exercise then you should follow this when making the tests.
-We aim to follow the canonical data as closely as possible in our tests to ensure thorough test coverage.
-If there is canonical data available you also need to create a file at `exercises/exercise-slug/.meta/version` specifying the canonical data version you have implemented (e.g. `1.0.0`).
-The canonical data version can be found at the top of the canonical data file for that exercise.
-See other exercises, e.g. [acronym](https://github.com/exercism/java/tree/main/exercises/practice/acronym/.meta), for an example `version` file.
+**NOTE:** Git and gradle commands can still be run in the command line when using and IDE.
+The steps outlining how to perform using IDE tools are for convenience only.
-* Make sure you've followed the [track policies](https://github.com/exercism/java/blob/main/POLICIES.md), especially the ones for exercise added/updated.
-
-Hopefully that should be enough information to help you port an exercise to the Java track.
-Feel free to open an issue or post in the [Building Exercism](https://forum.exercism.org/c/exercism/building-exercism/125) category of the [Exercism forum](https://forum.exercism.org/) if you have any questions, and we'll try and answer as soon as we can.
-
-## Updating the READMEs
-
-The `README.md` files are generated from the exercise descriptions in [problem specifications](https://github.com/exercism/problem-specifications/tree/main/exercises).
-They need to be regenerated regularly so that any changes to the descriptions in problem specifications propagate to our READMEs.
-This can be done using [configlet](https://github.com/exercism/configlet/releases):
-
- 1. Download configlet and put it somewhere in your [PATH](https://en.wikipedia.org/wiki/PATH_(variable))
-
- 2. Clone [the problem-specifications repository](https://github.com/exercism/problem-specifications).
-
- 3. Run `configlet generate . --spec-path path_to_problem_specifications` from the root of this repository.
-
-## Checking tests are up to date
-
-The tests for each exercise should follow the canonical data in [problem specifications](https://github.com/exercism/problem-specifications/tree/main/exercises) as closely as possible.
-The canonical data can change quite regularly, in which case the [canonical data version](https://github.com/exercism/problem-specifications#test-data-versioning) for that exercise will be updated.
+## Getting Familiar With the Codebase
-We keep track of which version of the canonical data each exercise implements in a version file, for example: https://github.com/exercism/java/blob/main/exercises/practice/two-fer/.meta/version.
-Not all exercises have canonical data in problem specifications.
-For those that don't we don't add a version file.
+There are two objectives to the design of this build:
-We have [a script](https://github.com/exercism/java/blob/main/scripts/canonical_data_check.sh) which can check if these version are up to date with the ones in problem specification.
-This script can be used to check if any version files, tests and reference implementations need updating.
+1. when a problem is built from within the `exercism/java` repo (i.e. when you, the contributor, are developing the exercise), the tests run against the reference solution;
+2. when a problem is built outside the `exercism/java` repo (when a participant is solving the exercise), the tests run against the "main" code.
-To run this script:
+This repo is a multi-project gradle build.
- 1. Clone [the problem-specifications repository](https://github.com/exercism/problem-specifications).
+### The `exercises` Module
- 2. Run `./scripts/canonical_data_check.sh -t . -s path_to_problem_specifications` from the root of this repository.
-
-## Checking tests are up to date and submit new issues
+This is the top-level module, contained in the `exercises` directory. It is a container for the problem submodules.
-There is [a script which allows you to submit new issues](https://github.com/exercism/java/blob/main/scripts/create_issues_versionchange_canonical.sh) to this repo with generic title, description and labels if a change in version was detected.
+- its `build.gradle` points the "main" sourceset to the reference solution.
+- its `settings.gradle` names each of the subprojects, one for each problem in the set.
-Example generic new issue:
-
+### The Problem Submodules
-Before you may submit a new issue, the script
- 1. Checks for differences between version numbers of each exercise (in comparison with the version number of the canonical data)
- 2. Checks whether an open issue exists for this exercise; if there is an open issue, you will have to check by yourself if the title of the open issue might be changed to include the new version number. Here, it is important to check whether someone is already working on the issue.
- 3. If a new issue may be opened for an exercise, the script will ask you if you want to submit the issue. Entering `y` will create the new issue.
+The `exercises` subdirectory contains all the problem submodules.
+Each problem/submodule is a subdirectory of the same name as its slug.
-To run this script:
+- its `build.gradle` names dependencies required to work that problem.
+- its `README.md` describes the exercise.
- 1. Clone [the problem-specifications repository](https://github.com/exercism/problem-specifications).
-
- 2. Create a file `.exercism-version-update-issue-script-settings.sh` in your home directory.
-
- 3. In this file, you have to put the following variables:
- - `TOKEN="your_token"`
- - `OWNER="exercism"`
- - `REPO="java"`
-
- For authentication, you need to create a personal token, see [this GitHub page](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) for more information.
+Each problem/submodule has three source sets:
- 4. Run `./scripts/create_issues_versionchange_canonical.sh -t . -s --spec-path path_to_problem_specifications` from the root of this repository and follow the directions.
-
- 5. If you submitted new issues, please check these submissions on the [issues page](https://github.com/exercism/java/issues).
+- `src/test/java/` — a test suite defining the edges of the problem
+- `.meta/src/reference/java/` — a reference solution that passes all the tests
+- `src/main/java/` — starter source file(s).
-## Checking exercises are implemented and submit new issues
+### Update/sync Gradle versions
-There is [a script](https://github.com/exercism/java/blob/main/scripts/create_issues_new_exercise.sh) which allows you to easily check if there are any exercism exercises which haven't been implemented in the Java track, and create issues for those exercises if there are any.
+Please read [How to Update Gradle](reference/how-to-update-gradle.md)
-Before you may submit a new issue, the script
- 1. Checks whether the exercise exists in the Java track (compared to exercism/problem-specifications)
- 2. Checks whether an open issue exists for this exercise concerning the implementation of the exercise;
- 3. If a new issue may be opened for an exercise, the script will ask you if you want to submit the issue. Entering `y` will create the new issue.
+## Contributing to Concept Exercises
-To run this script:
+Please read [Implementing a Concept Exercise](reference/implementing-a-concept-exercise.md).
- 1. Clone [the problem-specifications repository](https://github.com/exercism/problem-specifications).
-
- 2. Create a file `.exercism-version-update-issue-script-settings.sh` in your home directory.
-
- 3. In this file, you have to put the following variables:
- - `TOKEN="your_token"`
- - `OWNER="exercism"`
- - `REPO="java"`
-
- For authentication, you need to create a personal token, see [this GitHub page](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line) for more information.
+## Contributing to Practice Exercises
- 4. Run `./scripts/create_issues_new_exercise.sh -t . -s --spec-path path_to_problem_specifications` from the root of this repository and follow the directions.
-
- 5. If you decide to submit a new issue you can find the opened issue on the [issues page](https://github.com/exercism/java/issues).
+Please read [Contributing to Practice Exercises](reference/contributing-to-practice-exercises.md).
+[docs-building]: https://exercism.org/docs/building
+[docs-building-github]: https://exercism.org/docs/building/github
+[docs-building-github-prs]: https://exercism.org/docs/building/github/contributors-pull-request-guide
+[docs-java-installation]: https://exercism.org/docs/tracks/java/installation
[forum]: https://forum.exercism.org/
+[git-branching]: https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell
+[git-rebase]: https://git-scm.com/docs/git-rebase
+[github-forking]: https://help.github.com/articles/fork-a-repo/
+[github-sync-fork]: https://help.github.com/articles/syncing-a-fork/
+[track-repo]: https://github.com/exercism/java
diff --git a/POLICIES.md b/POLICIES.md
index 6f7b14261..09d803f04 100644
--- a/POLICIES.md
+++ b/POLICIES.md
@@ -15,13 +15,24 @@ Our policies are not set-in-stone. They represent directions chosen at a point i
## Event Checklist
-| Track Event | Policies to review |
-|:------------|:-----------------|
-| Exercise added/updated | [Prefer instance methods](#prefer-instance-methods); [Avoid using final](#avoid-using-final); [Adhere to best practices](#adhere-to-best-practices); [Starter implementations](#starter-implementations); [Ignore noninitial tests](#ignore-noninitial-tests); [Multiple file submissions](#multiple-file-submissions); [Name test class after class under test](#name-test-class-after-class-under-test); [Add hint for the first exercises without starter implementation](#add-hint-for-the-first-exercises-without-starter-implementation); [Reference tutorial in the first exercises](#reference-tutorial-in-the-first-exercises); [Avoid returning null](#avoid-returning-null); [Use assertThrows](#use-assertthrows); [Using other assertion libraries](#using-other-assertion-libraries)
-| Track rearranged | [Starter implementations](#starter-implementations); [Multiple file submissions](#multiple-file-submissions) |
-| New issue observed in track | [Good first issues](#good-first-issues) |
-| "Good first issue" issue completed | [Good first issues](#good-first-issues) |
-| Installing Java instructions updated | [Simple onboarding](#simple-onboarding) |
+| Track Event | Policies to review |
+| :----------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------- |
+| Exercise added/updated | [Prefer instance methods](#prefer-instance-methods) |
+| | [Avoid using final](#avoid-using-final) |
+| | [Adhere to best practices](#adhere-to-best-practices) |
+| | [Starter implementations](#starter-implementations) |
+| | [Ignore noninitial tests](#ignore-noninitial-tests) |
+| | [Multiple file submissions](#multiple-file-submissions) |
+| | [Name test class after class under test](#name-test-class-after-class-under-test) |
+| | [Add hint for the first exercises without starter implementation](#add-hint-for-the-first-exercises-without-starter-implementation) |
+| | [Reference tutorial in the first exercises](#reference-tutorial-in-the-first-exercises) |
+| | [Avoid returning null](#avoid-returning-null) |
+| | [Prefer AssertJ assertions](#prefer-assertj-assertions) |
+| Track rearranged | [Starter implementations](#starter-implementations) |
+| | [Multiple file submissions](#multiple-file-submissions) |
+| New issue observed in track | [Good first issues](#good-first-issues) |
+| "Good first issue" issue completed | [Good first issues](#good-first-issues) |
+| Installing Java instructions updated | [Simple onboarding](#simple-onboarding) |
## Policy Descriptions
@@ -34,9 +45,13 @@ References: [[1](https://github.com/exercism/java/issues/177#issuecomment-261291
### Starter implementations
> - Exercises of difficulty 4 or lower: provide stubs for all required constructors and methods. This means that you need to provide stubs for those constructors or methods that are necessary to pass all tests. E.g. stubs for private methods may be skipped.
-Stubs should include the following body:
- `throw new UnsupportedOperationException("Delete this statement and write your own implementation.");`
-> - Exercises of difficulty 5 or higher: copy the StubTemplate.java file (provided [here](https://github.com/exercism/java/tree/main/_template/src/main/java)) and rename it to fit the exercise. For example, for the exercise linked-list the file could be named LinkedList.java. Then either (1) add hints to the hints.md file (which gets merged into the README.md for the exercise) or (2) provide stubs as above for exercises that demand complicated method signatures.
+> Stubs should include the following body:
+>
+> ```java
+> throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+> ```
+>
+> - Exercises of difficulty 5 or higher: copy the StubTemplate.java file (provided in [this template file](https://github.com/exercism/java/blob/main/resources/exercise-template/src/main/java/ExerciseName.java)) and rename it to fit the exercise. For example, for the exercise linked-list the file could be named LinkedList.java. Then either (1) add hints to the hints.md file (which gets merged into the README.md for the exercise) or (2) provide stubs as above for exercises that demand complicated method signatures.
References: [[1](https://github.com/exercism/java/issues/178)], [[2](https://github.com/exercism/java/pull/683#discussion_r125506930)], [[3](https://github.com/exercism/java/issues/977)], [[4](https://github.com/exercism/java/issues/1721)]
@@ -47,8 +62,10 @@ References: [[1](https://github.com/exercism/java/issues/178)], [[2](https://git
### Adhere to best practices
> Ensure that all Java code adheres to the best practices listed below:
+>
> - Minimize the accessibility of classes and members ([Effective Java, item 13](http://jtechies.blogspot.com/2012/07/item-13-minimize-accessibility-of.html))
> - Always use curly brackets in `if statements`. This makes your code easier to maintain and easier to read ([Oracle style guide, item 7.4](http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-142311.html#431))
+
```java
// Please do this
if (condition) {
@@ -61,7 +78,9 @@ if (condition)
if (condition) doSomething();
```
+
> - Always put opening curly bracket not on a newline ([Oracle style guide, item 7.2](http://www.oracle.com/technetwork/java/javase/documentation/codeconventions-142311.html#431))
+
```java
// Please do this
for (int i = 0; i < 10; i++) {
@@ -89,7 +108,7 @@ References: [[1](https://github.com/exercism/java/issues/365#issuecomment-292533
### Good first issues
-> Aim to keep 10-20 small and straightforward issues open at any given time. Identify any such issues by applying the "good first issue" label. You can view the current list of these issues [here](https://github.com/exercism/java/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
+> Aim to keep 10-20 small and straightforward issues open at any given time. Identify any such issues by applying the "good first issue" label. You can view the current list of labeled issues [on GitHub](https://github.com/exercism/java/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22).
References: [[1](https://github.com/exercism/java/issues/220#issue-196447088)], [[2](https://github.com/exercism/java/issues/1669)]
@@ -102,7 +121,7 @@ References: [[1](https://github.com/exercism/java/issues/395#issue-215734887)]
### Name test class after class under test
> If you're testing a class called `SomeClassName` then your test class should be called `SomeClassNameTest`.
-
+>
> The exception to this is if the tests are split into several test classes where each test class tests different functionality. In that case each class should be named `SomeClassNameFunctionalityTest` where `Functionality` is the name of the functionality to be tested in that class. See the [clock exercise](https://github.com/exercism/java/tree/main/exercises/practice/clock) as an example.
References: [[1](https://github.com/exercism/java/issues/697)]
@@ -112,11 +131,11 @@ References: [[1](https://github.com/exercism/java/issues/697)]
> According to the starter implementation policy, any exercise with difficulty 4 or lower should have starter implementation.
> Any exercise with difficulty 5 or above will have no starter implementation (unless its signature is very complicated).
> This could be confusing to users when tackling their first exercise with difficulty 5 when they are used to starter implementation being provided.
-
+>
> Therefore a hints.md file should be added to the .meta directory for every exercise with difficulty 5.
> This file should explain what they need to do when there is no starter implementation.
> The files should all be the same so you can copy it from any other exercise with difficulty 5, e.g. [flatten-array](https://github.com/exercism/java/tree/main/exercises/pratice/flatten-array/.meta/hints.md).
-
+>
> We add the file to every exercise with difficulty 5 because the structure of the track means that we don't know which exercise will be the first one without starter implementation that a user will be faced with.
References: [[1](https://github.com/exercism/java/issues/1075)]
@@ -135,38 +154,26 @@ References: [[1](https://github.com/exercism/java/issues/1389)]
> When an error has occured or a method can't return anything, the canonical data will just mark that as `"expected": null`.
> This is because error handling varies from language to language, so the canonical data is leaving it up to each language track to decide how to deal with those situations.
> It doesn't mean that the method needs to return `null`.
-
+>
> In Java it's considered bad practice to return `null`.
> If you return `null` then the user of the method has to remember to check for `null` and they have to look at the implementation of the method to find out that this is necessary.
-
+>
> It's considered best practice to deal with errors and unexpected circumstances by throwing exceptions.
> If you throw an exception then you force the user to deal with the problem.
> You can either define your own exception (see [the triangle exercise](https://github.com/exercism/java/blob/main/exercises/practice/triangle/.meta/src/reference/java/TriangleException.java) for an example) or use a predefined one (see [the collatz-conjecture exercise](https://github.com/exercism/java/blob/main/exercises/practice/collatz-conjecture/src/test/java/CollatzCalculatorTest.java) for an example).
-
+>
> Another option is to use [Optionals](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html).
> This can be more suitable if the case you want to deal with isn't an exceptional occurence, but rather an expected scenario, e.g. a search method not finding what it was searching for.
> See [the word-search exercise](https://github.com/exercism/java/blob/main/exercises/practice/word-search/src/test/java/WordSearcherTest.java) for an example where `Optional` is used.
References: [[1](https://www.codebyamir.com/blog/stop-returning-null-in-java)]
-### Use assertThrows
-
-> Some exercises expect exceptions to be thrown in the tests.
-> The exercises on this track are all using [`org.junit.Assert.assertThrows`](https://junit.org/junit4/javadoc/latest/org/junit/Assert.html#assertThrows(java.lang.Class,%20org.junit.function.ThrowingRunnable)) instead of `@Test(expected = SomeException.class)`.
-> `assertThrows` is more powerful than using the `@Test` annotation.
-> With this method you can assert that a given function call (specified, for instance, as a lambda expression or method reference) results in a particular type of exception being thrown.
-> In addition it returns the exception that was thrown, so that further assertions can be made (e.g. to verify that the message and cause are correct).
-> Furthermore, you can make assertions on the state of a domain object after the exception has been thrown.
-> To be consistent, please use `assertThrows` whenever you expect an exception to be thrown.
-
-> See [the triangle tests](https://github.com/exercism/java/blob/main/exercises/practice/triangle/src/test/java/TriangleTest.java) for an example of where `assertThrows` is used.
-
-References: [[1](https://github.com/junit-team/junit4/wiki/Exception-testing)]
-
-### Using other assertion libraries
+### Prefer AssertJ assertions
-> Some exercises have expected results that may be better handled by another assertion library.
-> While the default will continue to be using JUnit's assertions (eg. `org.junit.Assert.assertEquals`), we do allow [AssertJ](https://assertj.github.io/doc/) as well.
-> All other assertion libraries (eg. [Hamcrest](http://hamcrest.org/JavaHamcrest/) and [Truth](https://truth.dev/)) are banned.
+> Use [AssertJ](https://assertj.github.io/doc/) assertions over JUnit assertions in exercise test suites.
+> AssertJ assertions are more robust and provide more readable output in assertion failures.
+> This is especially useful for students using the online editor, as the output of the assertion is all they have to debug test failures in their iterations.
+>
+> All other assertion libraries (eg. [Hamcrest](http://hamcrest.org/JavaHamcrest/) and [Truth](https://truth.dev/)) should not be used.
-References: [[1](https://github.com/exercism/java/issues/1803)]
+References: [[1](https://github.com/exercism/java/issues/1803)], [[2](https://github.com/exercism/java/issues/2147)]
diff --git a/README.md b/README.md
index 55e830a04..34cfaffe3 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,27 @@
# Exercism Java Track
-
[](https://github.com/exercism/java/actions/workflows/configlet.yml)
+[](https://github.com/exercism/java/actions/workflows/java.yml)
-Source for Exercism Exercises in Java.
+This repository contains the source for the exercises of the Java track on Exercism.
+
+## Java Track Tooling
+
+Next to the exercises, the Java track also consists of the following tooling:
+
+- [exercism/java-test-runner] - The Exercism [test runner][docs-test-runners] for the Java track that automatically verifies if a submitted solution passes all of the exercise's tests.
+- [exercism/java-representer] - The Exercism [representer][docs-representers] for the Java track that creates normalized representations of submitted solutions.
+- [exercism/java-analyzer] - The Exercism [analyzer][docs-analyzers] for the Java track that automatically provides comments on submitted solutions.
## Contributing Guide
For general information about how to contribute to Exercism, please refer to the [Contributing Guide](https://exercism.org/contributing).
For information on contributing to this track, refer to the [CONTRIBUTING.md](https://github.com/exercism/java/blob/main/CONTRIBUTING.md) file.
+
+[docs-analyzers]: https://exercism.org/docs/building/tooling/analyzers
+[docs-representers]: https://exercism.org/docs/building/tooling/representers
+[docs-test-runners]: https://exercism.org/docs/building/tooling/test-runners
+[exercism/java-analyzer]: https://github.com/exercism/java-analyzer
+[exercism/java-representer]: https://github.com/exercism/java-representer
+[exercism/java-test-runner]: https://github.com/exercism/java-test-runner
diff --git a/_template/.meta/src/reference/java/.keep b/_template/.meta/src/reference/java/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/_template/build.gradle b/_template/build.gradle
deleted file mode 100644
index 8bd005d42..000000000
--- a/_template/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
-}
-
-test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
-}
diff --git a/_template/src/main/java/StubTemplate.java b/_template/src/main/java/StubTemplate.java
deleted file mode 100644
index 6178f1beb..000000000
--- a/_template/src/main/java/StubTemplate.java
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
-
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
-
-Please remove this comment when submitting your solution.
-
-*/
diff --git a/_template/src/test/java/.keep b/_template/src/test/java/.keep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/bin/README.md b/bin/README.md
deleted file mode 100644
index 3a940b8ab..000000000
--- a/bin/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Cross-track consistency
-
-Many of these scripts are shared between the Java and Kotlin tracks. If you make an update to a script in one of these tracks, please also update the same script in the other track if appropriate. Thank you!
diff --git a/bin/build-jq.sh b/bin/build-jq.sh
deleted file mode 100755
index 3aca61d78..000000000
--- a/bin/build-jq.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env bash
-set -ex
-
-pushd bin
-curl --location https://github.com/stedolan/jq/releases/download/jq-1.5/jq-1.5.tar.gz >jq-1.5.tar.gz
-tar xvf jq-1.5.tar.gz
-cd jq-1.5
-./configure --disable-maintainer-mode && make
-mv jq ..
-popd
-
diff --git a/bin/fetch-configlet b/bin/fetch-configlet
index 4800e1508..6bef43ab7 100755
--- a/bin/fetch-configlet
+++ b/bin/fetch-configlet
@@ -24,10 +24,11 @@ get_download_url() {
local latest='https://api.github.com/repos/exercism/configlet/releases/latest'
local arch
case "$(uname -m)" in
- x86_64) arch='x86-64' ;;
- *686*) arch='i386' ;;
- *386*) arch='i386' ;;
- *) arch='x86-64' ;;
+ aarch64|arm64) arch='arm64' ;;
+ x86_64) arch='x86-64' ;;
+ *686*) arch='i386' ;;
+ *386*) arch='i386' ;;
+ *) arch='x86-64' ;;
esac
local suffix="${os}_${arch}.${ext}"
curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" |
@@ -47,7 +48,7 @@ main() {
fi
local os
- case "$(uname)" in
+ case "$(uname -s)" in
Darwin*) os='macos' ;;
Linux*) os='linux' ;;
Windows*) os='windows' ;;
@@ -58,8 +59,8 @@ main() {
local ext
case "${os}" in
- windows*) ext='zip' ;;
- *) ext='tar.gz' ;;
+ windows) ext='zip' ;;
+ *) ext='tar.gz' ;;
esac
echo "Fetching configlet..." >&2
@@ -69,16 +70,16 @@ main() {
curl "${curlopts[@]}" --output "${output_path}" "${download_url}"
case "${ext}" in
- *zip) unzip "${output_path}" -d "${output_dir}" ;;
- *) tar xzf "${output_path}" -C "${output_dir}" ;;
+ zip) unzip "${output_path}" -d "${output_dir}" ;;
+ *) tar xzf "${output_path}" -C "${output_dir}" ;;
esac
rm -f "${output_path}"
local executable_ext
case "${os}" in
- windows*) executable_ext='.exe' ;;
- *) executable_ext='' ;;
+ windows) executable_ext='.exe' ;;
+ *) executable_ext='' ;;
esac
local configlet_path="${output_dir}/configlet${executable_ext}"
diff --git a/bin/journey-test.sh b/bin/journey-test.sh
deleted file mode 100755
index 956466fa6..000000000
--- a/bin/journey-test.sh
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/env bash
-
-TRACK=java
-TRACK_REPO="$TRACK"
-TRACK_SRC_EXT="java"
-EXERCISES_TO_SOLVE=$@
-
-on_exit() {
- echo ">>> on_exit()"
- cd $EXECPATH
- echo "<<< on_exit()"
-}
-
-assert_installed() {
- local binary=$1
- echo ">>> assert_installed(binary=\"${binary}\")"
-
- if [[ "`which $binary`" == "" ]]; then
- echo "${binary} not found; it is required to perform this test."
- echo -e "Have you completed the setup instructions at https://github.com/exercism/${TRACK_REPO} ?\n"
- echo "PATH=${PATH}"
- echo "aborting."
- exit 1
- fi
- echo "<<< assert_installed()"
-}
-
-clean() {
- local build_dir="$1"
- echo ">>> clean(build_dir=\"${build_dir}\")"
-
- # empty, absolute path, or parent reference are considered dangerous to rm -rf against.
- if [[ "${build_dir}" == "" || ${build_dir} =~ ^/ || ${build_dir} =~ \.\. ]] ; then
- echo "Value for build_dir looks dangerous. Aborting."
- exit 1
- fi
-
- local build_path=$( pwd )/${build_dir}
- if [[ -d "${build_path}" ]] ; then
- echo "Cleaning journey script build output directory (${build_path})."
- rm -rf "${build_path}"
- fi
- cd exercises
- "$EXECPATH"/gradlew clean
- cd ..
- echo "<<< clean()"
-}
-
-solve_exercise() {
- local exercise="$1"
-
- echo -e "\n\n"
- echo "=================================================="
- echo "Solving ${exercise}"
- echo "=================================================="
-
- mkdir -p ${exercism_exercises_dir}/${TRACK}/${exercise}/src/main/java/
- mkdir -p ${exercism_exercises_dir}/${TRACK}/${exercise}/src/test/java/
- cp ${track_root}/exercises/${exercise}/build.gradle ${exercism_exercises_dir}/${TRACK}/${exercise}/build.gradle
- cp -R -H ${track_root}/exercises/${exercise}/.meta/src/reference/${TRACK}/* ${exercism_exercises_dir}/${TRACK}/${exercise}/src/main/${TRACK}/
- cp -R -H ${track_root}/exercises/${exercise}/src/test/${TRACK}/* ${exercism_exercises_dir}/${TRACK}/${exercise}/src/test/${TRACK}/
-
- pushd ${exercism_exercises_dir}/${TRACK}/${exercise}
- # Check that tests compile before we strip @Ignore annotations
- "$EXECPATH"/gradlew compileTestJava
- # Ensure we run all the tests (as delivered, all but the first is @Ignore'd)
- for testfile in `find . -name "*Test.${TRACK_SRC_EXT}"`; do
- # Strip @Ignore annotations to ensure we run the tests (as delivered, all but the first is @Ignore'd).
- # Note that unit-test.sh also strips @Ignore annotations via the Gradle task copyTestsFilteringIgnores.
- # The stripping implementations here and in copyTestsFilteringIgnores should be kept consistent.
- sed 's/@Ignore\(\(.*\)\)\{0,1\}//' ${testfile} > "${tempfile}" && mv "${tempfile}" "${testfile}"
- done
- "$EXECPATH"/gradlew test
- popd
-}
-
-solve_all_exercises() {
- local exercism_exercises_dir="$1"
- echo ">>> solve_all_exercises(exercism_exercises_dir=\"${exercism_exercises_dir}\")"
-
- local track_root=$( pwd )
- local exercises=`cat config.json | jq '.exercises[].slug + " "' --join-output`
- local total_exercises=`cat config.json | jq '.exercises | length'`
- local current_exercise_number=1
- local tempfile="${TMPDIR:-/tmp}/journey-test.sh-unignore_all_tests.txt"
-
- mkdir -p ${exercism_exercises_dir}
- pushd ${exercism_exercises_dir}
-
- for exercise in $exercises; do
- echo -e "\n\n"
- echo "=================================================="
- echo "${current_exercise_number} of ${total_exercises} -- ${exercise}"
- echo "=================================================="
-
- solve_exercise "${exercise}"
-
- current_exercise_number=$((current_exercise_number + 1))
- done
- popd
-}
-
-solve_single_exercise() {
- local exercism_exercises_dir="$1"
- local exercise_to_solve="$2"
- echo ">>> solve_single_exercises(exercism_exercises_dir=\"${exercism_exercises_dir}\", exercise_to_solve=\"$exercise_to_solve\")"
-
- local track_root=$( pwd )
- local tempfile="${TMPDIR:-/tmp}/journey-test.sh-unignore_all_tests.txt"
-
- mkdir -p ${exercism_exercises_dir}
- pushd ${exercism_exercises_dir}
-
- solve_exercise "${exercise_to_solve}"
-
- popd
-}
-
-main() {
- # all functions assume current working directory is repository root.
- cd "${SCRIPTPATH}/.."
-
- local track_root=$( pwd )
- local build_dir="build"
- local build_path="${track_root}/${build_dir}"
-
- local exercism_home="${build_path}/exercism"
-
- # fail fast if required binaries are not installed.
- assert_installed "jq"
-
- clean "${build_dir}"
-
- if [[ $EXERCISES_TO_SOLVE == "" ]]; then
- solve_all_exercises "${exercism_home}"
- else
- for exercise in $EXERCISES_TO_SOLVE
- do solve_single_exercise "${exercism_home}" "${exercise}"
- done
- fi
-}
-
-##########################################################################
-# Execution begins here...
-
-# If any command fails, fail the script.
-set -ex
-SCRIPTPATH=$( pushd `dirname $0` > /dev/null && pwd && popd > /dev/null )
-EXECPATH=$( pwd )
-# Make output easier to read in CI
-TERM=dumb
-
-trap on_exit EXIT
-main
-
diff --git a/bin/run-journey-test-from-ci.sh b/bin/run-journey-test-from-ci.sh
deleted file mode 100755
index 9c14e1bbd..000000000
--- a/bin/run-journey-test-from-ci.sh
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/bin/bash
-
-contains_setup_file() {
- local files=$1
- for file in $files; do
- if [[ $file == *.gradle || $file == *.sh || $file == config.json ]]; then
- return 0
- fi
- done
- return 1
-}
-
-contains_exercise() {
- local files=$1
- for file in $files; do
- if [[ $file == exercises* ]]; then
- return 0
- fi
- done
- return 1
-}
-
-run_journey_test_with_modified_exercises() {
- local modded_files=$1
- local last_modded_exercise=""
- local modded_exercises=""
-
- for file in $modded_files; do
- if [[ $file == exercises* ]] && [[ $file != exercises/settings.gradle ]] && [[ $file != exercises/build.gradle ]]; then
- local modded_exercise=${file#exercises/}
- modded_exercise=${modded_exercise%%/*}
- if [[ $last_modded_exercise != $modded_exercise ]]; then
- modded_exercises=$modded_exercises$modded_exercise$'\n'
- fi
- last_modded_exercise=$modded_exercise
- fi
- done
-
- echo "Running journey test with modified exercise(s): ${modded_exercises}"
- bin/journey-test.sh $modded_exercises
-}
-
-run_journey_test_with_all_exercises() {
- echo "Running journey test with all exercises"
- bin/journey-test.sh
-}
-
-main() {
- bin/build-jq.sh
-
- local pr_files_json=`curl -s https://api.github.com/repos/exercism/java/pulls/${TRAVIS_PULL_REQUEST}/files`
-
- echo "Pull request number: ${TRAVIS_PULL_REQUEST}"
- echo "Changes in pr json: ${pr_files_json}"
-
- # if jq fails to get the required data, then that means TRAVIS_PULL_REQUEST was not set (not run in travis-ci),
- # or was false (not a pull request), or the api limit was reached, or some other error occurred.
- # In that case, we should fall back with testing every exercise
- local pr_files_json_type=`echo $pr_files_json | bin/jq -r 'type'`
- if [[ $pr_files_json_type != "array" ]]; then
- echo "Didn't get pr changes from travis"
- run_journey_test_with_all_exercises
- return
- fi
-
- local modded_files=`echo $pr_files_json | bin/jq -r '.[].filename'`
-
- # If the changed files contain a .sh file or .gradle file or config.json then we should run all the exercises
- if contains_setup_file "${modded_files}"; then
- echo "Pr changes contain setup file(s): ${modded_files}"
- run_journey_test_with_all_exercises
- return
- fi
-
- if contains_exercise "${modded_files}"; then
- echo "Pr changes contain modified exercise file(s)"
- run_journey_test_with_modified_exercises "${modded_files}"
- fi
-}
-
-trap 'exit 1' ERR
-main
diff --git a/bin/site-editor-check.sh b/bin/site-editor-check.sh
deleted file mode 100755
index 234d4d9cb..000000000
--- a/bin/site-editor-check.sh
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env bash
-
-# checks whether all classes and solution stubs are visible in the editor
-# (via mentioning in the .meta/config.json)
-
-set -e
-
-root=$(git rev-parse --show-toplevel)
-
-for exercise_type in 'concept' 'practice'; do
- group="${root}/exercises/${exercise_type}"
- mapfile -t exercises < <(ls "${group}")
- for exercise in "${exercises[@]}"; do
- exercise_path="${group}/${exercise}"
- config_files=$(jq '.files' < "${exercise_path}/.meta/config.json")
- declare -A files
- for file_type in 'editor' 'solution'; do
- for file in $(echo "${config_files}" | jq -r ".${file_type} | @sh"); do
- if [[ "${file}" != null ]]; then
- if [[ "${file_type}" == 'editor' && $(grep 'UnsupportedOperationException' < "${exercise_path}/${file//\'/}") != '' ]]; then
- echo "${file} should be editable"
- exit 1
- fi
- files[${file}]=true
- fi
- done
- done
- mapfile -t java_files < <(find "${exercise_path}/src/main" -type f -name '*.java')
- for java_file in "${java_files[@]}"; do
- file_location=${java_file//${exercise_path}\//}
- if [[ "${files["'${file_location}'"]}" != 'true' ]]; then
- echo "${exercise_type}/${exercise}/${file_location} is not available in the editor"
- exit 1
- fi
- done
- done
-done
\ No newline at end of file
diff --git a/bin/test-changed-exercise b/bin/test-changed-exercise
new file mode 100755
index 000000000..b18a488e8
--- /dev/null
+++ b/bin/test-changed-exercise
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+set -eo pipefail
+
+# Determine the base branch of the PR
+BASE_BRANCH=${GITHUB_BASE_REF:-main}
+
+# Fetch full history for proper diff
+git fetch origin "$BASE_BRANCH"
+
+# Compute merge base
+MERGE_BASE=$(git merge-base HEAD origin/"$BASE_BRANCH")
+
+# Get changed files relative to merge base
+changed_files=$(git diff --name-only "$MERGE_BASE" HEAD)
+
+# If any Gradle build file changed, run the full suite and exit
+if echo "$changed_files" | grep -qE '\.(gradle|gradlew|bat)$|settings\.gradle'; then
+ echo "Gradle build files changed, running full test suite..."
+ ./bin/test-with-test-runner
+ exit 0
+fi
+
+# Extract unique exercise directories
+changed_exercises=$(echo "$changed_files" | \
+ grep -E '^exercises/(practice|concept)/[^/]+/.+\.java$' | \
+ cut -d/ -f1-3 | sort -u)
+
+if [ -z "$changed_exercises" ]; then
+ echo "No relevant exercises changed, skipping tests."
+ exit 0
+fi
+
+# Print exercises
+echo "Changed exercises detected:"
+echo "$changed_exercises"
+echo "----------------------------------------"
+
+summary_dir="exercises/build"
+summary_file="${summary_dir}/summary.txt"
+mkdir -p "$summary_dir"
+
+# Run tests
+exit_code=0
+for dir in $changed_exercises; do
+ slug=$(basename "$dir")
+
+ echo "========================================"
+ echo "=== Running tests for $slug ==="
+ echo "========================================"
+
+ results_path="$dir/build/results.txt"
+ mkdir -p "$(dirname "$results_path")"
+
+ if [[ $dir == exercises/practice/* ]]; then
+ ./exercises/gradlew -p exercises ":practice:$slug:test" 2>&1 | tee "$results_path" || true
+ elif [[ $dir == exercises/concept/* ]]; then
+ ./exercises/gradlew -p exercises ":concept:$slug:test" 2>&1 | tee "$results_path" || true
+ fi
+
+ # Detect failure
+ if grep -q "FAILED" "$results_path"; then
+ exit_code=1
+
+ # Determine practice/slug or concept/slug
+ relative_path=$(echo "$dir" | sed 's|^exercises/||')
+
+ # Create summary.txt with header only on first failure
+ if [ ! -f "$summary_file" ]; then
+ echo "The following exercises have test failures or test errors:" > "$summary_file"
+ fi
+
+ # Append the correct path (practice/slug or concept/slug)
+ echo "$relative_path" >> "$summary_file"
+ fi
+
+done
+
+exit $exit_code
\ No newline at end of file
diff --git a/bin/test-deprecated-exercises b/bin/test-deprecated-exercises
new file mode 100755
index 000000000..5260d6b6c
--- /dev/null
+++ b/bin/test-deprecated-exercises
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+set -eo pipefail
+
+# Determine the base branch of the PR
+BASE_BRANCH=${GITHUB_BASE_REF:-main}
+
+# Fetch full history for proper diff
+git fetch origin "$BASE_BRANCH"
+
+# Compute merge base
+MERGE_BASE=$(git merge-base HEAD origin/"$BASE_BRANCH")
+
+# Get changed files relative to merge base
+changed_files=$(git diff --name-only "$MERGE_BASE" HEAD)
+
+# Extract unique exercise directories
+changed_exercises=$(echo "$changed_files" | grep -E '^exercises/(practice|concept)/' || true)
+changed_exercises=$(echo "$changed_exercises" | cut -d/ -f1-3 | sort -u)
+
+# Early exit if no exercise changed
+if [ -z "$changed_exercises" ]; then
+ echo "No exercises changed!"
+ exit 0
+fi
+
+# Load deprecated exercises from config.json
+deprecated_exercises=$(jq -r '
+ [
+ (.exercises.concept[]? | select(.status=="deprecated") | "exercises/concept/" + .slug),
+ (.exercises.practice[]? | select(.status=="deprecated") | "exercises/practice/" + .slug)
+ ] | .[]
+' config.json)
+
+# Check for deprecated ones
+for ex in $changed_exercises; do
+ if echo "$deprecated_exercises" | grep -qx "$ex"; then
+ echo "❌ Deprecated exercise changed: $ex"
+ exit 1
+ fi
+done
+
+echo "✅ No deprecated exercises changed!"
diff --git a/bin/test-with-test-runner b/bin/test-with-test-runner
new file mode 100755
index 000000000..5e5dc8889
--- /dev/null
+++ b/bin/test-with-test-runner
@@ -0,0 +1,103 @@
+#!/usr/bin/env bash
+
+# Test if the example/exemplar solution of each
+# Practice/Concept Exercise passes the exercise's tests.
+
+# Example:
+# ./bin/test-with-test-runner
+
+set -eo pipefail
+
+docker pull exercism/java-test-runner
+
+exit_code=0
+
+summary_dir="exercises/build"
+summary_file="${summary_dir}/summary.txt"
+mkdir -p "$summary_dir"
+
+function run_test_runner() {
+ local slug=$1
+ local solution_dir=$2
+ local output_dir=$3
+
+ docker run \
+ --rm \
+ --network none \
+ --mount type=bind,src="${solution_dir}",dst=/solution \
+ --mount type=bind,src="${output_dir}",dst=/output \
+ --tmpfs /tmp:rw \
+ exercism/java-test-runner "${slug}" "/solution" "/output"
+}
+
+function verify_exercise() {
+ local dir=$(realpath $1)
+ local slug=$(basename "${dir}")
+ local output_dir="${dir}/build"
+ local implementation_file_key=$2
+ local implementation_files=($(jq -r --arg d "${dir}" --arg k "${implementation_file_key}" '$d + "/" + .files[$k][]' "${dir}/.meta/config.json"))
+ local stub_files=($(jq -r --arg d "${dir}" '$d + "/" + .files.solution[]' "${dir}/.meta/config.json"))
+ local results_file="${output_dir}/results.json"
+ local exercise_type=$3
+ local exercise_status=($(jq -r --arg t ${exercise_type} --arg s ${slug} '.exercises[$t][] | select(.slug == $s).status' config.json))
+
+ if [[ $exercise_status == "deprecated" ]]; then
+ echo "Skipping deprecated exercise: ${slug}"
+ return
+ fi
+
+ mkdir -p "${output_dir}"
+
+ for stub_file in "${stub_files[@]}"; do
+ cp "${stub_file}" "${stub_file}.bak"
+ done
+
+ for impl_file in "${implementation_files[@]}"; do
+ cp "${impl_file}" "${dir}/src/main/java/$(basename ${impl_file})"
+ done
+
+ run_test_runner "${slug}" "${dir}" "${output_dir}"
+
+ if [[ $(jq -r '.status' "${results_file}") != "pass" ]]; then
+ echo "${slug}: ${implementation_file_key} solution did not pass the tests"
+
+ # Determine practice/slug or concept/slug
+ local relative_path=$(echo "$dir" | sed -E 's|.*/exercises/||')
+
+ # Create summary.txt with header only on first failure
+ if [ ! -f "$summary_file" ]; then
+ echo "The following exercises have test failures or test errors:" > "$summary_file"
+ fi
+
+ # Append the correct path (practice/slug or concept/slug)
+ echo "$relative_path" >> "$summary_file"
+
+ exit_code=1
+ fi
+
+ for impl_file in "${implementation_files[@]}"; do
+ rm "${dir}/src/main/java/$(basename ${impl_file})"
+ done
+
+ for stub_file in "${stub_files[@]}"; do
+ mv "${stub_file}.bak" "${stub_file}"
+ done
+}
+
+# Verify the Concept Exercises
+for concept_exercise_dir in ./exercises/concept/*/; do
+ if [ -d $concept_exercise_dir ]; then
+ echo "Checking $(basename "${concept_exercise_dir}") exercise..."
+ verify_exercise $concept_exercise_dir "exemplar" "concept"
+ fi
+done
+
+# Verify the Practice Exercises
+for practice_exercise_dir in ./exercises/practice/*/; do
+ if [ -d $practice_exercise_dir ]; then
+ echo "Checking $(basename "${practice_exercise_dir}") exercise..."
+ verify_exercise $practice_exercise_dir "example" "practice"
+ fi
+done
+
+exit ${exit_code}
diff --git a/bin/unit-tests.sh b/bin/unit-tests.sh
deleted file mode 100755
index 23863f9a9..000000000
--- a/bin/unit-tests.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env bash
-trap 'exit 1' ERR
-
-./gradlew --version
-
-echo ""
-echo ">>> Running configlet..."
-bin/fetch-configlet
-bin/configlet lint .
-
-pushd exercises
-echo ""
-echo ">>> Running tests..."
-TERM=dumb ../gradlew check compileStarterSourceJava --parallel --continue
-popd
diff --git a/concepts/arrays/about.md b/concepts/arrays/about.md
index b7588364b..45a5862fe 100644
--- a/concepts/arrays/about.md
+++ b/concepts/arrays/about.md
@@ -1,7 +1,7 @@
# About Arrays
-In Java, data structures that can hold zero or more elements are known as _collections_.
-An **array** is a collection that has a fixed size and whose elements must all be of the same type.
+In Java, arrays are a way to store multiple values of the same type in a single structure.
+Unlike other data structures, arrays have a fixed size once created.
Elements can be assigned to an array or retrieved from it using an index.
Java arrays use zero-based indexing: the first element's index is 0, the second element's index is 1, etc.
@@ -44,7 +44,8 @@ int secondElement = twoInts[1];
Accessing an index that is outside of the valid indexes for the array results in an `IndexOutOfBoundsException`.
Arrays can be manipulated by either calling an array instance's methods or properties, or by using the static methods defined in the `Arrays` class (typically only used in generic code).
-The most commonly used property for arrays is its length which can be accessed like this:
+The `length` property holds the length of an array.
+It can be accessed like this:
```java
int arrayLength = someArray.length;
diff --git a/concepts/arrays/introduction.md b/concepts/arrays/introduction.md
index 218b91869..b83cac31f 100644
--- a/concepts/arrays/introduction.md
+++ b/concepts/arrays/introduction.md
@@ -1,7 +1,7 @@
# Introduction to Arrays
-In Java, data structures that can hold zero or more elements are known as _collections_.
-An **array** is a collection that has a fixed size and whose elements must all be of the same type.
+In Java, arrays are a way to store multiple values of the same type in a single structure.
+Unlike other data structures, arrays have a fixed size once created.
Elements can be assigned to an array or retrieved from it using an index.
Java arrays use zero-based indexing: the first element's index is 0, the second element's index is 1, etc.
@@ -44,7 +44,8 @@ int secondElement = twoInts[1];
Accessing an index that is outside of the valid indexes for the array results in an `IndexOutOfBoundsException`.
Arrays can be manipulated by either calling an array instance's methods or properties, or by using the static methods defined in the `Arrays` class (typically only used in generic code).
-The most commonly used property for arrays is its length which can be accessed like this:
+The `length` property holds the length of an array.
+It can be accessed like this:
```java
int arrayLength = someArray.length;
diff --git a/concepts/basics/about.md b/concepts/basics/about.md
index 3711d08ab..508ae41ef 100644
--- a/concepts/basics/about.md
+++ b/concepts/basics/about.md
@@ -1,12 +1,14 @@
# About
-Java is a statically-typed language, which means that everything has a type at compile-time. Assigning a value to a name is referred to as defining a variable.
+Java is a statically-typed language, which means that everything has a type at compile-time.
+Assigning a value to a name is referred to as defining a variable.
```java
int explicitVar = 10; // Explicitly typed
```
-The value of a variable can be assigned and updated using the [`=` operator][assignment]. Once defined, a variable's type can never change.
+The value of a variable can be assigned and updated using the [`=` operator][assignment].
+Once defined, a variable's type can never change.
```java
int count = 1; // Assign initial value
@@ -16,7 +18,13 @@ count = 2; // Update to new value
// count = false;
```
-Java is an object-oriented language and requires all functions to be defined in a _class_, which are defined using the [`class` keyword][classes]. A function within a class is referred to as a _method_. Each [method][methods] can have zero or more parameters. All parameters must be explicitly typed, there is no type inference for parameters. Similarly, the return type must also be made explicit. Values are returned from functions using the [`return` keyword][return]. To allow a method to be called by other classes, the `public` access modifier must be added.
+Java is an object-oriented language and requires all functions to be defined in a _class_, which are defined using the [`class` keyword][classes].
+A function within a class is referred to as a _method_.
+Each [method][methods] can have zero or more parameters.
+All parameters must be explicitly typed, there is no type inference for parameters.
+Similarly, the return type must also be made explicit.
+Values are returned from functions using the [`return` keyword][return].
+To allow a method to be called by other classes, the `public` access modifier must be added.
```java
class Calculator {
@@ -32,7 +40,8 @@ Invoking a method is done by specifying its class and method name and passing ar
int sum = new Calculator().add(1, 2);
```
-If a method does not use any class _state_ (which is the case in this exercise), the method can be made _static_ using the `static` modifier. Similarly, if a class only has static methods, it too can be made static using the `static` modifier.
+If a method does not use any class _state_ (which is the case in this exercise), the method can be made _static_ using the `static` modifier.
+Similarly, if a class only has static methods, it too can be made static using the `static` modifier.
```java
static class Calculator {
@@ -44,7 +53,8 @@ static class Calculator {
Scope in Java is defined between the `{` and `}` characters.
-Java supports two types of [comments][comments]. Single line comments are preceded by `//` and multiline comments are inserted between `/*` and `*/`.
+Java supports two types of [comments][comments].
+Single line comments are preceded by `//` and multiline comments are inserted between `/*` and `*/`.
Integer values are defined as one or more (consecutive) digits and support the [default mathematical operators][operators].
diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md
index 65fe8a965..e38461c07 100644
--- a/concepts/basics/introduction.md
+++ b/concepts/basics/introduction.md
@@ -1,22 +1,28 @@
# Introduction
-Java is a statically-typed language, which means that everything has a type at compile-time. Assigning a value to a name is referred to as defining a variable. A variable is defined by explicitly specifying its type.
+Java is a statically-typed language, which means that the type of a variable is known at compile-time.
+Assigning a value to a name is referred to as defining a variable.
+A variable is defined by explicitly specifying its type.
```java
int explicitVar = 10;
```
-Updating a variable's value is done through the `=` operator. Once defined, a variable's type can never change.
+Updating a variable's value is done through the `=` operator.
+Here, `=` does not represent mathematical equality.
+It simply assigns a value to a variable.
+Once defined, a variable's type can never change.
```java
int count = 1; // Assign initial value
count = 2; // Update to new value
-// Compiler error when assigning different type
+// Compiler error when assigning a different type
// count = false;
```
-Java is an [object-oriented language][object-oriented-programming] and requires all functions to be defined in a _class_. The `class` keyword is used to define a class.
+Java is an [object-oriented language][object-oriented-programming] and requires all functions to be defined in a _class_.
+The `class` keyword is used to define a class.
```java
class Calculator {
@@ -24,7 +30,12 @@ class Calculator {
}
```
-A function within a class is referred to as a _method_. Each method can have zero or more parameters. All parameters must be explicitly typed, there is no type inference for parameters. Similarly, the return type must also be made explicit. Values are returned from functions using the `return` keyword. To allow a method to be called by other classes, the `public` access modifier must be added.
+A function within a class is referred to as a _method_.
+Each method can have zero or more parameters.
+All parameters must be explicitly typed, there is no type inference for parameters.
+Similarly, the return type must also be made explicit.
+Values are returned from methods using the `return` keyword.
+To allow a method to be called by other classes, the `public` access modifier must be added.
```java
class Calculator {
@@ -34,14 +45,15 @@ class Calculator {
}
```
-Invoking a method is done by specifying its class and method name and passing arguments for each of the method's parameters.
+Invoking/calling a method is done by specifying its class and method name and passing arguments for each of the method's parameters.
```java
-int sum = new Calculator().add(1, 2);
+int sum = new Calculator().add(1, 2); // here the "add" method has been called to perform the task of addition
```
Scope in Java is defined between the `{` and `}` characters.
-Java supports two types of comments. Single line comments are preceded by `//` and multiline comments are inserted between `/*` and `*/`.
+Java supports two types of comments.
+Single line comments are preceded by `//` and multiline comments are inserted between `/*` and `*/`.
[object-oriented-programming]: https://docs.oracle.com/javase/tutorial/java/javaOO/index.html
diff --git a/concepts/bit-manipulation/.meta/config.json b/concepts/bit-manipulation/.meta/config.json
new file mode 100644
index 000000000..1f0e75736
--- /dev/null
+++ b/concepts/bit-manipulation/.meta/config.json
@@ -0,0 +1,7 @@
+{
+ "blurb": "Perform bitwise operations using the bitwise operators.",
+ "authors": [
+ "kahgoh"
+ ],
+ "contributors": []
+}
diff --git a/concepts/bit-manipulation/about.md b/concepts/bit-manipulation/about.md
new file mode 100644
index 000000000..b3ee81447
--- /dev/null
+++ b/concepts/bit-manipulation/about.md
@@ -0,0 +1,93 @@
+# About Bit Manipulation
+
+Java has operators for manipulating the bits of an [integral type][integral-type] (a `byte`, `short`, `int`, `long` or `char`).
+
+## Shift operators
+
+Use `<<` to shift bits to the left and `>>` to shift to the right.
+
+```java
+// Shift two places to the left
+0b0000_1011 << 2;
+// # => 0b0010_1100
+
+// Shift two places to the right
+0b0000_1011 >> 2;
+// # => 0b0000_0010
+```
+
+`>>` is also called a [signed shift or arithmetic shift][arithmetic-shift] operator because the bit it is inserts is same as its sign bit.
+This is the left most bit and is 0 if the value is positive or 1 when negative.
+Shifting to the left with `<<` always inserts 0s on the right hand side.
+
+```java
+// Shift 2 places to the right preserves the sign
+// The binary value is two's complement of 0b0000_0110 (or 0x00000026), so the binary representation will be
+// 1000_0000_0000_0000_0000_0000_0010_0110
+int value = -0x7FFFFFDA;
+
+// Shift two places to the right, preserving the sign bit
+value >> 2;
+// # => 1110_0000_0000_0000_0000_0000_0000_1001
+```
+
+Use `>>>` instead when 0s are to be inserted when shifting to the right.
+Inserting 0s when shifting is also known as a [logical shift][logical-shift].
+
+```java
+// Shift two places to the right, inserting 0s on the left
+value >>> 2;
+// # => 0010_0000_0000_0000_0000_0000_0000_1001
+```
+
+## Bitwise Operations
+
+### Bitwise AND
+
+The bitwise AND (`&`) operator takes two values and performs an AND on each bit.
+It compares each bit from the first value with the bit in the same position from the second value.
+If they are both 1, then the result's bit is 1.
+Otherwise, the result's bit is 0.
+
+```java
+0b0110_0101 & 0b0011_1100;
+// # => 0b0010_0100
+```
+
+### Bitwise OR
+
+The bitwise OR (`|`) operator takes two values and performs an OR on each bit.
+It compares each bit from the first value with the bit in the same position from the second value.
+If either bit is 1, the result's bit is 1.
+Otherwise, it is 0.
+
+```java
+0b0110_0101 | 0b0011_1100;
+// # => 0b0111_1101
+```
+
+### Bitwise XOR
+
+The bitwise XOR operator (`^`) performs a bitwise XOR on two values.
+Like the bitwise AND and bitwise OR operators, it compares each bit from the first value against the bit in the same position from the second value.
+If only one of them is 1, the resulting bit is 1.
+Otherwise, it is 0.
+
+```java
+0b0110_0101 ^ 0b0011_1100;
+// # => 0b0101_1001
+```
+
+### Bitwise NOT(`~`)
+
+Lastly, the bitwise NOT operator (`~`) flips each bit.
+Unlike the earlier operators, this is a unary operator, acting only on one value.
+
+```java
+~0b0110_0101;
+// # => 0b1001_1010
+```
+
+[integral-type]: https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.2
+[arithmetic-shift]: https://en.wikipedia.org/wiki/Arithmetic_shift
+[logical-shift]: https://en.wikipedia.org/wiki/Logical_shift
diff --git a/concepts/bit-manipulation/introduction.md b/concepts/bit-manipulation/introduction.md
new file mode 100644
index 000000000..25174e342
--- /dev/null
+++ b/concepts/bit-manipulation/introduction.md
@@ -0,0 +1,87 @@
+# Introduction to Bit Manipulation
+
+Java has operators for manipulating the bits of a `byte`, `short`, `int`, `long` or `char`.
+
+## Shift operators
+
+Use `<<` to shift bits to the left and `>>` to shift to the right.
+
+```java
+// Shift two places to the left
+0b0000_1011 << 2;
+// # => 0b0010_1100
+
+// Shift two places to the right
+0b0000_1011 >> 2;
+// # => 0b0000_0010
+```
+
+The `<<` operator always inserts 0s on the right hand side.
+However, `>>` inserts the same bit as the left most bit (1 if the number is negative or 0 if positive).
+
+```java
+// Shift 2 places to the right preserves the sign
+// This is a negative value, whose binary representation is
+// 1000_0000_0000_0000_0000_0000_0010_0110
+int value = -0x7FFFFFDA;
+
+// Shift two places to the right, preserving the sign bit
+value >> 2;
+// # => 1110_0000_0000_0000_0000_0000_0000_1001
+```
+
+Use `>>>` instead when 0s are to be inserted when shifting to the right.
+
+```java
+// Shift two places to the right, inserting 0s on the left
+value >>> 2;
+// # => 0010_0000_0000_0000_0000_0000_0000_1001
+```
+
+## Bitwise Operations
+
+### Bitwise AND
+
+The bitwise AND (`&`) operator takes two values and performs an AND on each bit.
+It compares each bit from the first value with the bit in the same position from the second value.
+If they are both 1, then the result's bit is 1.
+Otherwise, the result's bit is 0.
+
+```java
+0b0110_0101 & 0b0011_1100;
+// # => 0b0010_0100
+```
+
+### Bitwise OR
+
+The bitwise OR (`|`) operator takes two values and performs an OR on each bit.
+It compares each bit from the first value with the bit in the same position from the second value.
+If either bit is 1, the result's bit is 1.
+Otherwise, it is 0.
+
+```java
+0b0110_0101 | 0b0011_1100;
+// # => 0b0111_1101
+```
+
+### Bitwise XOR
+
+The bitwise XOR operator (`^`) performs a bitwise XOR on two values.
+Like the bitwise AND and bitwise OR operators, it compares each bit from the first value against the bit in the same position from the second value.
+If only one of them is 1, the resulting bit is 1.
+Otherwise, it is 0.
+
+```java
+0b0110_0101 ^ 0b0011_1100;
+// # => 0b0101_1001
+```
+
+### Bitwise NOT(`~`)
+
+Lastly, the bitwise NOT operator (`~`) flips each bit.
+Unlike the earlier operators, this is a unary operator, acting only on one value.
+
+```java
+~0b0110_0101;
+// # => 0b1001_1010
+```
diff --git a/concepts/bit-manipulation/links.json b/concepts/bit-manipulation/links.json
new file mode 100644
index 000000000..3d7941b67
--- /dev/null
+++ b/concepts/bit-manipulation/links.json
@@ -0,0 +1,14 @@
+[
+ {
+ "url": "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op3.html",
+ "description": "The Java Tutorials - Bitwise and Bit Shift Operators"
+ },
+ {
+ "url": "https://dev.java/learn/language-basics/using-operators/#bitwise-bitshift",
+ "description": "Using Operators in Your Programs: Bitwise and Bit Shift Operators"
+ },
+ {
+ "url": "https://dev.java/learn/language-basics/all-operators/#bitwise-bitshift",
+ "description": "Summary of Operators: Bitwise and Bit Shift Operators"
+ }
+]
diff --git a/concepts/booleans/about.md b/concepts/booleans/about.md
index 621dded37..4edee1936 100644
--- a/concepts/booleans/about.md
+++ b/concepts/booleans/about.md
@@ -2,15 +2,16 @@
Booleans in Java are represented by the `boolean` type, which values can be either `true` or `false`.
-Java supports three [boolean operators][operators]:
+Java supports three [boolean operators][operators]:
-- `!` (NOT): negates the boolean
-- `&&` (AND): takes two booleans and results in true if they're both true
-- `||` (OR): results in true if any of the two booleans is true
+- `!` (NOT): negates the boolean
+- `&&` (AND): takes two booleans and results in true if they're both true
+- `||` (OR): results in true if any of the two booleans is true
The `&&` and `||` operators use _short-circuit evaluation_, which means that the right-hand side of the operator is only evaluated when needed.
-These are also known as conditional or logical operators. `!` is sometimes classified as a bitwise operation in the documentation but it has the conventional _NOT_ semantics.
+These are also known as conditional or logical operators.
+`!` is sometimes classified as a bitwise operation in the documentation but it has the conventional _NOT_ semantics.
```java
true || false // => true
@@ -22,7 +23,9 @@ true && true // => true
!false // => true
```
-The three boolean operators each have a different [_operator precedence_][precedence]. As a consequence, they are evaluated in this order: `not` first, `&&` second, and finally `||`. If you want to 'escape' these rules, you can enclose a boolean expression in parentheses (`()`), as the parentheses have an even higher operator precedence.
+The three boolean operators each have a different [_operator precedence_][precedence].
+As a consequence, they are evaluated in this order: `not` first, `&&` second, and finally `||`.
+If you want to 'escape' these rules, you can enclose a boolean expression in parentheses (`()`), as the parentheses have an even higher operator precedence.
```java
!true && false // => false
diff --git a/concepts/booleans/introduction.md b/concepts/booleans/introduction.md
index e31557333..348f1e43c 100644
--- a/concepts/booleans/introduction.md
+++ b/concepts/booleans/introduction.md
@@ -8,7 +8,7 @@ Java supports three boolean operators:
- `&&` (AND): takes two booleans and results in true if they're both true
- `||` (OR): results in true if any of the two booleans is true
-**Examples**
+## Examples
```java
!true // => false
diff --git a/concepts/chars/.meta/config.json b/concepts/chars/.meta/config.json
index 7f86b573e..bcf21a72c 100644
--- a/concepts/chars/.meta/config.json
+++ b/concepts/chars/.meta/config.json
@@ -3,5 +3,7 @@
"authors": [
"ystromm"
],
- "contributors": []
+ "contributors": [
+ "kahgoh"
+ ]
}
diff --git a/concepts/chars/about.md b/concepts/chars/about.md
index b9de623d9..d6ae4bd75 100644
--- a/concepts/chars/about.md
+++ b/concepts/chars/about.md
@@ -1,7 +1,108 @@
# About
-`char`s are generally easy to use. They can be extracted from strings, added back
-(by means of a string builder), defined and initialised using literals with single quotes, as in `char ch = 'A';`
-, assigned and compared.
+The Java `char` primitive type is a 16 bit representation of a single Unicode character.
-The Character class encapsulates the char value.
+~~~~exercism/note
+The `char` type is based on the [original Unicode specification][unicode-specification], which used 16 bits to represent characters.
+This is enough to cover most of the common letters and covers characters in the range 0x0000 to 0xFFFF.
+The specification has since expanded the range of possible characters up to 0x01FFFF.
+
+[unicode-specification]: https://www.unicode.org/versions/Unicode1.0.0/
+~~~~
+
+Multiple `char`s can comprise a string, such as `"word"`, or `char`s can be processed independently.
+A `char` literal is surrounded by single quotes (e.g. `'A'`).
+
+```java
+char lowerA = 'a';
+char upperB = 'B';
+```
+
+## Getting the `char`s of a `String`
+
+The `String.toCharArray` method returns a String's chars as an array.
+As mentioned in [arrays][concept-arrays], you can use a `for` loop to iterate over the array.
+
+```java
+String text = "Hello";
+char[] asArray = text.toCharArray();
+
+for (char ch: asArray) {
+ System.out.println(ch);
+}
+
+// Outputs:
+// H
+// e
+// l
+// l
+// o
+```
+
+## The [Character][docs-character] class
+
+There are many builtin library methods to inspect and manipulate `char`s.
+These can be found as static methods of the [`java.lang.Character`][docs-character] class.
+Here are some examples:
+
+```java
+Character.isWhitespace(' '); // true
+Character.isWhitespace('#'); // false
+
+Character.isLetter('a'); // true
+Character.isLetter('3'); // false
+
+Character.isDigit('6'); // true
+Character.isDigit('?'); // false
+```
+
+~~~~exercism/note
+Some methods in the Character class have an overload so that it can take either an `char` or `int`.
+For example, `isDigit` has one that accepts a [`char`][is-digit-char] and another an [`int`][is-digit-int].
+As mentioned earlier, the `char` type can only represent the characters in the range from 0x0000 to 0xFFFF.
+The `int`, however, can represent all characters, hence the `int` overloads.
+
+[is-digit-char]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#isDigit(char)
+[is-digit-int]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html#isDigit(int)
+~~~~
+
+## Adding a `char` to a `String`
+
+The `+` operator can be used to add a `char` to a `String`.
+
+```java
+'a' + " banana" // => "a banana"
+"banana " + 'a' // => "banana a"
+```
+
+~~~~exercism/caution
+Becareful _not_ to use `+` to join two `char`s together to form a `String`!
+Adding two `char`s this way gives an `int`, _not_ a `String`!
+For example:
+
+```java
+'b' + 'c';
+// => 197 (not the String "bc")
+```
+
+This is because Java promotes the `char` to an `int` (see [4.2 Primitive Types and Values ][jls-primitives] of the [Java Language Specification][jls-main]).
+
+[jls-main]: https://docs.oracle.com/javase/specs/jls/se21/html/
+[jls-primitives]: https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.2
+~~~~
+
+However, when there are many characters to be added, it can be more efficient to use a [`StringBuilder`][docs-stringBuilder] instead:
+
+```java
+StringBuilder builder = new StringBuilder();
+builder.append('a');
+builder.append('b');
+builder.append('c');
+
+String builtString = builder.toString();
+// => abc
+```
+
+[concept-arrays]: https://exercism.org/tracks/java/concepts/arrays
+[docs-character]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html
+[docs-stringBuilder]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/StringBuilder.html
diff --git a/concepts/chars/introduction.md b/concepts/chars/introduction.md
index f2f8e9f0d..bb80baba7 100644
--- a/concepts/chars/introduction.md
+++ b/concepts/chars/introduction.md
@@ -1,19 +1,87 @@
# Introduction
-The Java `char` type represents the smallest addressable components of text.
-Multiple `char`s can comprise a string such as `"word"` or `char`s can be
-processed independently. Their literals have single quotes e.g. `'A'`.
+## chars
-Java `char`s support Unicode encoding, so in addition to the Latin character set
-most modern writing systems can be represented,
-e.g. Greek `'β'`.
+The Java `char` primitive type is a 16 bit representation of a single character.
+Multiple `char`s can comprise a string, such as `"word"`, or `char`s can be processed independently.
+A `char` literal is surrounded by single quotes (e.g. `'A'`).
-There are many builtin library methods to inspect and manipulate `char`s. These
-can be found as static methods of the `java.lang.Character` class.
+```java
+char lowerA = 'a';
+char upperB = 'B';
+```
-`char`s are sometimes used in conjunction with a `StringBuilder` object.
-This object has methods that allow a string to be constructed
-character by character and manipulated. At the end of the process
-`toString()` can be called on it to output a complete string.
+## Getting the `char`s of a `String`
-In order to get chars from the String one could use `String.charAt(index)` method.
+The `String.toCharArray` method returns a String's chars as an array.
+As mentioned in arrays, you can use a `for` loop to iterate over the array.
+
+```java
+String text = "Hello";
+char[] asArray = text.toCharArray();
+
+for (char ch: asArray) {
+ System.out.println(ch);
+}
+
+// Outputs:
+// H
+// e
+// l
+// l
+// o
+```
+
+## The Character class
+
+There are many builtin library methods to inspect and manipulate `char`s.
+These can be found as static methods of the `java.lang.Character` class.
+Here are some examples:
+
+```java
+Character.isWhitespace(' '); // true
+Character.isWhitespace('#'); // false
+
+Character.isLetter('a'); // true
+Character.isLetter('3'); // false
+
+Character.isDigit('6'); // true
+Character.isDigit('?'); // false
+```
+
+## Adding a `char` to a `String`
+
+The `+` operator can be used to add a `char` to a `String`.
+
+```java
+'a' + " banana" // => "a banana"
+"banana " + 'a' // => "banana a"
+```
+
+~~~~exercism/caution
+Becareful _not_ to use `+` to join two `char`s together to form a `String`!
+Adding two `char`s this way gives an `int`, _not_ a `String`!
+For example:
+
+```java
+'b' + 'c';
+// => 197 (not the String "bc")
+```
+
+This is because Java promotes the `char` to an `int` (see [4.2 Primitive Types and Values ][jls-primitives] of the [Java Language Specification][jls-main]).
+
+[jls-main]: https://docs.oracle.com/javase/specs/jls/se21/html/
+[jls-primitives]: https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.2
+~~~~
+
+However, when there are many characters to be added, it can be more efficient to use a `StringBuilder` instead:
+
+```java
+StringBuilder builder = new StringBuilder();
+builder.append('a');
+builder.append('b');
+builder.append('c');
+
+String builtString = builder.toString();
+// => abc
+```
diff --git a/concepts/chars/links.json b/concepts/chars/links.json
index bf2b4a182..b78281499 100644
--- a/concepts/chars/links.json
+++ b/concepts/chars/links.json
@@ -1,12 +1,10 @@
[
{
- "url":"https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html",
+ "url":"https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Character.html",
"description":"javadoc"
},
{
- "url": "https://docs.oracle.com/javase/tutorial/i18n/text/unicode.html",
- "description": "unicode"
+ "url": "https://dev.java/learn/numbers-strings/characters/",
+ "description": "characters"
}
-
-
]
diff --git a/concepts/classes/about.md b/concepts/classes/about.md
index 722952fec..e0a173e76 100644
--- a/concepts/classes/about.md
+++ b/concepts/classes/about.md
@@ -1,17 +1,20 @@
# About
-The primary object-oriented construct in Java is the _class_, which is a combination of data ([_fields_][fields]), also known as instance variables, and behavior ([_methods_][methods]). The fields and methods of a class are known as its _members_.
+The primary object-oriented construct in Java is the _class_, which is a combination of data ([_fields_][fields]), also known as instance variables, and behavior ([_methods_][methods]).
+The fields and methods of a class are known as its _members_.
Access to members can be controlled through access modifiers, the two most common ones being:
- [`public`][public]: the member can be accessed by any code (no restrictions).
- [`private`][private]: the member can only be accessed by code in the same class.
-In Java if no access modifier is specified, the default is _package visibility_. In this case, the member is visible to all classes defined into the same package.
+In Java if no access modifier is specified, the default is _package visibility_.
+In this case, the member is visible to all classes defined into the same package.
The above-mentioned grouping of related data and behavior plus restricting access to members is known as [_encapsulation_][encapsulation], which is one of the core object-oriented concepts.
-You can think of a class as a template for creating instances of that class. To [create an instance of a class][creating-objects] (also known as an _object_), the [`new` keyword][new] is used:
+You can think of a class as a template for creating instances of that class.
+To [create an instance of a class][creating-objects] (also known as an _object_), the [`new` keyword][new] is used:
```java
class Car {
@@ -34,7 +37,9 @@ class Car {
}
```
-One can optionally assign an initial value to a field. If a field does _not_ specify an initial value, it will be set to its type's [default value][default-values]. An instance's field values can be accessed and updated using dot-notation.
+One can optionally assign an initial value to a field.
+If a field does _not_ specify an initial value, it will be set to its type's [default value][default-values].
+An instance's field values can be accessed and updated using dot-notation.
```java
class Car {
@@ -53,7 +58,8 @@ newCar.year; // => 0
newCar.year = 2018;
```
-Private fields are usually updated as a side effect of calling a method. Such methods usually don't return any value, in which case the return type should be [`void`][void]:
+Private fields are usually updated as a side effect of calling a method.
+Such methods usually don't return any value, in which case the return type should be [`void`][void]:
```java
class CarImporter {
@@ -67,9 +73,11 @@ class CarImporter {
}
```
-Note that is not customary to use public fields in Java classes. Private fields are typically used which are accessed through [_getters_ and _setters_][so-getters-setters].
+Note that is not customary to use public fields in Java classes.
+Private fields are typically used which are accessed through [_getters_ and _setters_][so-getters-setters].
-Within a class, the [`this` keyword][this] will refer to the current class. This is especially useful if a parameter has the same name as a field:
+Within a class, the [`this` keyword][this] will refer to the current class.
+This is especially useful if a parameter has the same name as a field:
```java
class CarImporter {
diff --git a/concepts/classes/introduction.md b/concepts/classes/introduction.md
index 774eefc4f..08c04a5aa 100644
--- a/concepts/classes/introduction.md
+++ b/concepts/classes/introduction.md
@@ -1,13 +1,15 @@
# Introduction
-The primary object-oriented construct in Java is the _class_, which is a combination of data (_fields_) and behavior (_methods_). The fields and methods of a class are known as its _members_.
+The primary object-oriented construct in Java is the _class_, which is a combination of data (_fields_) and behavior (_methods_).
+The fields and methods of a class are known as its _members_.
Access to members can be controlled through access modifiers, the two most common ones being:
- `public`: the member can be accessed by any code (no restrictions).
- `private`: the member can only be accessed by code in the same class.
-You can think of a class as a template for creating instances of that class. To create an instance of a class (also known as an _object_), the `new` keyword is used:
+You can think of a class as a template for creating instances of that class.
+To create an instance of a class (also known as an _object_), the `new` keyword is used:
```java
class Car {
@@ -30,7 +32,9 @@ class Car {
}
```
-One can optionally assign an initial value to a field. If a field does _not_ specify an initial value, it wll be set to its type's default value. An instance's field values can be accessed and updated using dot-notation.
+One can optionally assign an initial value to a field.
+If a field does _not_ specify an initial value, it will be set to its type's default value.
+An instance's field values can be accessed and updated using dot-notation.
```java
class Car {
@@ -49,7 +53,8 @@ newCar.year; // => 0
newCar.year = 2018;
```
-Private fields are usually updated as a side effect of calling a method. Such methods usually don't return any value, in which case the return type should be `void`:
+Private fields are usually updated as a side effect of calling a method.
+Such methods usually don't return any value, in which case the return type should be `void`:
```java
class CarImporter {
diff --git a/concepts/conditionals-if/.meta/config.json b/concepts/conditionals-if/.meta/config.json
deleted file mode 100644
index fd0cc4d7a..000000000
--- a/concepts/conditionals-if/.meta/config.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "blurb": "Java supports various logical operations and conditionals like the if statement.",
- "authors": [
- "TalesDias"
- ],
- "contributors": []
-}
diff --git a/concepts/conditionals-if/about.md b/concepts/conditionals-if/about.md
deleted file mode 100644
index e67f6e3dc..000000000
--- a/concepts/conditionals-if/about.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# About
-
-## Logical Operators
-
-Java supports three [logical operators][logical-operators] `&&` (AND), `||` (OR), and `!` (NOT).
-
-## If statement
-
-The underlying type of any conditional operation is the `boolean` type, which can have the value of `true` or `false`. Conditionals are often used as flow control mechanisms to check for various conditions. For checking a particular case an [`if` statement][if-statement] can be used, which executes its code if the underlying condition is `true` like this:
-
-```java
-int val;
-
-if(val == 9) {
- // conditional code
-}
-```
-
-In scenarios involving more than one case many `if` statements can be chained together using the `else if` and `else` statements.
-
-```java
-if(val == 10) {
- // conditional code
-} else if(val == 17) {
- // conditional code
-} else {
- // executes when val is different from 10 and 17
-}
-```
-
-## Switch statement
-
-Java also provides a [`switch` statement][switch-statement] for scenarios with multiple options. It can be used to switch on a variable's content as a replacement for simple `if ... else if` statements. A switch statement can have a `default` case which is executed if no other case applies.
-
-In Java you can't use any type as the value in a `switch`, only integer and enumerated data types, plus the `String` class are allowed.
-
-If there are three or more cases in a single `if` (e.g. `if ... else if ... else`), it should be replaced by a `switch` statement. A `switch` with a single case should be replaced by an `if` statement.
-
-```java
-int val;
-
-// switch statement on variable content
-switch(val) {
- case 1:
- // conditional code
- break;
- case 2: case 3: case 4:
- // conditional code
- break;
- default:
- // if all cases fail
- break;
-}
-```
-
- Note: Make sure the expression in the switch statement is not null, otherwise a NullPointerException will be thrown
-
-To learn more about this topic it is recommended to check these sources:
-
-- [A refresh of Ternary operators][example-ternary]
-- [If/Else detailed with flowcharts][example-ifelse-flowcharts]
-- [Switch examples][example-switch]
-
-[logical-operators]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op2.html
-[if-statement]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/if.html
-[switch-statement]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
-[example-ifelse-flowcharts]: https://www.javatpoint.com/java-if-else
-[example-ternary]: https://www.programiz.com/java-programming/ternary-operator
-[example-switch]: https://www.geeksforgeeks.org/switch-statement-in-java/
diff --git a/concepts/conditionals-if/introduction.md b/concepts/conditionals-if/introduction.md
deleted file mode 100644
index 3011f35f8..000000000
--- a/concepts/conditionals-if/introduction.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# Introduction
-
-## Logical Operators
-
-Java supports the three logical operators `&&` (AND), `||` (OR), and `!` (NOT).
-
-## If statement
-
-The underlying type of any conditional operation is the `boolean` type, which can have the value of `true` or `false`. Conditionals are often used as flow control mechanisms to check for various conditions. For checking a particular case an `if` statement can be used, which executes its code if the underlying condition is `true` like this:
-
-```java
-int val;
-
-if(val == 9) {
- // conditional code
-}
-```
-
-In scenarios involving more than one case many `if` statements can be chained together using the `else if` and `else` statements.
-
-```java
-if(val == 10) {
- // conditional code
-} else if(val == 17) {
- // conditional code
-} else {
- // executes when val is different from 10 and 17
-}
-```
-
-## Switch statement
-
-Java also provides a `switch` statement for scenarios with multiple options.
-
-```java
-int val;
-
-// switch statement on variable content
-switch(val) {
- case 1:
- // conditional code
- break;
- case 2: case 3: case 4:
- // conditional code
- break;
- default:
- // if all cases fail
- break;
-}
-```
diff --git a/concepts/conditionals-if/links.json b/concepts/conditionals-if/links.json
deleted file mode 100644
index 0ba39adb6..000000000
--- a/concepts/conditionals-if/links.json
+++ /dev/null
@@ -1,26 +0,0 @@
-[
- {
- "url": "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op2.html",
- "description": "logical-operators"
- },
- {
- "url": "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/if.html",
- "description": "if-statement"
- },
- {
- "url": "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html",
- "description": "switch-statement"
- },
- {
- "url": "https://www.programiz.com/java-programming/ternary-operator",
- "description": "example-ternary"
- },
- {
- "url": "https://www.javatpoint.com/java-if-else",
- "description": "example-ifelse-flowcharts"
- },
- {
- "url": "https://www.geeksforgeeks.org/switch-statement-in-java/",
- "description": "example-switch"
- }
-]
diff --git a/concepts/constructors/about.md b/concepts/constructors/about.md
index 6f8be6668..0ad5b101e 100644
--- a/concepts/constructors/about.md
+++ b/concepts/constructors/about.md
@@ -1,6 +1,8 @@
# About
-Creating an instance of a _class_ is done by calling its [_constructor_][constructors] through the [`new` operator][new]. A constructor is a special type of method whose goal is to initialize a newly created instance. Constructors look like regular methods, but without a return type and with a name that matches the class's name.
+Creating an instance of a _class_ is done by calling its [_constructor_][constructors] through the [`new` operator][new].
+A constructor is a special type of method whose goal is to initialize a newly created instance.
+Constructors look like regular methods, but without a return type and with a name that matches the class's name.
```java
class Library {
@@ -15,7 +17,9 @@ class Library {
var library = new Library();
```
-Like regular methods, constructors can have parameters. Constructor parameters are usually stored as (private) [fields][fields] to be accessed later, or else used in some one-off calculation. Arguments can be passed to constructors just like passing arguments to regular methods.
+Like regular methods, constructors can have parameters.
+Constructor parameters are usually stored as (private) [fields][fields] to be accessed later, or else used in some one-off calculation.
+Arguments can be passed to constructors just like passing arguments to regular methods.
```java
class Building {
@@ -32,7 +36,8 @@ class Building {
var largeBuilding = new Building(55, 6.2)
```
-Specifying a constructor is optional. If no constructor is specified, a parameterless constructor is generated by the compiler:
+Specifying a constructor is optional.
+If no constructor is specified, a parameterless constructor is generated by the compiler:
```java
class Elevator {
@@ -42,7 +47,8 @@ class Elevator {
var elevator = new Elevator();
```
-If fields have an initial value assigned to them, the compiler will output code in which the assignment is actually done inside the constructor. The following class declarations are thus equivalent (functionality-wise):
+If fields have an initial value assigned to them, the compiler will output code in which the assignment is actually done inside the constructor.
+The following class declarations are thus equivalent (functionality-wise):
```java
class UsingFieldInitialization {
diff --git a/concepts/constructors/introduction.md b/concepts/constructors/introduction.md
index 22cf45ec2..69c09e5f3 100644
--- a/concepts/constructors/introduction.md
+++ b/concepts/constructors/introduction.md
@@ -1,6 +1,8 @@
# Introduction
-Creating an instance of a _class_ is done by calling its _constructor_ through the `new` operator. A constructor is a special type of method whose goal is to initialize a newly created instance. Constructors look like regular methods, but without a return type and with a name that matches the class's name.
+Creating an instance of a _class_ is done by calling its _constructor_ through the `new` operator.
+A constructor is a special type of method whose goal is to initialize a newly created instance.
+Constructors look like regular methods, but without a return type and with a name that matches the class's name.
```java
class Library {
@@ -16,7 +18,9 @@ class Library {
var library = new Library();
```
-Like regular methods, constructors can have parameters. Constructor parameters are usually stored as (private) fields to be accessed later, or else used in some one-off calculation. Arguments can be passed to constructors just like passing arguments to regular methods.
+Like regular methods, constructors can have parameters.
+Constructor parameters are usually stored as (private) fields to be accessed later, or else used in some one-off calculation.
+Arguments can be passed to constructors just like passing arguments to regular methods.
```java
class Building {
diff --git a/concepts/datetime/.meta/config.json b/concepts/datetime/.meta/config.json
new file mode 100644
index 000000000..f68620354
--- /dev/null
+++ b/concepts/datetime/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "There are several classes in Java to work with dates and time.",
+ "authors": ["sanderploegsma"],
+ "contributors": []
+}
diff --git a/concepts/datetime/about.md b/concepts/datetime/about.md
new file mode 100644
index 000000000..8ab1fbad6
--- /dev/null
+++ b/concepts/datetime/about.md
@@ -0,0 +1,124 @@
+# About
+
+The `java.time` package introduced in Java 8 contains several classes to work with dates and time.
+
+## `LocalDate`
+
+The [`java.time.LocalDate`][localdate-docs] class represents a date without a time-zone in the [ISO-8601 calendar system][iso-8601], such as `2007-12-03`:
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+```
+
+Dates can be compared to other dates:
+
+```java
+LocalDate date1 = LocalDate.of(2007, 12, 3);
+LocalDate date2 = LocalDate.of(2007, 12, 4);
+
+date1.isBefore(date2);
+// => true
+
+date1.isAfter(date2);
+// => false
+```
+
+A `LocalDate` instance has getters to retrieve time portions from it:
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+
+date.getYear();
+// => 2007
+
+date.getMonthValue();
+// => 12
+
+date.getDayOfMonth();
+// => 3
+```
+
+A `LocalDate` instance has methods to add time units to it.
+
+```exercism/note
+These methods return a _new_ `LocalDate` instance and do not update the existing instance, as the `LocalDate` class is immutable.
+```
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+
+date.plusDays(3);
+// => 2007-12-06
+
+date.plusMonths(1);
+// => 2008-01-03
+
+date.plusYears(1);
+// => 2008-12-03
+```
+
+## `LocalDateTime`
+
+The [`java.time.LocalDateTime`][localdatetime-docs] class represents a date-time without a time-zone in the [ISO-8601 calendar system][iso-8601], such as `2007-12-03T10:15:30`:
+
+```java
+LocalDateTime datetime = LocalDateTime.of(2007, 12, 3, 10, 15, 30);
+
+datetime.getYear();
+// => 2007
+
+datetime.getMonthValue();
+// => 12
+
+datetime.getDayOfMonth();
+// => 3
+
+datetime.getHours();
+// => 10
+
+datetime.getMinutes();
+// => 15
+
+datetime.getSeconds();
+// => 30
+```
+
+Like the `LocalDate` class, a `LocalDateTime` instance has the same methods to compare to other `LocalDateTime`s and to add time units to it.
+
+It is also possible to convert a `LocalDate` instance into a `LocalDateTime`:
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+LocalDateTime datetime = date.atTime(10, 15, 30);
+datetime.toString();
+// => "2007-12-03T10:15:30"
+```
+
+## Formatting datetimes
+
+Both `LocalDate` and `LocalDateTime` use the [ISO-8601][iso-8601] standard notation when converting from and to a `String`.
+
+```java
+LocalDateTime datetime = LocalDateTime.of(2007, 12, 3, 10, 15, 30);
+LocalDateTime parsed = LocalDateTime.parse("2007-12-03T10:15:30");
+
+datetime.isEqual(parsed);
+// => true
+```
+
+Attempting to parse a `LocalDate` or `LocalDateTime` from a `String` like this using a different format is not possible.
+Instead, to format dates using a custom format, you should use the [`java.time.format.DateTimeFormatter`][datetimeformatter-docs]:
+
+```java
+DateTimeFormatter parser = DateTimeFormatter.ofPattern("dd/MM/yyyy");
+LocalDate date = LocalDate.parse("03/12/2007", formatter);
+
+DateTimeFormatter printer = DateTimeFormatter.ofPattern("MMMM d, yyyy");
+printer.format(date);
+// => "December 3, 2007"
+```
+
+[iso-8601]: https://en.wikipedia.org/wiki/ISO_8601
+[localdate-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html
+[localdatetime-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html
+[datetimeformatter-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html
diff --git a/concepts/datetime/introduction.md b/concepts/datetime/introduction.md
new file mode 100644
index 000000000..2b9513189
--- /dev/null
+++ b/concepts/datetime/introduction.md
@@ -0,0 +1,89 @@
+# Introduction
+
+The `java.time` package introduced in Java 8 contains several classes to work with dates and time.
+
+## `LocalDate`
+
+The `java.time.LocalDate` class represents a date without a time-zone in the [ISO-8601 calendar system][iso-8601], such as `2007-12-03`:
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+```
+
+Dates can be compared to other dates:
+
+```java
+LocalDate date1 = LocalDate.of(2007, 12, 3);
+LocalDate date2 = LocalDate.of(2007, 12, 4);
+
+date1.isBefore(date2);
+// => true
+
+date1.isAfter(date2);
+// => false
+```
+
+A `LocalDate` instance has getters to retrieve time portions from it:
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+
+date.getDayOfMonth();
+// => 3
+```
+
+A `LocalDate` instance has methods to add time units to it:
+
+```exercism/note
+These methods return a _new_ `LocalDate` instance and do not update the existing instance, as the `LocalDate` class is immutable.
+```
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+
+date.plusDays(3);
+// => 2007-12-06
+```
+
+## `LocalDateTime`
+
+The `java.time.LocalDateTime` class represents a date-time without a time-zone in the [ISO-8601 calendar system][iso-8601], such as `2007-12-03T10:15:30`:
+
+```java
+LocalDateTime datetime = LocalDateTime.of(2007, 12, 3, 10, 15, 30);
+```
+
+You can convert a `LocalDate` instance into a `LocalDateTime`:
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+LocalDateTime datetime = date.atTime(10, 15, 30);
+datetime.toString();
+// => "2007-12-03T10:15:30"
+```
+
+## Formatting datetimes
+
+Both `LocalDate` and `LocalDateTime` use the [ISO-8601][iso-8601] standard notation when converting from and to a `String`.
+
+```java
+LocalDateTime datetime = LocalDateTime.of(2007, 12, 3, 10, 15, 30);
+LocalDateTime parsed = LocalDateTime.parse("2007-12-03T10:15:30");
+
+datetime.isEqual(parsed);
+// => true
+```
+
+Attempting to parse a `LocalDate` or `LocalDateTime` from a `String` like this using a different format is not possible.
+Instead, to format dates using a custom format, you should use the `java.time.format.DateTimeFormatter`:
+
+```java
+DateTimeFormatter parser = DateTimeFormatter.ofPattern("dd/MM/yyyy");
+LocalDate date = LocalDate.parse("03/12/2007", parser);
+
+DateTimeFormatter printer = DateTimeFormatter.ofPattern("MMMM d, yyyy");
+printer.format(date);
+// => "December 3, 2007"
+```
+
+[iso-8601]: https://en.wikipedia.org/wiki/ISO_8601
diff --git a/concepts/datetime/links.json b/concepts/datetime/links.json
new file mode 100644
index 000000000..6e3a8e547
--- /dev/null
+++ b/concepts/datetime/links.json
@@ -0,0 +1,14 @@
+[
+ {
+ "url": "https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html",
+ "description": "LocalDate documentation"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html",
+ "description": "LocalDateTime documentation"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html",
+ "description": "DateTimeFormatter documentation"
+ }
+]
diff --git a/concepts/enums/.meta/config.json b/concepts/enums/.meta/config.json
new file mode 100644
index 000000000..1fb16845f
--- /dev/null
+++ b/concepts/enums/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Enums are useful to create a predefined set of constants.",
+ "authors": ["sanderploegsma"],
+ "contributors": []
+}
diff --git a/concepts/enums/about.md b/concepts/enums/about.md
new file mode 100644
index 000000000..c259d9262
--- /dev/null
+++ b/concepts/enums/about.md
@@ -0,0 +1,120 @@
+# About
+
+An _enum type_ is a special data type that enables for a variable to be a set of predefined constants.
+The variable must be equal to one of the values that have been predefined for it.
+Common examples include compass directions (values of `NORTH`, `SOUTH`, `EAST`, and `WEST`) and the days of the week.
+
+Because they are constants, the names of an enum type's fields are in uppercase letters.
+
+## Defining an enum type
+
+In the Java programming language, you define an enum type by using the `enum` keyword.
+For example, you would specify a days-of-the-week enum type as:
+
+```java
+public enum DayOfWeek {
+ SUNDAY,
+ MONDAY,
+ TUESDAY,
+ WEDNESDAY,
+ THURSDAY,
+ FRIDAY,
+ SATURDAY
+}
+```
+
+You should use enum types any time you need to represent a fixed set of constants.
+That includes natural enum types such as the planets in our solar system and data sets where you know all possible values at compile time - for example, the choices on a menu, command line flags, and so on.
+
+## Using an enum type
+
+Here is some code that shows you how to use the `DayOfWeek` enum defined above:
+
+```java
+public class Shop {
+ public String getOpeningHours(DayOfWeek dayOfWeek) {
+ switch (dayOfWeek) {
+ case MONDAY:
+ case TUESDAY:
+ case WEDNESDAY:
+ case THURSDAY:
+ case FRIDAY:
+ return "9am - 5pm";
+ case SATURDAY:
+ return "10am - 4pm"
+ case SUNDAY:
+ return "Closed.";
+ }
+ }
+}
+```
+
+```java
+var shop = new Shop();
+shop.getOpeningHours(DayOfWeek.WEDNESDAY);
+// => "9am - 5pm"
+```
+
+## Adding methods and fields
+
+Java programming language enum types are much more powerful than their counterparts in other languages.
+The `enum` declaration defines a _class_ (called an _enum type_).
+The enum class body can include methods and other fields:
+
+```java
+public enum Rating {
+ GREAT(5),
+ GOOD(4),
+ OK(3),
+ BAD(2),
+ TERRIBLE(1);
+
+ private final int numberOfStars;
+
+ Rating(int numberOfStars) {
+ this.numberOfStars = numberOfStars;
+ }
+
+ public int getNumberOfStars() {
+ return this.numberOfStars;
+ }
+}
+```
+
+Calling the `getNumberOfStars` method on a member of the `Rating` enum type:
+
+```java
+Rating.GOOD.getNumberOfStars();
+// => 4
+```
+
+## Built-in methods
+
+The compiler automatically adds some special methods when it creates an enum.
+
+For example, they have a static `values` method that returns an array containing all of the values of the enum in the order they are declared:
+
+```java
+for (DayOfWeek dayOfWeek : DayOfWeek.values()) {
+ System.out.println(dayOfWeek.toString());
+}
+```
+
+The snippet above would print the following:
+
+```text
+SUNDAY
+MONDAY
+TUESDAY
+WEDNESDAY
+THURSDAY
+FRIDAY
+SATURDAY
+```
+
+The compiler also adds a static `valueOf` method that returns an enum member based on its name:
+
+```java
+DayOfWeek.valueOf("SUNDAY");
+// => DayOfWeek.SUNDAY
+```
diff --git a/concepts/enums/introduction.md b/concepts/enums/introduction.md
new file mode 100644
index 000000000..61b1786a2
--- /dev/null
+++ b/concepts/enums/introduction.md
@@ -0,0 +1,89 @@
+# Introduction
+
+An _enum type_ is a special data type that enables for a variable to be a set of predefined constants.
+The variable must be equal to one of the values that have been predefined for it.
+Common examples include compass directions (values of `NORTH`, `SOUTH`, `EAST`, and `WEST`) and the days of the week.
+
+Because they are constants, the names of an enum type's fields are in uppercase letters.
+
+## Defining an enum type
+
+In the Java programming language, you define an enum type by using the `enum` keyword.
+For example, you would specify a days-of-the-week enum type as:
+
+```java
+public enum DayOfWeek {
+ SUNDAY,
+ MONDAY,
+ TUESDAY,
+ WEDNESDAY,
+ THURSDAY,
+ FRIDAY,
+ SATURDAY
+}
+```
+
+You should use enum types any time you need to represent a fixed set of constants.
+That includes natural enum types such as the planets in our solar system and data sets where you know all possible values at compile time - for example, the choices on a menu, command line flags, and so on.
+
+## Using an enum type
+
+Here is some code that shows you how to use the `DayOfWeek` enum defined above:
+
+```java
+public class Shop {
+ public String getOpeningHours(DayOfWeek dayOfWeek) {
+ switch (dayOfWeek) {
+ case MONDAY:
+ case TUESDAY:
+ case WEDNESDAY:
+ case THURSDAY:
+ case FRIDAY:
+ return "9am - 5pm";
+ case SATURDAY:
+ return "10am - 4pm"
+ case SUNDAY:
+ return "Closed.";
+ }
+ }
+}
+```
+
+```java
+var shop = new Shop();
+shop.getOpeningHours(DayOfWeek.WEDNESDAY);
+// => "9am - 5pm"
+```
+
+## Adding methods and fields
+
+Java programming language enum types are much more powerful than their counterparts in other languages.
+The `enum` declaration defines a _class_ (called an _enum type_).
+The enum class body can include methods and other fields:
+
+```java
+public enum Rating {
+ GREAT(5),
+ GOOD(4),
+ OK(3),
+ BAD(2),
+ TERRIBLE(1);
+
+ private final int numberOfStars;
+
+ Rating(int numberOfStars) {
+ this.numberOfStars = numberOfStars;
+ }
+
+ public int getNumberOfStars() {
+ return this.numberOfStars;
+ }
+}
+```
+
+Calling the `getNumberOfStars` method on a member of the `Rating` enum type:
+
+```java
+Rating.GOOD.getNumberOfStars();
+// => 4
+```
diff --git a/concepts/enums/links.json b/concepts/enums/links.json
new file mode 100644
index 000000000..55f1e75ad
--- /dev/null
+++ b/concepts/enums/links.json
@@ -0,0 +1,6 @@
+[
+ {
+ "url": "https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html",
+ "description": "Java Tuturial on Enum Types"
+ }
+]
diff --git a/concepts/exceptions/.meta/config.json b/concepts/exceptions/.meta/config.json
new file mode 100644
index 000000000..ef26a0098
--- /dev/null
+++ b/concepts/exceptions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Exceptions are thrown when an error that needs special handling occurs.",
+ "authors": ["sanderploegsma"],
+ "contributors": ["BahaaMohamed98"]
+}
diff --git a/concepts/exceptions/about.md b/concepts/exceptions/about.md
new file mode 100644
index 000000000..d3a4f5946
--- /dev/null
+++ b/concepts/exceptions/about.md
@@ -0,0 +1,151 @@
+# About
+
+The Java programming language uses [_exceptions_][exceptions] to handle errors and other exceptional events.
+
+## What is an exception
+
+An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions.
+Exceptions are raised explicitly in Java, and the act of raising an exception is called _throwing an exception_.
+The act of handling an exception is called _catching an exception_.
+
+In Java, all exceptions are subclasses of the `Exception` class, which itself is a subclass of `Throwable`.
+
+Java distinguishes two types of exceptions:
+
+1. Checked exceptions
+2. Unchecked exceptions
+
+### Checked exceptions
+
+_Checked exceptions_ are exceptional conditions that an application should anticipate and recover from.
+An example of a checked exception is the `FileNotFoundException` which occurs when a method is trying to read a file that does not exist.
+
+This type of exception is checked at compile-time: methods that throw checked exceptions should specify this in their method signature, and code calling a method that might throw a checked exception is required to handle it or the code will not compile.
+
+All checked exceptions are subclasses of `Exception` that do not extend `RuntimeException`.
+
+### Unchecked exceptions
+
+_Unchecked exceptions_ are exceptional conditions that an application usually cannot anticipate or recover from.
+An example of an unchecked exception is the `NullPointerException` which occurs when a method that is expecting a non-null value but receives `null`.
+
+This type of exception is not checked at compile-time: methods that throw unchecked exceptions are not required to specify this in their method signature, and code calling a method that might throw an unchecked exception is not required to handle it.
+
+All unchecked exceptions inherit from `RuntimeException`, which itself is an extension of `Exception`.
+
+## Throwing exceptions
+
+A method in Java can throw an exception by using the [`throw` statement][throw-statement].
+
+### Throwing a checked exception
+
+When throwing a checked exception from a method, it is required to specify this in the method signature by using the [`throws` keyword][throws-keyword], as shown in the example below.
+This forces calling code to anticipate that an exception might be thrown and handle it accordingly.
+
+```java
+public class InsufficientBalanceException extends Exception {
+
+}
+
+public class BankAccount {
+ public void withdraw(double amount) throws InsufficientBalanceException {
+ if (balance < amount) {
+ throw new InsufficientBalanceException();
+ }
+
+ // rest of the method implementation
+ }
+}
+```
+
+### Throwing an unchecked exception
+
+When throwing an unchecked exception from a method, it is not required to specify this in the method signature - although it is supported.
+
+```java
+public class BankAccount {
+ public void withdraw(double amount) {
+ if (amount < 0) {
+ throw new IllegalArgumentException("Cannot withdraw a negative amount");
+ }
+
+ // rest of the method implementation
+ }
+}
+```
+
+## Handling exceptions
+
+Handling exceptions in Java is done with the [`try`][try-block], [`catch`][catch-block] and [`finally`][finally-block] keywords.
+
+- Code statements that might throw an exception should be wrapped in a `try` block.
+- The `try` block is followed by one or more `catch` blocks that catch the exceptions thrown in the `try` block.
+- The `catch` blocks are optionally followed by a `finally` block that always executes after the `try` block, regardless of whether an exception was thrown or not.
+
+The following example shows how these keywords work:
+
+```java
+public class ATM {
+ public void withdraw(BankAccount bankAccount, double amount) {
+ try {
+ System.out.println("Withdrawing " + amount);
+ bankAccount.withdraw(amount);
+ System.out.println("Withdrawal succeeded");
+ } catch (InsufficientBalanceException) {
+ System.out.println("Withdrawal failed: insufficient balance");
+ } catch (RuntimeException e) {
+ System.out.println("Withdrawal failed: " + e.getMessage());
+ } finally {
+ System.out.println("Current balance: " + bankAccount.getBalance());
+ }
+ }
+}
+```
+
+In this example, when no exception is thrown, the following is printed:
+
+```text
+Withdrawing 10.0
+Withdrawal succeeded
+Current balance: 5.0
+```
+
+However, should the `bankAccount.withdraw(amount)` statement throw an `InsufficientBalanceException`, the following is printed:
+
+```text
+Withdrawing 10.0
+Withdrawal failed: insufficient balance
+Current balance: 5.0
+```
+
+Or, in case an unchecked exception is thrown by the `bankAccount.withdraw(amount)`, the following is printed:
+
+```text
+Withdrawing -10.0
+Withdrawal failed: Cannot withdraw a negative amount
+Current balance: 5.0
+```
+
+## Errors
+
+Java also has a separate category called _Errors_ which are serious problems that are external to an application.
+An example of an error is the `OutOfMemoryError` which occurs when an application is trying to use more memory than is available on the system.
+
+Like unchecked exceptions, errors are not checked at compile-time.
+The difference is that they represent system level problems and are generally thrown by the Java Virtual machine or environment instead of the application.
+Applications should generally not attempt to catch or handle them.
+
+All errors in Java inherit from the `Error` class.
+
+## When not to use exceptions
+
+As stated previously, exceptions are events that disrupt the normal flow of instructions, and are used to handle _exceptional events_.
+It is therefore [not recommended to use exceptions for flow control][dont-use-exceptions-for-flow-control] in your application.
+
+[exceptions]: https://docs.oracle.com/javase/tutorial/essential/exceptions/index.html
+[throw-statement]: https://docs.oracle.com/javase/tutorial/essential/exceptions/throwing.html
+[try-block]: https://docs.oracle.com/javase/tutorial/essential/exceptions/try.html
+[catch-block]: https://docs.oracle.com/javase/tutorial/essential/exceptions/catch.html
+[finally-block]: https://docs.oracle.com/javase/tutorial/essential/exceptions/finally.html
+[throws-keyword]: https://docs.oracle.com/javase/tutorial/essential/exceptions/declaring.html
+[dont-use-exceptions-for-flow-control]: https://web.archive.org/web/20140430044213/http://c2.com/cgi-bin/wiki?DontUseExceptionsForFlowControl
diff --git a/concepts/exceptions/introduction.md b/concepts/exceptions/introduction.md
new file mode 100644
index 000000000..c50113113
--- /dev/null
+++ b/concepts/exceptions/introduction.md
@@ -0,0 +1,138 @@
+# Introduction
+
+The Java programming language uses _exceptions_ to handle errors and other exceptional events.
+
+## What is an exception
+
+An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions.
+Exceptions are raised explicitly in Java, and the act of raising an exception is called _throwing an exception_.
+The act of handling an exception is called _catching an exception_.
+
+In Java, all exceptions are subclasses of the `Exception` class, which itself is a subclass of `Throwable`.
+
+Java distinguishes two types of exceptions:
+
+1. Checked exceptions
+2. Unchecked exceptions
+
+### Checked exceptions
+
+_Checked exceptions_ are exceptional conditions that an application should anticipate and recover from.
+An example of a checked exception is the `FileNotFoundException` which occurs when a method is trying to read a file that does not exist.
+
+This type of exception is checked at compile-time: methods that throw checked exceptions should specify this in their method signature, and code calling a method that might throw a checked exception is required to handle it or the code will not compile.
+
+All checked exceptions are subclasses of `Exception` that do not extend `RuntimeException`.
+
+### Unchecked exceptions
+
+_Unchecked exceptions_ are exceptional conditions that an application usually cannot anticipate or recover from.
+An example of an unchecked exception is the `NullPointerException` which occurs when a method that is expecting a non-null value but receives `null`.
+
+This type of exception is not checked at compile-time: methods that throw unchecked exceptions are not required to specify this in their method signature, and code calling a method that might throw an unchecked exception is not required to handle it.
+
+All unchecked exceptions inherit from `RuntimeException`, which itself is an extension of `Exception`.
+
+## Throwing exceptions
+
+A method in Java can throw an exception by using the `throw` statement.
+
+### Throwing a checked exception
+
+When throwing a checked exception from a method, it is required to specify this in the method signature by using the `throws` keyword, as shown in the example below.
+This forces calling code to anticipate that an exception might be thrown and handle it accordingly.
+
+```java
+public class InsufficientBalanceException extends Exception {
+
+}
+
+public class BankAccount {
+ public void withdraw(double amount) throws InsufficientBalanceException {
+ if (balance < amount) {
+ throw new InsufficientBalanceException();
+ }
+
+ // rest of the method implementation
+ }
+}
+```
+
+### Throwing an unchecked exception
+
+When throwing an unchecked exception from a method, it is not required to specify this in the method signature - although it is supported.
+
+```java
+public class BankAccount {
+ public void withdraw(double amount) {
+ if (amount < 0) {
+ throw new IllegalArgumentException("Cannot withdraw a negative amount");
+ }
+
+ // rest of the method implementation
+ }
+}
+```
+
+## Handling exceptions
+
+Handling exceptions in Java is done with the `try`, `catch` and `finally` keywords.
+
+- Code statements that might throw an exception should be wrapped in a `try` block.
+- The `try` block is followed by one or more `catch` blocks that catch the exceptions thrown in the `try` block.
+- The `catch` blocks are optionally followed by a `finally` block that always executes after the `try` block, regardless of whether an exception was thrown or not.
+
+The following example shows how these keywords work:
+
+```java
+public class ATM {
+ public void withdraw(BankAccount bankAccount, double amount) {
+ try {
+ System.out.println("Withdrawing " + amount);
+ bankAccount.withdraw(amount);
+ System.out.println("Withdrawal succeeded");
+ } catch (InsufficientBalanceException) {
+ System.out.println("Withdrawal failed: insufficient balance");
+ } catch (RuntimeException e) {
+ System.out.println("Withdrawal failed: " + e.getMessage());
+ } finally {
+ System.out.println("Current balance: " + bankAccount.getBalance());
+ }
+ }
+}
+```
+
+In this example, when no exception is thrown, the following is printed:
+
+```text
+Withdrawing 10.0
+Withdrawal succeeded
+Current balance: 5.0
+```
+
+However, should the `bankAccount.withdraw(amount)` statement throw an `InsufficientBalanceException`, the following is printed:
+
+```text
+Withdrawing 10.0
+Withdrawal failed: insufficient balance
+Current balance: 5.0
+```
+
+Or, in case an unchecked exception is thrown by the `bankAccount.withdraw(amount)`, the following is printed:
+
+```text
+Withdrawing -10.0
+Withdrawal failed: Cannot withdraw a negative amount
+Current balance: 5.0
+```
+
+## Errors
+
+Java also has a separate category called _Errors_ which are serious problems that are external to an application.
+An example of an error is the `OutOfMemoryError` which occurs when an application is trying to use more memory than is available on the system.
+
+Like unchecked exceptions, errors are not checked at compile-time.
+The difference is that they represent system level problems and are generally thrown by the Java Virtual machine or environment instead of the application.
+Applications should generally not attempt to catch or handle them.
+
+All errors in Java inherit from the `Error` class.
diff --git a/concepts/exceptions/links.json b/concepts/exceptions/links.json
new file mode 100644
index 000000000..ba9240eb2
--- /dev/null
+++ b/concepts/exceptions/links.json
@@ -0,0 +1,6 @@
+[
+ {
+ "url": "https://docs.oracle.com/javase/tutorial/essential/exceptions/index.html",
+ "description": "Exceptions in Java"
+ }
+]
diff --git a/concepts/for-loops/introduction.md b/concepts/for-loops/introduction.md
index ec08db817..7412e28ee 100644
--- a/concepts/for-loops/introduction.md
+++ b/concepts/for-loops/introduction.md
@@ -45,7 +45,7 @@ for (int i = 1; i <= 4; i++) {
The output would be:
-```
+```text
square of 1 is 1
square of 2 is 4
square of 3 is 9
diff --git a/concepts/foreach-loops/about.md b/concepts/foreach-loops/about.md
index 62cc9a796..293b9c38f 100644
--- a/concepts/foreach-loops/about.md
+++ b/concepts/foreach-loops/about.md
@@ -29,7 +29,7 @@ for(char vowel: vowels) {
which outputs:
-```
+```text
a
e
i
diff --git a/concepts/foreach-loops/introduction.md b/concepts/foreach-loops/introduction.md
index da5d8caef..7e2eec3c4 100644
--- a/concepts/foreach-loops/introduction.md
+++ b/concepts/foreach-loops/introduction.md
@@ -29,7 +29,7 @@ for(char vowel: vowels) {
which outputs:
-```
+```text
a
e
i
diff --git a/concepts/if-else-statements/.meta/config.json b/concepts/if-else-statements/.meta/config.json
new file mode 100644
index 000000000..5ab956b98
--- /dev/null
+++ b/concepts/if-else-statements/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "If-else statements can be used to perform conditional logic.",
+ "authors": ["TalesDias", "sanderploegsma"],
+ "contributors": []
+}
diff --git a/concepts/if-else-statements/about.md b/concepts/if-else-statements/about.md
new file mode 100644
index 000000000..ab9a63ed6
--- /dev/null
+++ b/concepts/if-else-statements/about.md
@@ -0,0 +1,62 @@
+# About
+
+## The _if-then_ statement
+
+The most basic control flow statement in Java is the [_if-then_ statement][if-statement].
+This statement is used to only execute a section of code if a particular condition is `true`.
+An _if-then_ statement is defined using the `if` clause:
+
+```java
+class Car {
+ void drive() {
+ // the "if" clause: the car needs to have fuel left to drive
+ if (fuel > 0) {
+ // the "then" clause: the car drives, consuming fuel
+ fuel--;
+ }
+ }
+}
+```
+
+In the above example, if the car is out of fuel, calling the `Car.drive` method will do nothing.
+
+## The _if-then-else_ statement
+
+The _if-then-else_ statement provides an alternative path of execution for when the condition in the `if` clause evaluates to `false`.
+This alternative path of execution follows an `if` clause and is defined using the `else` clause:
+
+```java
+class Car {
+ void drive() {
+ if (fuel > 0) {
+ fuel--;
+ } else {
+ stop();
+ }
+ }
+}
+```
+
+In the above example, if the car is out of fuel, calling the `Car.drive` method will call another method to stop the car.
+
+The _if-then-else_ statement also supports multiple conditions by using the `else if` clause:
+
+```java
+class Car {
+ void drive() {
+ if (fuel > 5) {
+ fuel--;
+ } else if (fuel > 0) {
+ turnOnFuelLight();
+ fuel--;
+ } else {
+ stop();
+ }
+ }
+}
+```
+
+In the above example, driving the car when the fuel is less then or equal to `5` will drive the car, but it will turn on the fuel light.
+When the fuel reaches `0`, the car will stop driving.
+
+[if-statement]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/if.html
diff --git a/concepts/if-else-statements/introduction.md b/concepts/if-else-statements/introduction.md
new file mode 100644
index 000000000..4e6d3dc32
--- /dev/null
+++ b/concepts/if-else-statements/introduction.md
@@ -0,0 +1,60 @@
+# Introduction
+
+## The _if-then_ statement
+
+The most basic control flow statement in Java is the _if-then_ statement.
+This statement is used to only execute a section of code if a particular condition is `true`.
+An _if-then_ statement is defined using the `if` clause:
+
+```java
+class Car {
+ void drive() {
+ // the "if" clause: the car needs to have fuel left to drive
+ if (fuel > 0) {
+ // the "then" clause: the car drives, consuming fuel
+ fuel--;
+ }
+ }
+}
+```
+
+In the above example, if the car is out of fuel, calling the `Car.drive` method will do nothing.
+
+## The _if-then-else_ statement
+
+The _if-then-else_ statement provides an alternative path of execution for when the condition in the `if` clause evaluates to `false`.
+This alternative path of execution follows an `if` clause and is defined using the `else` clause:
+
+```java
+class Car {
+ void drive() {
+ if (fuel > 0) {
+ fuel--;
+ } else {
+ stop();
+ }
+ }
+}
+```
+
+In the above example, if the car is out of fuel, calling the `Car.drive` method will call another method to stop the car.
+
+The _if-then-else_ statement also supports multiple conditions by using the `else if` clause:
+
+```java
+class Car {
+ void drive() {
+ if (fuel > 5) {
+ fuel--;
+ } else if (fuel > 0) {
+ turnOnFuelLight();
+ fuel--;
+ } else {
+ stop();
+ }
+ }
+}
+```
+
+In the above example, driving the car when the fuel is less then or equal to `5` will drive the car, but it will turn on the fuel light.
+When the fuel reaches `0`, the car will stop driving.
diff --git a/concepts/if-else-statements/links.json b/concepts/if-else-statements/links.json
new file mode 100644
index 000000000..0f138a23f
--- /dev/null
+++ b/concepts/if-else-statements/links.json
@@ -0,0 +1,10 @@
+[
+ {
+ "url": "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/if.html",
+ "description": "Java tutorial on if-then-else statements"
+ },
+ {
+ "url": "https://www.javatpoint.com/java-if-else",
+ "description": "Example if-then-else statements with flow charts"
+ }
+]
diff --git a/concepts/inheritance/about.md b/concepts/inheritance/about.md
index 52b72cfbd..bd6dd590e 100644
--- a/concepts/inheritance/about.md
+++ b/concepts/inheritance/about.md
@@ -9,23 +9,22 @@ A class can extend another class using `extends` keyword and can inherit from an
## Access Modifiers
-The access modifiers define rules for the member variables and methods of a class about their access from other classes (or anywhere in
-the code).
+The access modifiers define rules for the member variables and methods of a class about their access from other classes (or anywhere in the code).
-There are four access modifiers:
+There are four access modifiers:
-- private
-- public
-- protected
+- `private`
+- `public`
+- `protected`
- default (No keyword required)
-You can read more about them [here][access-modifiers]
+You can read more about them [in this article][access-modifiers]
## Inheritance vs Composition
These concepts are very similar and are often confused.
-- Inheritance means that the child has IS-A relationship with the parent class.
+- Inheritance means that the child has an IS-A relationship with the parent class.
```java
interface Animal() {
diff --git a/concepts/inheritance/introduction.md b/concepts/inheritance/introduction.md
index 66d1c52e0..23af059b0 100644
--- a/concepts/inheritance/introduction.md
+++ b/concepts/inheritance/introduction.md
@@ -1,8 +1,8 @@
# Introduction
-Inheritance is a core concept in OOP (Object Oriented Programming). It donates IS-A relationship.
-It literally means in programming as it means in english, inheriting features from parent(in programming features is normally functions
-and variables).
+Inheritance is a core concept in OOP (Object-Oriented Programming).
+It represents an IS-A relationship.
+It literally means in programming as it means in english, inheriting features from parent (in programming features is normally functions and variables).
Consider a class, `Animal` as shown,
@@ -11,7 +11,7 @@ Consider a class, `Animal` as shown,
public class Animal {
public void bark() {
- System.out.println("This is a animal");
+ System.out.println("This is an animal");
}
}
@@ -25,6 +25,7 @@ Consider an animal named `Lion`, having a class like,
//Lion class is a child class of Animal.
public class Lion extends Animal {
+ @Override
public void bark() {
System.out.println("Lion here!!");
}
@@ -32,6 +33,11 @@ public class Lion extends Animal {
}
```
+~~~~exercism/note
+The `Override` annotation is used to indicate that a method in a subclass is overriding a method of its superclass.
+It's not strictly necessary but it's a best practice to use it.
+~~~~
+
Now whenever we do,
```java
@@ -46,7 +52,7 @@ The output will look like
Lion here!!
```
-According to OOP, there are many types of inheritance, but Java supports only some of them(Multi-level and Hierarchical).
+According to OOP, there are many types of inheritance, but Java supports only some of them (Multi-level and Hierarchical).
To read more about it, please read [this][java-inheritance].
[java-inheritance]: https://www.javatpoint.com/inheritance-in-java#:~:text=On%20the%20basis%20of%20class,will%20learn%20about%20interfaces%20later.
diff --git a/concepts/interfaces/about.md b/concepts/interfaces/about.md
index a0ece63ae..9a3e78ec2 100644
--- a/concepts/interfaces/about.md
+++ b/concepts/interfaces/about.md
@@ -1,6 +1,7 @@
# About
-[`interfaces`][interfaces] are the primary means of [decoupling][wiki-loose-coupling] the uses of a class from its implementation. This decoupling provides flexibility for maintenance of the implementation and helps support type safe generic behavior.
+[`interfaces`][interfaces] are the primary means of [decoupling][wiki-loose-coupling] the uses of a class from its implementation.
+This decoupling provides flexibility for maintenance of the implementation and helps support type safe generic behavior.
The syntax of an interface is similar to that of a class except that methods appear as the signature only and no body is provided.
@@ -25,9 +26,12 @@ The implementing class must implement all operations defined by the interface.
Interfaces typically do one or more of the following:
-- allow a number of different classes to be treated generically by the using code. In this case interfaces are playing the same role as a base class. An example of this is [java.io.InputStream][input-stream],
-- expose a subset of functionality for some specific purpose (such as [`Comparable`][comparable]) or
-- expose the public API of a class so that multiple implementations can co-exist. One example is that of a [test double][wiki-test-double]
+- Allow a number of different classes to be treated generically by the using code.
+ In this case interfaces are playing the same role as a base class.
+ An example of this is [java.io.InputStream][input-stream],
+- Expose a subset of functionality for some specific purpose (such as [`Comparable`][comparable])
+- Expose the public API of a class so that multiple implementations can co-exist.
+ One example is that of a [test double][wiki-test-double]
```java
public class ItalianTraveller implements ItalianLanguage {
@@ -104,9 +108,9 @@ public class DocumentTranslator implements ScriptConverter {
Code which uses the above interfaces and classes can:
-- treat all speakers in the same way irrespective of language.
-- allow some subsystem handling script conversion to operate without caring about what specific types it is dealing with.
-- remain unaware of the changes to the italian speaker which is convenient if the class code and user code are maintained by different teams
+- Treat all speakers in the same way irrespective of language.
+- Allow some subsystem handling script conversion to operate without caring about what specific types it is dealing with.
+- Remain unaware of the changes to the italian speaker which is convenient if the class code and user code are maintained by different teams.
Interfaces are widely used to support testing as they allow for easy [mocking][so-mocking-interfaces].
@@ -114,7 +118,9 @@ Interfaces can extend other interfaces with the `extend` keyword.
Members of an interface are public by default.
-Interfaces can contain nested types: `interfaces`, `enums` and `classes`. Here, the containing interfaces act as [namespaces][wiki-namespaces]. Nested types are accessed outside the interface by prefixing the interface name and using dot syntax to identify the member.
+Interfaces can contain nested types: `interfaces`, `enums` and `classes`.
+Here, the containing interfaces act as [namespaces][wiki-namespaces].
+Nested types are accessed outside the interface by prefixing the interface name and using dot syntax to identify the member.
By design, Java does not support multiple inheritance, but it facilitates a kind of multiple inheritance through interfaces.
@@ -124,7 +130,6 @@ Moreover, the concept of [polymorphism can be implemented through interfaces][in
[so-mocking-interfaces]: https://stackoverflow.com/a/9226437/96167
[comparable]: https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html
[wiki-test-double]: https://en.wikipedia.org/wiki/Test_double
-[wiki-polymorphism]: https://en.wikipedia.org/wiki/Polymorphism_(computer_science)
[wiki-namespaces]: https://en.wikipedia.org/wiki/Namespace
[wiki-loose-coupling]: https://en.wikipedia.org/wiki/Loose_coupling
[interfaces]: https://docs.oracle.com/javase/tutorial/java/concepts/interface.html
diff --git a/concepts/interfaces/introduction.md b/concepts/interfaces/introduction.md
index 3fc56599d..171b0efde 100644
--- a/concepts/interfaces/introduction.md
+++ b/concepts/interfaces/introduction.md
@@ -1,6 +1,7 @@
# Introduction
-An interface is a type containing members defining a group of related functionality. It distances the uses of a class from the implementation allowing multiple different implementations or support for some generic behavior such as formatting, comparison or conversion.
+An interface is a type containing members defining a group of related functionality.
+It distances the uses of a class from the implementation allowing multiple different implementations or support for some generic behavior such as formatting, comparison or conversion.
The syntax of an interface is similar to that of a class except that methods appear as the signature only and no body is provided.
@@ -10,11 +11,11 @@ public interface Language {
String speak();
}
-public class ItalianTaveller implements Language, Cloneable {
+public class ItalianTraveller implements Language, Cloneable {
// from Language interface
public String getLanguageName() {
- return "Italiano";
+ return "Italiano";
}
// from Language interface
@@ -23,8 +24,8 @@ public class ItalianTaveller implements Language, Cloneable {
}
// from Cloneable interface
- public Object Clone() {
- ItalianTaveller it = new ItalianTaveller();
+ public Object clone() {
+ ItalianTraveller it = new ItalianTraveller();
return it;
}
}
@@ -34,4 +35,5 @@ All operations defined by the interface must be implemented by the implementing
Interfaces usually contain instance methods.
-An example of an interface found in the Java Class Library, apart from `Cloneable` illustrated above, is `Comparable`. The `Comparable` interface can be implemented where a default generic sort order in collections is required.
+An example of an interface found in the Java Class Library, apart from `Cloneable` illustrated above, is `Comparable`.
+The `Comparable` interface can be implemented where a default generic sort order in collections is required.
diff --git a/concepts/lists/about.md b/concepts/lists/about.md
index c5a878659..230bdcfd6 100644
--- a/concepts/lists/about.md
+++ b/concepts/lists/about.md
@@ -1,7 +1,7 @@
# About Lists
**Lists** are the ordered sequence collection in Java.
-Unlike arrays, a [`List`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html) can grow in size to accomodate any number of items.
+Unlike arrays, a [`List`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html) can grow in size to accommodate any number of items.
One standard implementation is the `ArrayList` which is backed by a re-sizable array.
Another standard implementation is the `LinkedList` class which is backed by a doubly-linked list.
@@ -13,7 +13,7 @@ For example:
List emptyListOfStrings = List.of();
List singleInteger = List.of(1);
List threeBooleans = List.of(true, false, true);
-List listWithMulitipleTypes = List.of("hello", 1, true);
+List listWithMultipleTypes = List.of("hello", 1, true);
```
`List`s have various helpful methods to add, remove, get, and check for an element to be present:
diff --git a/concepts/lists/introduction.md b/concepts/lists/introduction.md
index 0ce7901ff..7d5d865ef 100644
--- a/concepts/lists/introduction.md
+++ b/concepts/lists/introduction.md
@@ -1,7 +1,7 @@
# Introduction to Lists
**Lists** are the ordered sequence collection in Java.
-Unlike arrays, a [`List`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html) can grow in size to accomodate any number of items.
+Unlike arrays, a [`List`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html) can grow in size to accommodate any number of items.
One standard implementation is the `ArrayList` which is backed by a re-sizable array.
Another standard implementation is the `LinkedList` class which is backed by a doubly-linked list.
@@ -13,7 +13,7 @@ For example:
List emptyListOfStrings = List.of();
List singleInteger = List.of(1);
List threeBooleans = List.of(true, false, true);
-List listWithMulitipleTypes = List.of("hello", 1, true);
+List listWithMultipleTypes = List.of("hello", 1, true);
```
`List`s have various helpful methods to add, remove, get, and check for an element to be present:
diff --git a/concepts/maps/.meta/config.json b/concepts/maps/.meta/config.json
new file mode 100644
index 000000000..98c459cdc
--- /dev/null
+++ b/concepts/maps/.meta/config.json
@@ -0,0 +1,7 @@
+{
+ "blurb": "Maps are a collection of key value pairs.",
+ "authors": [
+ "kahgoh"
+ ],
+ "contributors": []
+}
diff --git a/concepts/maps/about.md b/concepts/maps/about.md
new file mode 100644
index 000000000..86a820cf1
--- /dev/null
+++ b/concepts/maps/about.md
@@ -0,0 +1,173 @@
+# About Maps
+
+A **Map** is a data structure for storing key value pairs.
+It is similar to dictionaries in other programming languages.
+The [Map][map-javadoc] interface defines the operations you can make with a map.
+
+## HashMap
+
+Java has a number of different Map implementations.
+[HashMap][hashmap-javadoc] is a commonly used one.
+
+```java
+// Make an instance
+Map fruitPrices = new HashMap<>();
+```
+
+~~~~exercism/note
+When defining a `Map` variable, it is recommended to define the variable as a `Map` type rather than the specific type, as in the above example.
+This practice makes it easy to change the `Map` implementation later.
+~~~~
+
+`HashMap` also has a copy constructor.
+
+```java
+// Make a copy of a map
+Map copy = new HashMap<>(fruitPrices);
+```
+
+Add entries to the map using [put][map-put-javadoc].
+
+```java
+fruitPrices.put("apple", 100);
+fruitPrices.put("pear", 80);
+// => { "apple" => 100, "pear" => 80 }
+```
+
+Only one value can be associated with each key.
+Calling `put` with the same key will update the key's value.
+
+```java
+fruitPrices.put("pear", 40);
+// => { "apple" => 100, "pear" => 40 }
+```
+
+Use [get][map-get-javadoc] to get the value for a key.
+
+```java
+fruitPrices.get("apple"); // => 100
+```
+
+Use [containsKey][map-containskey-javadoc] to see if the map contains a particular key.
+
+```java
+fruitPrices.containsKey("apple"); // => true
+fruitPrices.containsKey("orange"); // => false
+```
+
+Remove entries with [remove][map-remove-javadoc].
+
+```java
+fruitPrices.put("plum", 90); // Add plum to map
+fruitPrices.remove("plum"); // Removes plum from map
+```
+
+The [size][map-size-javadoc] method returns the number of entries.
+
+```java
+fruitPrices.size(); // Returns 2
+```
+
+You can use the [keySet][map-keyset-javadoc] or [values][map-values-javadoc] methods to obtain the keys or the values in a Map as a Set or collection respectively.
+
+```java
+fruitPrices.keySet(); // Returns "apple" and "pear" in a set
+fruitPrices.values(); // Returns 100 and 80, in a Collection
+```
+
+## HashMap uses `hashCode` and `equals`
+
+HashMaps uses the object's [hashCode][object-hashcode-javadoc] and [equals][object-equals-javadoc] method to work out where to store and how to retrieve the values for a key.
+For this reason, it is important that their return values do not change between storing and getting them, otherwise the HashMap may not be able to find the value.
+
+For example, lets say we have the following class that will be used as the key to a map:
+
+```java
+public class Stock {
+ private String name;
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (Objects.equals(Stock.class, obj.getClass()) && obj instanceof Stock other) {
+ return Objects.equals(name, other.name);
+ }
+ return false;
+ }
+}
+```
+
+The `hashCode` and `equals` depend on the `name` field, which can be changed via `setName`.
+Altering the `hashCode` can produce surprising results:
+
+```java
+Stock stock = new Stock();
+stock.setName("Beanies");
+
+Map stockCount = new HashMap<>();
+stockCount.put(stock, 80);
+
+stockCount.get(stock); // Returns 80
+
+Stock other = new Stock();
+other.setName("Beanies");
+
+stockCount.get(other); // Returns 80 because "other" and "stock" are equal
+
+stock.setName("Choccies");
+stockCount.get(stock); // Returns null because hashCode value has changed
+
+stockCount.get(other); // Also returns null because "other" and "stock" are not equal
+
+stock.setName("Beanies");
+stockCount.get(stock); // HashCode restored, so returns 80 again
+
+stockCount.get(other); // Also returns 80 again because "other" and "stock" are back to equal
+```
+
+## Map.of and Map.copyOf
+
+Another common way to create maps is to use [Map.of][map-of-javadoc] or [Map.ofEntries][map-ofentries-javadoc].
+
+```java
+// Using Map.of
+Map temperatures = Map.of("Mon", 30, "Tue", 28, "Wed", 32);
+
+// or using Map.ofEntries
+Map temperatures2 = Map.ofEntries(Map.entry("Mon", 30, "Tue", 28, "Wed", 32));
+```
+
+Unlike `HashMap`, they populate the map upfront and become read-only once created.
+[Map.copyOf][map-copyof-javadoc] makes a read-only copy of a map.
+
+```java
+Map readOnlyFruitPrices = Map.copyOf(fruitPrices);
+```
+
+Calling methods like `put`, `remove` or `clear` results in an `UnsupportedOperationException`.
+
+[map-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html
+[hashmap-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html
+[map-put-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#put(K,V)
+[map-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#get(java.lang.Object)
+[map-containskey-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsKey(java.lang.Object)
+[map-remove-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#remove(java.lang.Object)
+[map-size-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#size()
+[map-of-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#of()
+[map-ofentries-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#ofEntries(java.util.Map.Entry...)
+[map-copyof-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#copyOf(java.util.Map)
+[object-hashcode-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#hashCode()
+[object-equals-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#equals(java.lang.Object)
+[map-keyset-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#keySet()
+[map-values-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#values()
diff --git a/concepts/maps/introduction.md b/concepts/maps/introduction.md
new file mode 100644
index 000000000..60bfbe7e3
--- /dev/null
+++ b/concepts/maps/introduction.md
@@ -0,0 +1,72 @@
+# Introduction
+
+A **Map** is a data structure for storing key value pairs.
+It is similar to dictionaries in other programming languages.
+The [Map][map-javadoc] interface defines operations on a map.
+
+Java has a number of different Map implementations.
+[HashMap][hashmap-javadoc] is a commonly used one.
+
+```java
+// Make an instance
+Map fruitPrices = new HashMap<>();
+```
+
+Add entries to the map using [put][map-put-javadoc].
+
+```java
+fruitPrices.put("apple", 100);
+fruitPrices.put("pear", 80);
+// => { "apple" => 100, "pear" => 80 }
+```
+
+Only one value can be associated with each key.
+Calling `put` with the same key will update the key's value.
+
+```java
+fruitPrices.put("pear", 40);
+// => { "apple" => 100, "pear" => 40 }
+```
+
+Use [get][map-get-javadoc] to get the value for a key.
+
+```java
+fruitPrices.get("apple"); // => 100
+```
+
+Use [containsKey][map-containskey-javadoc] to see if the map contains a particular key.
+
+```java
+fruitPrices.containsKey("apple"); // => true
+fruitPrices.containsKey("orange"); // => false
+```
+
+Remove entries with [remove][map-remove-javadoc].
+
+```java
+fruitPrices.put("plum", 90); // Add plum to map
+fruitPrices.remove("plum"); // Removes plum from map
+```
+
+The [size][map-size-javadoc] method returns the number of entries.
+
+```java
+fruitPrices.size(); // Returns 2
+```
+
+You can use the [keySet][map-keyset-javadoc] or [values][map-values-javadoc] methods to obtain the keys or the values in a Map as a Set or collection respectively.
+
+```java
+fruitPrices.keySet(); // Returns "apple" and "pear" in a set
+fruitPrices.values(); // Returns 100 and 80, in a Collection
+```
+
+[map-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html
+[hashmap-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html
+[map-put-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#put(K,V)
+[map-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#get(java.lang.Object)
+[map-containskey-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsKey(java.lang.Object)
+[map-remove-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#remove(java.lang.Object)
+[map-size-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#size()
+[map-keyset-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#keySet()
+[map-values-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#values()
diff --git a/concepts/maps/links.json b/concepts/maps/links.json
new file mode 100644
index 000000000..22258ff94
--- /dev/null
+++ b/concepts/maps/links.json
@@ -0,0 +1,10 @@
+[
+ {
+ "url": "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html",
+ "description": "Interface Map documentation"
+ },
+ {
+ "url": "https://dev.java/learn/api/collections-framework/maps/",
+ "description": "Using Maps to Store Key Value Pairs"
+ }
+ ]
diff --git a/concepts/method-overloading/.meta/config.json b/concepts/method-overloading/.meta/config.json
new file mode 100644
index 000000000..696591ef6
--- /dev/null
+++ b/concepts/method-overloading/.meta/config.json
@@ -0,0 +1,7 @@
+{
+ "blurb": "Method overloading in Java allows multiple methods in a class to have the same name with different parameters, enhancing flexibility and readability.",
+ "authors": [
+ "sougat818"
+ ],
+ "contributors": []
+}
diff --git a/concepts/method-overloading/about.md b/concepts/method-overloading/about.md
new file mode 100644
index 000000000..a47e3dedf
--- /dev/null
+++ b/concepts/method-overloading/about.md
@@ -0,0 +1,52 @@
+# About
+
+[Method overloading][method-overloading] is a key concept in Java, allowing a class to have more than one method with the same name, as long as each method has a different parameter list.
+This feature is a fundamental aspect of Java's object-oriented programming and provides a way to perform similar operations with different types or numbers of inputs.
+Method overloading enhances the readability and organization of code by allowing methods that perform similar functions to share the same name.
+It is important to note that overloading is determined by the method's signature, which includes the method name and the parameter list.
+
+## Key Points
+
+- **Signature Matters**: The return type is not part of the method signature, so just changing the return type of a method does not constitute overloading.
+- **Parameter Differences**: Overloaded methods must differ in the number or type of parameters.
+- **Usage**: Overloading is often used to provide more intuitive ways to use a method with different types of inputs.
+
+## Examples of Method Overloading
+
+Consider a class `Calculator` that can add numbers.
+Overloading allows different types of addition operations:
+
+```java
+public class Calculator {
+
+ public int add(int a, int b) {
+ return a + b;
+ }
+
+ public double add(double a, double b) {
+ return a + b;
+ }
+
+ public int add(int a, int b, int c) {
+ return a + b + c;
+ }
+}
+```
+
+## Best Practices
+
+- **Clarity**: Overloaded methods should be used in a way that makes code more intuitive.
+ - The behavior of overloaded methods should be related and consistent.
+- **Avoid Ambiguity**: Ensure that each overloaded method has a clear purpose and that the differences in parameter lists are significant enough to avoid confusion.
+- **Documentation**: Properly document each version of the overloaded methods.
+ - Clear documentation helps other developers understand the purpose and usage of each method variant.
+- **Consistent Return Types**: While return types can vary in overloaded methods, it's generally good practice to keep them consistent where possible to avoid confusion.
+- **Limit Overloading**: Avoid overusing method overloading.
+ - Excessive overloading can make the code harder to read and maintain.
+
+## Conclusion
+
+Method overloading is a powerful feature in Java that, when used correctly, can greatly enhance the readability and flexibility of your code.
+It allows methods to be more versatile and adaptable to different contexts, making your Java programs more modular and maintainable.
+
+[method-overloading]: https://docs.oracle.com/javase/tutorial/java/javaOO/methods.html#overloading
diff --git a/concepts/method-overloading/introduction.md b/concepts/method-overloading/introduction.md
new file mode 100644
index 000000000..c7bc3918b
--- /dev/null
+++ b/concepts/method-overloading/introduction.md
@@ -0,0 +1,60 @@
+# Introduction
+
+In Java, method overloading is a feature that allows a class to have more than one method having the same name, if their
+parameter lists are different.
+It is related to compile-time (or static) polymorphism.
+This concept is crucial for
+creating methods that perform similar tasks but with different inputs.
+
+## Why Overload Methods?
+
+Method overloading increases the readability of the program.
+Different methods can be given the same name but with
+different parameters.
+Depending on the number of parameters or the type of parameters, the corresponding method is called.
+
+## How to Overload Methods?
+
+The key to method overloading is a method's signature.
+Two methods will be considered different if they have different signatures.
+There are two ways to overload a method:
+
+1. **Different Number of Parameters**: Methods can have the same name but a different number of parameters.
+
+ ```java
+ public class Display {
+
+ public void show(int x) {
+ System.out.println("Show with int: " + x);
+ }
+
+ public void show(int x, int y) {
+ System.out.println("Show with two ints: " + x + ", " + y);
+ }
+ }
+ ```
+
+2. **Different Types of Parameters**: Methods can have the same name and the same number of parameters but with
+ different types.
+
+ ```java
+ public class Display {
+
+ public void show(int x) {
+ System.out.println("Show with int: " + x);
+ }
+
+ public void show(String s) {
+ System.out.println("Show with String: " + s);
+ }
+ }
+ ```
+
+## Points to Remember
+
+- Overloaded methods must change the argument list.
+- Overloaded methods can also change the return type, but merely changing the return type does not constitute method
+ overloading.
+- Methods can be overloaded in the same class or in a subclass.
+
+In this concept, we will explore various examples and nuances of method overloading in Java.
diff --git a/concepts/method-overloading/links.json b/concepts/method-overloading/links.json
new file mode 100644
index 000000000..f1ba01f28
--- /dev/null
+++ b/concepts/method-overloading/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "https://docs.oracle.com/javase/tutorial/java/javaOO/methods.html#overloading",
+ "description": "Detailed explanation of method overloading in Java, covering the basics and advanced usage."
+ },
+ {
+ "url": "https://www.baeldung.com/java-method-overload-override",
+ "description": "A comprehensive guide to understanding method overloading in Java with examples."
+ },
+ {
+ "url": "https://stackoverflow.com/questions/154577/polymorphism-vs-overriding-vs-overloading",
+ "description": "Practical insights and common questions about method overloading in Java."
+ },
+ {
+ "url": "https://www.youtube.com/watch?v=Egzz2I3iseg",
+ "description": "Video tutorial explaining method overloading with visual examples and coding demonstrations."
+ }
+]
diff --git a/concepts/nullability/.meta/config.json b/concepts/nullability/.meta/config.json
new file mode 100644
index 000000000..cbd2f43a4
--- /dev/null
+++ b/concepts/nullability/.meta/config.json
@@ -0,0 +1,7 @@
+{
+ "blurb": "In Java, the null literal is used to denote the absence of a value.",
+ "authors": [
+ "smcg468"
+ ],
+ "contributors": []
+}
\ No newline at end of file
diff --git a/concepts/nullability/about.md b/concepts/nullability/about.md
new file mode 100644
index 000000000..0c12d8b4e
--- /dev/null
+++ b/concepts/nullability/about.md
@@ -0,0 +1,52 @@
+# About
+
+In Java, the [`null` literal][null-keyword] is used to denote the absence of a value.
+
+[Primitive data types][primitive-data-types] in Java all have a default value and therefore can never be `null`.
+By convention, they start with a lowercase letter e.g `int`.
+
+[Reference types][reference-data-types] contain the memory address of an object and can have a value of `null`.
+They generally start with an uppercase letter, e.g. `String`.
+
+Attempting to assign a primitive variable a value of `null` will result in a compile time error as the variable always holds a default value of the type assigned.
+
+```java
+// Throws compile time error stating the required type is int, but null was provided
+int number = null;
+```
+
+Assigning a reference variable a `null` value will not result in a compile time error as reference variables are nullable.
+
+```java
+//No error will occur as the String variable str is nullable
+String str = null;
+```
+
+Whilst accessing a reference variable which has a value of `null` will compile fine, it will result in a `NullPointerException` being thrown at runtime.
+
+```java
+int[] arr = null;
+
+// Throws NullPointerException at runtime
+arr.Length;
+```
+
+A [`NullPointerException` is thrown][null-pointer-exception] when trying to access a reference variable which is null but requires an object.
+
+To safely work with nullable values, one should check if they are `null` before working with them which can be done using [equality operators][equality-operators] such as `==` or `!=`:
+
+```java
+int[] arr = null;
+
+if(arr != null) {
+ System.out.println(arr.length);
+} else {
+ //Perform an alternate action when arr is null
+}
+```
+
+[null-keyword]: https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.7
+[primitive-data-types]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
+[reference-data-types]: https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.3
+[null-pointer-exception]: https://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
+[equality-operators]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op2.html
diff --git a/concepts/nullability/introduction.md b/concepts/nullability/introduction.md
new file mode 100644
index 000000000..e0b4cff07
--- /dev/null
+++ b/concepts/nullability/introduction.md
@@ -0,0 +1,23 @@
+# Introduction
+
+In Java, the `null` literal is used to denote the absence of a value.
+
+Primitive data types in Java all have a default value and therefore can never be `null`.
+By convention, they start with a lowercase letter e.g `int`.
+
+Reference types contain the memory address of an object and can have a value of `null`.
+They generally start with an uppercase letter, e.g. `String`.
+
+Attempting to assign a primitive variable a value of `null` will result in a compile time error as the variable always holds a primitive value of the type assigned.
+
+```java
+//Throws compile time error stating the required type is int, but null was provided
+int number = null;
+```
+
+Assigning a reference variable a `null` value will not result in a compile time error as reference variables are nullable.
+
+```java
+//No error will occur as the String variable str is nullable
+String str = null;
+```
diff --git a/concepts/nullability/links.json b/concepts/nullability/links.json
new file mode 100644
index 000000000..65761f5c3
--- /dev/null
+++ b/concepts/nullability/links.json
@@ -0,0 +1,22 @@
+[
+ {
+ "url": "https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.7",
+ "description": "null-keyword"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html",
+ "description": "primitive-data-types"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.3",
+ "description": "reference-data-types"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html",
+ "description": "null-pointer-exception"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op2.html",
+ "description": "equality-operators"
+ }
+]
\ No newline at end of file
diff --git a/concepts/numbers/.meta/config.json b/concepts/numbers/.meta/config.json
index fdffc23f4..506871c4f 100644
--- a/concepts/numbers/.meta/config.json
+++ b/concepts/numbers/.meta/config.json
@@ -1,7 +1,5 @@
{
"blurb": "Java includes various numeric types including integer and floating-point numbers.",
- "authors": [
- "TalesDias"
- ],
- "contributors": []
+ "authors": ["TalesDias"],
+ "contributors": ["sanderploegsma"]
}
diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md
index 3ef210de8..842043580 100644
--- a/concepts/numbers/about.md
+++ b/concepts/numbers/about.md
@@ -2,19 +2,19 @@
One of the key aspects of working with numbers in Java is the distinction between integers (numbers with no digits after the decimal separator) and floating-point numbers (numbers with zero or more digits after the decimal separator).
-Java has other [datatypes][numeric-datatypes] apart from `int` and `double`
+Java has other [datatypes][numeric-datatypes] apart from `int` and `double`:
```java
-//8-Bit Integer
+// 8-Bit Integer
byte a = 127;
-//16-Bit Integer
+// 16-Bit Integer
short b = 262143;
-//64-Bit Integer
+// 64-Bit Integer
long d = 18446744073709551999L;
-//32-bit Single-Precision Floating-Point
+// 32-bit Single-Precision Floating-Point
float e = 5409.29f;
```
@@ -28,7 +28,8 @@ double largeDouble = 9_876_543.21;
// => 9876543.21
```
-Arithmetic is done using the standard [arithmetic operators][arithmetic-operators] (`+`, `-`, `*`, etc.). Numbers can be compared using the standard [comparison operators][comparison-operators] (`<`, `>=`, etc.) along with the equality operator (`==`) and inequality operator (`!=`).
+Arithmetic is done using the standard [arithmetic operators][arithmetic-operators] (`+`, `-`, `*`, etc.).
+Numbers can be compared using the standard [comparison operators][comparison-operators] (`<`, `>=`, etc.) along with the equality operator (`==`) and inequality operator (`!=`).
```java
5 * 6
@@ -49,7 +50,8 @@ When converting between numeric types, there are two types of numeric conversion
1. Implicit conversions: no data will be lost and no additional syntax is required.
2. Explicit conversions: data could be lost and additional syntax in the form of a _cast_ is required.
-As an `int` has less precision than a `double`, converting from an `int` to a `double` is safe and is thus an [implicit conversion][type-casting]. However, converting from a `double` to an `int` could mean losing data, so that requires an [explicit conversion][type-casting].
+As an `int` has less precision than a `double`, converting from an `int` to a `double` is safe and is thus an [implicit conversion][type-casting].
+However, converting from a `double` to an `int` could mean losing data, so that requires an [explicit conversion][type-casting].
```java
int i = 9;
@@ -62,20 +64,6 @@ double fromInt = i;
int fromDouble = (int)d;
```
-An `if` statement can be used to conditionally execute code. The condition of an `if` statement must be of type `boolean`. Java has no concept of _truthy_ values.
-
-```java
-int x = 6;
-
-if (x == 5){
- // Execute logic if x equals 5
-} else if (x > 7){
- // Execute logic if x greater than 7
-} else{
- // Execute logic in all other cases
-}
-```
-
[arithmetic-operators]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op1.html
[comparison-operators]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/op2.html
[type-casting]: https://www.programiz.com/java-programming/typecasting
diff --git a/concepts/numbers/introduction.md b/concepts/numbers/introduction.md
index d68887ddb..0fe2df18b 100644
--- a/concepts/numbers/introduction.md
+++ b/concepts/numbers/introduction.md
@@ -2,32 +2,21 @@
There are two different types of numbers in Java:
-- Integers: numbers with no digits behind the decimal separator (whole numbers). Examples are `-6`, `0`, `1`, `25`, `976` and `-500000`.
-- Floating-point numbers: numbers with zero or more digits behind the decimal separator. Examples are `-20.4`, `0.1`, `2.72`, `16.984025` and `1024.0`.
+- Integers: numbers with no digits behind the decimal separator (whole numbers).
+ Examples are `-6`, `0`, `1`, `25`, `976` and `-500000`.
+- Floating-point numbers: numbers with zero or more digits behind the decimal separator.
+ Examples are `-20.4`, `0.1`, `2.72`, `16.984025` and `1024.0`.
-The two most common numeric types in Java are `int` and `double`. An `int` is a 32-bit integer and a `double` is a 64-bit floating-point number.
+The two most common numeric types in Java are `int` and `double`.
+An `int` is a 32-bit integer and a `double` is a 64-bit floating-point number.
-Arithmetic is done using the standard arithmetic operators. Numbers can be compared using the standard numeric comparison operators (eg. `5 > 4` and `4 <= 5`) and the equality (`==`) and inequality (`!=`) operators.
+Arithmetic is done using the standard arithmetic operators.
+Numbers can be compared using the standard numeric comparison operators (eg. `5 > 4` and `4 <= 5`) and the equality (`==`) and inequality (`!=`) operators.
Java has two types of numeric conversions:
1. Implicit conversions: no data will be lost and no additional syntax is required.
2. Explicit conversions: data could be lost and additional syntax in the form of a _cast_ is required.
-As an `int` has less precision than a `double`, converting from an `int` to a `double` is safe and is thus an implicit conversion. However, converting from a `double` to an `int` could mean losing data, so that requires an explicit conversion.
-
-In this exercise you must conditionally execute logic. The most common way to do this in Java is by using an `if/else` statement:
-
-```java
-int x = 6;
-
-if (x == 5) {
- // Execute logic if x equals 5
-} else if (x > 7) {
- // Execute logic if x greater than 7
-} else {
- // Execute logic in all other cases
-}
-```
-
-The condition of an `if` statement must be of type `boolean`.
+As an `int` has less precision than a `double`, converting from an `int` to a `double` is safe and is thus an implicit conversion.
+However, converting from a `double` to an `int` could mean losing data, so that requires an explicit conversion.
diff --git a/concepts/randomness/.meta/config.json b/concepts/randomness/.meta/config.json
new file mode 100644
index 000000000..2169b7737
--- /dev/null
+++ b/concepts/randomness/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Java includes utilities to generate random numbers.",
+ "authors": ["sanderploegsma"],
+ "contributors": []
+}
diff --git a/concepts/randomness/about.md b/concepts/randomness/about.md
new file mode 100644
index 000000000..da1debc5d
--- /dev/null
+++ b/concepts/randomness/about.md
@@ -0,0 +1,58 @@
+# About
+
+## Generating random values
+
+There are multiple ways to generate random values in Java.
+
+### Using `Random`
+
+The [`java.util.Random`][java-util-random-docs] class provides several methods to generate pseudo-random values.
+
+```java
+Random random = new Random();
+
+random.next(8); // Generates a random int with the given number of bits, in this case 8
+
+random.nextInt(); // Generates a random int in the range Integer.MIN_VALUE through Integer.MAX_VALUE
+random.nextInt(10); // Generates a random int in the range 0 to 10
+
+random.nextFloat(); // Generates a random float in the range 0.0 to 1.0
+random.nextDouble(); // Generates a random double in the range 0.0 to 1.0
+
+random.nextBoolean(); // Generates a random boolean value
+random.nextLong(); // Generates a random long value
+```
+
+Next to its default constructor, the `Random` class also has another constructor where a custom seed can be provided.
+If two instances of Random are created with the same seed, and the same sequence of method calls is made for each, they will generate and return identical sequences of numbers.
+
+### Using `Math.random()`
+
+The [`Math.random()`][math-random-docs] method is a utility method to generate a random `Double` in the range from `0.0` to `1.0`.
+
+### Using `ThreadLocalRandom`
+
+The [`java.util.concurrent.ThreadLocalRandom`][thread-local-random-docs] class is an alternative for `java.util.Random` and is designed to be thread-safe.
+This class has some extra utility methods to generate values that take both a lower and upper bound, making it a bit easier to work with.
+
+```java
+ThreadLocalRandom random = ThreadLocalRandom.current();
+
+random.nextInt(10, 20); // Generates a random int in the range 10 to 20
+random.nextLong(10, 20); // Generates a random long in the range 10 to 20
+
+random.nextFloat(10.0, 20.0); // Generates a random float in the range 10 to 20
+random.nextDouble(10.0, 20.0); // Generates a random double in the range 10 to 20
+```
+
+## Security
+
+Random values are often used to generate sensitive values like passwords.
+However, all of the methods to generate random values as described above are _not_ considered cryptographically secure.
+
+In order to generate cryptographically strong random numbers, use the [`java.security.SecureRandom`][secure-random-docs] class.
+
+[java-util-random-docs]: https://docs.oracle.com/javase/8/docs/api/java/util/Random.html
+[math-random-docs]: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#random--
+[thread-local-random-docs]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadLocalRandom.html
+[secure-random-docs]: https://docs.oracle.com/javase/8/docs/api/java/security/SecureRandom.html
diff --git a/concepts/randomness/introduction.md b/concepts/randomness/introduction.md
new file mode 100644
index 000000000..17f35a0f1
--- /dev/null
+++ b/concepts/randomness/introduction.md
@@ -0,0 +1,57 @@
+# Introduction
+
+An instance of the `java.util.Random` class can be used to generate random numbers in Java.
+
+## Random integers
+
+A random integer can be generated using the `nextInt()` method.
+This will generate a value in the range from `Integer.MIN_VALUE` to `Integer.MAX_VALUE`.
+
+```java
+Random random = new Random();
+
+random.nextInt();
+// => -1169335537
+```
+
+To limit the range of generated values, use `nextInt(int)`.
+This will generate a value in the range from `0` (inclusive) to the given upper bound (exclusive).
+
+For example, this will generate a random number from `0` through `9`.
+
+```java
+Random random = new Random();
+
+random.nextInt(10);
+// => 6
+```
+
+And this will generate a random number from `10` through `19`.
+
+```java
+Random random = new Random();
+
+10 + random.nextInt(10);
+// => 11
+```
+
+## Random doubles
+
+A random double can be generated using the `nextDouble()` method.
+This will generate a value in the range from `0.0` to `1.0`.
+
+```java
+Random random = new Random();
+
+random.nextDouble();
+// => 0.19250004204021398
+```
+
+And this will generate a random number from `100.0` to `200.0`.
+
+```java
+Random random = new Random();
+
+100.0 + 100.0 * random.nextDouble();
+// => 111.31849856260328
+```
diff --git a/concepts/randomness/links.json b/concepts/randomness/links.json
new file mode 100644
index 000000000..7a8f7eab6
--- /dev/null
+++ b/concepts/randomness/links.json
@@ -0,0 +1,14 @@
+[
+ {
+ "url": "https://docs.oracle.com/javase/8/docs/api/java/util/Random.html",
+ "description": "java.util.Random API documentation"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#random--",
+ "description": "Math.random() documentation"
+ },
+ {
+ "url": "https://docs.oracle.com/javase/8/docs/api/java/security/SecureRandom.html",
+ "description": "java.security.SecureRandom API documentation"
+ }
+]
diff --git a/concepts/sets/.meta/config.json b/concepts/sets/.meta/config.json
new file mode 100644
index 000000000..069ef6c01
--- /dev/null
+++ b/concepts/sets/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Sets are unordered collections that do not allow duplicates.",
+ "authors": ["sanderploegsma"],
+ "contributors": []
+}
diff --git a/concepts/sets/about.md b/concepts/sets/about.md
new file mode 100644
index 000000000..79607f271
--- /dev/null
+++ b/concepts/sets/about.md
@@ -0,0 +1,52 @@
+# About
+
+A [`Set`][set-docs] is an unordered collection that (unlike `List`) is guaranteed to not contain any duplicate values.
+
+The generic type parameter of the `Set` interface denotes the type of the elements contained in the `Set`:
+
+```java
+Set ints = Set.of(1, 2, 3);
+Set strings = Set.of("alpha", "beta", "gamma");
+Set mixed = Set.of(1, false, "foo");
+```
+
+Note that the `Set.of()` method creates an [_unmodifiable_][unmodifiable-set-docs] `Set` instance.
+Trying to call methods like `add` and `remove` on this instance will result in an exception at run-time.
+
+To create a modifiable `Set`, you need to instantiate a class that implements the `Set` interface.
+The most-used built-in class that implements this interface is the [`HashSet`][hashset-docs] class.
+
+```java
+Set ints = new HashSet<>();
+```
+
+The `Set` interface extends from the [`Collection`][collection-docs] and [`Iterable`][iterable-docs] interfaces, and therefore shares a lot of methods with other types of collections.
+A notable difference to the `Collection` interface, however, is that methods like `add` and `remove` return a `boolean` (instead of `void`) which indicates whether the item was contained in the set when that method was called:
+
+```java
+Set set = new HashSet<>();
+set.add(1);
+// => true
+set.add(2);
+// => true
+set.add(1);
+// => false
+set.size();
+// => 2
+set.contains(1);
+// => true
+set.contains(3);
+// => false
+set.remove(3);
+// => false
+set.remove(2);
+// => true
+set.size();
+// => 1
+```
+
+[collection-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collection.html
+[hashset-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html
+[iterable-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Iterable.html
+[set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html
+[unmodifiable-set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#unmodifiable
diff --git a/concepts/sets/introduction.md b/concepts/sets/introduction.md
new file mode 100644
index 000000000..f25255098
--- /dev/null
+++ b/concepts/sets/introduction.md
@@ -0,0 +1,52 @@
+# Introduction
+
+A [`Set`][set-docs] is an unordered collection that (unlike `List`) is guaranteed to not contain any duplicate values.
+
+The generic type parameter of the `Set` interface denotes the type of the elements contained in the `Set`:
+
+```java
+Set ints = Set.of(1, 2, 3);
+Set strings = Set.of("alpha", "beta", "gamma");
+Set mixed = Set.of(1, false, "foo");
+```
+
+Note that the `Set.of()` method creates an [_unmodifiable_][unmodifiable-set-docs] `Set` instance.
+Trying to call methods like `add` and `remove` on this instance will result in an exception at run-time.
+
+To create a modifiable `Set`, you need to instantiate a class that implements the `Set` interface.
+The most-used built-in class that implements this interface is the [`HashSet`][hashset-docs] class.
+
+```java
+Set ints = new HashSet<>();
+```
+
+The `Set` interface extends from the [`Collection`][collection-docs] and [`Iterable`][iterable-docs] interfaces, and therefore shares a lot of methods with other types of collections.
+A notable difference to the `Collection` interface, however, is that methods like `add` and `remove` return a `boolean` (instead of `void`) which indicates whether the item was contained in the set when that method was called:
+
+```java
+Set set = new HashSet<>();
+set.add(1);
+// => true
+set.add(2);
+// => true
+set.add(1);
+// => false
+set.size();
+// => 2
+set.contains(1);
+// => true
+set.contains(3);
+// => false
+set.remove(3);
+// => false
+set.remove(2);
+// => true
+set.size();
+// => 1
+```
+
+[collection-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collection.html
+[hashset-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html
+[iterable-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Iterable.html
+[set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html
+[unmodifiable-set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#unmodifiable
diff --git a/concepts/sets/links.json b/concepts/sets/links.json
new file mode 100644
index 000000000..1ad0bceab
--- /dev/null
+++ b/concepts/sets/links.json
@@ -0,0 +1,10 @@
+[
+ {
+ "url": "https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html",
+ "description": "java.util.Set API documentation"
+ },
+ {
+ "url": "https://www.baeldung.com/java-set-operations",
+ "description": "Implementing set operations in Java"
+ }
+]
diff --git a/concepts/strings/about.md b/concepts/strings/about.md
index 85169cf96..1dc4e8205 100644
--- a/concepts/strings/about.md
+++ b/concepts/strings/about.md
@@ -1,14 +1,17 @@
# About
-The key thing to remember about Java strings is that they are immutable objects representing text as a sequence of Unicode characters (letters, digits, punctuation, etc.). Double quotes are used to define a `String` instance:
+The key thing to remember about Java strings is that they are immutable objects representing text as a sequence of Unicode characters (letters, digits, punctuation, etc.).
+Double quotes are used to define a `String` instance:
```java
String fruit = "Apple";
```
-Manipulating a string can be done using method of class [`String`][string-class]. As string values can never change after having been defined, all string manipulation methods will return a new string.
+Manipulating a string can be done using method of class [`String`][string-class].
+As string values can never change after having been defined, all string manipulation methods will return a new string.
-A string is delimited by double quote (`"`) characters. Some special characters need escaping using the backslash (`\`) character.
+A string is delimited by double quote (`"`) characters.
+Some special characters need escaping using the backslash (`\`) character.
Characters to be escaped in Java:
- `"`
@@ -19,7 +22,8 @@ String escaped = "c:\\test.txt";
// => c:\test.txt
```
-Finally, there are many ways to concatenate a string. The simplest one is the `+` operator
+Finally, there are many ways to concatenate a string.
+The simplest one is the `+` operator:
```java
String name = "Jane";
diff --git a/concepts/strings/introduction.md b/concepts/strings/introduction.md
index 127e0a0e0..129e3b00e 100644
--- a/concepts/strings/introduction.md
+++ b/concepts/strings/introduction.md
@@ -1,10 +1,13 @@
# Introduction
-A `String` in Java is an object that represents immutable text as a sequence of Unicode characters (letters, digits, punctuation, etc.). Double quotes are used to define a `String` instance:
+A `String` in Java is an object that represents immutable text as a sequence of Unicode characters (letters, digits, punctuation, etc.).
+Double quotes are used to define a `String` instance:
```java
String fruit = "Apple";
```
-Strings are manipulated by calling the string's methods. Once a string has been constructed, its value can never change. Any methods that appear to modify a string will actually return a new string.
+Strings are manipulated by calling the string's methods.
+Once a string has been constructed, its value can never change.
+Any methods that appear to modify a string will actually return a new string.
The `String` class provides some _static_ methods to transform the strings.
diff --git a/concepts/switch-statement/.meta/config.json b/concepts/switch-statement/.meta/config.json
index ce9f08c55..62882afb5 100644
--- a/concepts/switch-statement/.meta/config.json
+++ b/concepts/switch-statement/.meta/config.json
@@ -4,5 +4,6 @@
"Azumix"
],
"contributors": [
+ "josealonso"
]
}
diff --git a/concepts/switch-statement/about.md b/concepts/switch-statement/about.md
index 91a41b677..2c89c5c04 100644
--- a/concepts/switch-statement/about.md
+++ b/concepts/switch-statement/about.md
@@ -1,15 +1,19 @@
# About
-Like an _if/else_ statement, a `switch` statement allows you to change the flow of the program by conditionally executing code. The difference is that a `switch` statement can only compare the value of a primitive or string expression against pre-defined constant values.
+Like an _if/else_ statement, a `switch` statement allows you to change the flow of the program by conditionally executing code.
+The difference is that a `switch` statement can only compare the value of a primitive or string expression against pre-defined constant values.
Some keywords are useful when using a switch statement.
-- `switch` : this keyword allows you to declare the structure of the switch. It is followed by the expression or the variable that will make the result change.
-- `case` : you will use this one to declare the differents possibilties for the result.
-- `break` : the `break` keyword is very useful in order to stop the execution of the switch at the end of the wanted flow. If you forget it, the program will continue and may lead to unexpected results.
-- `default` : as it's name says, use it as a default result when no other case matchs your expression's result.
+- `switch`: this keyword allows you to declare the structure of the switch.
+ It is followed by the expression or the variable that will make the result change.
+- `case`: you will use this one to declare the differents possibilties for the result.
+- `break`: the `break` keyword is very useful in order to stop the execution of the switch at the end of the wanted flow.
+ If you forget it, the program will continue and may lead to unexpected results.
+- `default`: as it's name says, use it as a default result when no other case matchs your expression's result.
-At their simplest they test a primitive or string expression and make a decision based on its value. For example:
+At their simplest they test a primitive or string expression and make a decision based on its value.
+For example:
```java
String direction = getDirection();
@@ -21,7 +25,7 @@ switch (direction) {
goRight();
break;
default:
- //otherwise
+ // otherwise
markTime();
break;
}
@@ -30,37 +34,39 @@ switch (direction) {
Starting with Java 14 (available as a preview before in Java 12 and 13) it is possible to use the "enhanced" switch implementation.
1. You have the possiblity to assign multiple value in a single case.
- In the traditional switch-statement you can use fall-through. In the following example `case 1` and `case 3` will execute the same stuff. This is done by `case 1` not using the `break` keyword.
+ In the traditional switch-statement you can use fall-through. In the following example `case 1` and `case 3` will execute the same stuff.
+ This is done by `case 1` not using the `break` keyword.
```java
switch (number) {
case 1:
case 3:
- //do same stuff
+ // do same stuff
break;
case 2:
- //do different stuff
+ // do different stuff
break;
- (...)
+ // (...)
}
```
In the enhanced `switch expression` you can directly assign multiple value to a `case`.
- Look at the following example :
+ Look at the following example:
```java
switch (number) {
case 1, 3:
- //do stuff
+ // do stuff
break;
case 2:
- //do other stuff
+ // do other stuff
break;
- (...)
+ // (...)
}
```
-2. You can now write a `switch-statement` or a `switch expression`. What is the difference ?
+2. You can now write a `switch-statement` or a `switch expression`.
+ What is the difference?
Basicly a statement is expecting some strict logic where an expression can return a value.
Instead of :
@@ -68,73 +74,96 @@ Starting with Java 14 (available as a preview before in Java 12 and 13) it is po
String result = "";
switch (expression) {
case "bob":
- result = "bob;
+ result = "bob";
break;
- (...)
+ // (...)
}
```
- You can do :
+ You can do:
```java
- String result = switch(expression) {
+ String result = switch (expression) {
case "bob":
yield "bob";
- (...)
+ // (...)
}
```
- The [`yield`][yield-keyword] works like a `return` except it's for switch expression. As `yield` terminates the expression `break` is not needed here.
+ The [`yield`][yield-keyword] works like a `return` except it's for switch expression.
+ As `yield` terminates the expression `break` is not needed here.
-3. Another difference between _switch statements_ and _switch expressions_: in _switch expressions_ you _**MUST**_ cover all cases. Either by having a `case` for all possible values or using a `default` case.
+3. Another difference between _switch statements_ and _switch expressions_: in _switch expressions_ you _**MUST**_ cover all cases.
+ Either by having a `case` for all possible values or using a `default` case.
-4. You can use `->` instead of `:`. The `->` allow you to not include the `break` keyword. Both notations can be used but in a switch you have to stick with only one.
+4. You can use `->` instead of `:`.
+ The `->` allow you to not include the `break` keyword.
+ Both notations can be used but in a switch you have to stick with only one.
```java
- switch(expression) {
- case 1 -> yield "one"
- case 2 -> yield "two"
- default: yield "other number" // Removing this will result in a compile error
- }
+ switch(expression) {
+ case 1 -> yield "one"
+ case 2 -> yield "two"
+ default: yield "other number" // Removing this will result in a compile error
+ }
```
-5. The scope. Traditional `switch` can lead to some unexpected behavior because of its scope as there is only one scope for the whole `switch`.
+5. The scope.
+ Traditional `switch` can lead to some unexpected behavior because of its scope as there is only one scope for the whole `switch`.
```java
- switch(expression) {
- case 1:
- String message = "something";
- break;
- case 2:
- String message = "anything";
- break;
- (...)
- }
+ switch(expression) {
+ case 1:
+ String message = "something";
+ break;
+ case 2:
+ String message = "anything";
+ break;
+ // (...)
+ }
```
This example is not working because message is declared twice in the `switch`.
- It could be solved using :
+ It could be solved using:
```java
- switch(expression) {
- case 1: {
- String message = "something";
- break;
- }
- case 2: {
- String message = "anything";
- break;
- }
- (...)
- }
+ switch (expression) {
+ case 1: {
+ String message = "something";
+ break;
+ }
+ case 2: {
+ String message = "anything";
+ break;
+ }
+ // (...)
+ }
```
- As the `{}` is delimiting the scope of the `case`. However it's not intuitive because `{}` are not mandatory.
- However if you use the new `->` notation it must be followed by either : a single statement/expression, a `throw` statement or a `{}` block. No more confusion!
+ As the `{}` is delimiting the scope of the `case`.
+ However it's not intuitive because `{}` are not mandatory.
+ However if you use the new `->` notation it must be followed by either: a single statement/expression, a `throw` statement or a `{}` block.
+ No more confusion!
-You can find more information on enhanced switch [here][switch1], [here][switch2] and on the [oracle documentation][oracle-doc].
+You can find more information on enhanced switch in [this article][switch1] and [this one][switch2], along with the official [Oracle documentation][oracle-doc].
+
+In addition, a feature called `Guarded Patterns` was added in Java 21, which allows you to do checks in the case label itself.
+
+```java
+String dayOfMonth = getDayOfMonth();
+String day = "";
+return switch (day) {
+ case "Tuesday" when dayOfMonth == 13 -> "Forbidden day!!";
+ case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" -> "Week day";
+ case "Saturday", "Sunday" -> "Weekend";
+ default -> "Unknown";
+};
+```
+
+You can find more information on the switch expression on Java 21 in [this blog][switch-on-Java-21]
[yield-keyword]: https://www.codejava.net/java-core/the-java-language/yield-keyword-in-java
[switch1]: https://www.vojtechruzicka.com/java-enhanced-switch/
[switch2]: https://howtodoinjava.com/java14/switch-expressions/
[oracle-doc]: https://docs.oracle.com/en/java/javase/13/language/switch-expressions.html
+[switch-on-Java-21]: https://blog.adamgamboa.dev/switch-expression-on-java-21/#3-guarded-pattern
diff --git a/concepts/switch-statement/introduction.md b/concepts/switch-statement/introduction.md
index 5b345f00b..f9e7b275f 100644
--- a/concepts/switch-statement/introduction.md
+++ b/concepts/switch-statement/introduction.md
@@ -1,15 +1,19 @@
# Introduction
-Like an _if/else_ statement, a `switch` statement allows you to change the flow of the program by conditionally executing code. The difference is that a `switch` statement can only compare the value of a primitive or string expression against pre-defined constant values.
+Like an _if/else_ statement, a `switch` statement allows you to change the flow of the program by conditionally executing code.
+The difference is that a `switch` statement can only compare the value of a primitive or string expression against pre-defined constant values.
Some keywords are useful when using a switch statement.
-- `switch` : this keyword allows you to declare the structure of the switch. It is followed by the expression or the variable that will make the result change.
-- `case` : you will use this to declare the differents possibilties for the result.
-- `break` : the `break` keyword is very useful in order to stop the execution of the switch at the end of the wanted flow. If you forget it, the program will continue and may lead to unexpected results.
-- `default` : as its name says, use it as a default result when no other case matchs your expression's result.
+- `switch`: this keyword allows you to declare the structure of the switch.
+ It is followed by the expression or the variable that will change the result.
+- `case`: you will use this keyword to declare the different possibilities for the result.
+- `break`: the `break` keyword is very useful in order to stop the execution of the switch at the end of the wanted flow.
+ If you forget it, the program will continue and may lead to unexpected results.
+- `default`: as its name says, use it as a default result when no other case matches your expression's result.
-At their simplest, they test a primitive or string expression and make a decision based on its value. For example:
+At their simplest they test a primitive or string expression and make a decision based on its value.
+For example:
```java
String direction = getDirection();
@@ -21,7 +25,7 @@ switch (direction) {
goRight();
break;
default:
- //otherwise
+ // otherwise
markTime();
break;
}
diff --git a/concepts/switch-statement/links.json b/concepts/switch-statement/links.json
index 11502d493..c428d3386 100644
--- a/concepts/switch-statement/links.json
+++ b/concepts/switch-statement/links.json
@@ -14,5 +14,9 @@
{
"url": "https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html",
"description": "oracle-doc"
+ },
+ {
+ "url": "https://blog.adamgamboa.dev/switch-expression-on-java-21/#3-guarded-pattern",
+ "description": "switch-on-Java-21"
}
]
diff --git a/concepts/ternary-operators/about.md b/concepts/ternary-operators/about.md
index a1f2dc113..461494b83 100644
--- a/concepts/ternary-operators/about.md
+++ b/concepts/ternary-operators/about.md
@@ -1,6 +1,7 @@
# Ternary Operator
-The _ternary operators_ can be thought of as being a compact version of _if-else_. It's usually used in (but not restricted to) return statements, it needs just one single line to make the decision, returning the left value if the expression is `true` and the right value if `false`.
+The _ternary operators_ can be thought of as being a compact version of _if-else_.
+It's usually used in (but not restricted to) return statements, it needs just one single line to make the decision, returning the left value if the expression is `true` and the right value if `false`.
A lot of simple _if/else_ expressions can be simplified using _ternary operators_.
@@ -16,9 +17,12 @@ if ( 5 > 4 ) {
}
```
-So, how to decide between _if-else_ and _ternary_ ? Well, _ternary operators_ are used in simple scenarios, where you just need to return a value based on a condition and no extra computation is needed. Use an _if-else_ for everthing else, like nested conditions, big expressions and when more than one line is needed to decide the return value.
+So, how to decide between _if-else_ and _ternary_?
+Well, _ternary operators_ are used in simple scenarios, where you just need to return a value based on a condition and no extra computation is needed.
+Use an _if-else_ for everthing else, like nested conditions, big expressions and when more than one line is needed to decide the return value.
-While you can nest _ternary operators_, the code often becomes hard to read. In these cases, nested if's are preferred.
+While you can nest _ternary operators_, the code often becomes hard to read.
+In these cases, nested if's are preferred.
```java
// hard to read
@@ -41,7 +45,7 @@ return val4;
_Ternary operators_ and _if/else_ statements are a good example that you have different ways of achieving the same result when programming.
-For more examples check out [this][ternary-operator-first] and [this][ternary-operator-second] sources.
+For more examples check out [this][ternary-operator-first] and [this][ternary-operator-second].
[ternary-operator-first]: https://www.programiz.com/java-programming/ternary-operator
[ternary-operator-second]: https://www.baeldung.com/java-ternary-operator
diff --git a/concepts/ternary-operators/introduction.md b/concepts/ternary-operators/introduction.md
index 606435f9c..b2e4776ab 100644
--- a/concepts/ternary-operators/introduction.md
+++ b/concepts/ternary-operators/introduction.md
@@ -1,6 +1,7 @@
# Introduction
-The _ternary operator_ is a lightweight, compact alternative for simple _if/else_ statements. Usually used in (but not restricted to) return statements, it needs just one single line to make the decision, returning the left value if the expression is `true` and the right value if `false`, as follows:
+The _ternary operator_ is a lightweight, compact alternative for simple _if/else_ statements.
+Usually used in (but not restricted to) return statements, it needs just one single line to make the decision, returning the left value if the expression is `true` and the right value if `false`, as follows:
```java
boolean expr = 0 != 200;
diff --git a/config.json b/config.json
index c10207b89..92dfd05b1 100644
--- a/config.json
+++ b/config.json
@@ -6,7 +6,7 @@
"concept_exercises": true,
"test_runner": true,
"representer": true,
- "analyzer": false
+ "analyzer": true
},
"blurb": "Java is a very widely used Object Oriented programming language. It's safe, simple to use and portable so that you can \"write once, run anywhere\".",
"version": 3,
@@ -16,7 +16,7 @@
"highlightjs_language": "java"
},
"test_runner": {
- "average_run_time": 12.0
+ "average_run_time": 12
},
"files": {
"solution": [
@@ -27,13 +27,16 @@
],
"example": [
".meta/src/reference/java/%{pascal_slug}.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"exercises": {
"concept": [
{
"slug": "lasagna",
- "name": "Cook your lasagna",
+ "name": "Cook Your Lasagna",
"uuid": "01924e92-ecc7-4ba6-83f9-f142c0756b9f",
"concepts": [
"basics"
@@ -63,7 +66,7 @@
"foreach-loops"
],
"prerequisites": [
- "conditionals-if"
+ "if-else-statements"
],
"status": "active"
},
@@ -77,7 +80,6 @@
],
"prerequisites": [
"arrays",
- "for-loops",
"strings"
],
"status": "active"
@@ -87,14 +89,13 @@
"name": "Calculator Conundrum",
"uuid": "9e62feec-c2c3-4fd6-94f5-0574cc65447d",
"concepts": [
- "exception-handling"
+ "exceptions"
],
"prerequisites": [
- "conditionals",
- "switch-case",
- "basics"
+ "if-else-statements",
+ "switch-statement"
],
- "status": "wip"
+ "status": "beta"
},
{
"slug": "squeaky-clean",
@@ -104,19 +105,20 @@
"chars"
],
"prerequisites": [
+ "arrays",
"strings"
],
"status": "active"
},
{
- "slug": "elons-toy-car",
- "name": "Elon's Toy Car",
+ "slug": "jedliks-toy-car",
+ "name": "Jedlik's Toy Car",
"uuid": "2ae791e9-eb7a-4344-841d-0c4797e5106c",
"concepts": [
"classes"
],
"prerequisites": [
- "conditionals-if",
+ "if-else-statements",
"numbers",
"strings"
],
@@ -126,13 +128,9 @@
"slug": "blackjack",
"name": "Play Your Cards!",
"uuid": "43b8f2e3-99e9-49cc-a897-d66f0f26670d",
- "concepts": [
- "conditionals-if"
- ],
- "prerequisites": [
- "booleans"
- ],
- "status": "active"
+ "concepts": [],
+ "prerequisites": [],
+ "status": "deprecated"
},
{
"slug": "need-for-speed",
@@ -165,10 +163,11 @@
"name": "Cars, Assemble!",
"uuid": "3f451c6b-04e2-4b08-8bb0-7dcd2ec5b8f4",
"concepts": [
+ "if-else-statements",
"numbers"
],
"prerequisites": [
- "conditionals-if"
+ "booleans"
],
"status": "active"
},
@@ -192,7 +191,7 @@
"ternary-operators"
],
"prerequisites": [
- "conditionals-if",
+ "if-else-statements",
"numbers"
],
"status": "active"
@@ -219,393 +218,393 @@
"prerequisites": [
"classes",
"strings",
- "booleans"
+ "if-else-statements"
],
"status": "active"
- }
- ],
- "practice": [
+ },
{
- "slug": "hello-world",
- "name": "Hello World",
- "uuid": "f77dc7e3-35a8-4300-a7c8-2c1765e9644d",
- "practices": [
- "basics"
+ "slug": "logs-logs-logs",
+ "name": "Logs, Logs, Logs!",
+ "uuid": "f33927f7-676f-4045-b1fc-34e719453c61",
+ "concepts": [
+ "enums"
],
- "prerequisites": [],
- "difficulty": 1
+ "prerequisites": [
+ "strings",
+ "switch-statement",
+ "constructors"
+ ]
},
{
- "slug": "two-fer",
- "name": "Two Fer",
- "uuid": "74515d45-565b-4be2-96c4-77e58efa9257",
- "practices": [
- "strings",
- "conditionals-if"
+ "slug": "tim-from-marketing",
+ "name": "Tim from Marketing",
+ "uuid": "28bd20c5-4fdd-4660-9225-54f24aae24e4",
+ "concepts": [
+ "nullability"
],
"prerequisites": [
- "basics"
- ],
- "difficulty": 1
+ "if-else-statements",
+ "strings"
+ ]
},
{
- "slug": "hamming",
- "name": "Hamming",
- "uuid": "ce899ca6-9cc7-47ba-b76f-1bbcf2630b76",
- "practices": [
- "for-loops"
+ "slug": "captains-log",
+ "name": "Captain's Log",
+ "uuid": "1ade8233-7a73-4fd9-afe2-f80d7cca14ab",
+ "concepts": [
+ "randomness"
],
"prerequisites": [
- "strings",
- "chars"
- ],
- "difficulty": 3
+ "arrays",
+ "numbers",
+ "strings"
+ ]
},
{
- "slug": "gigasecond",
- "name": "Gigasecond",
- "uuid": "bf1641c8-dc0d-4d38-9cfe-b4c132ea3553",
- "practices": [
- "numbers"
+ "slug": "booking-up-for-beauty",
+ "name": "Booking Up For Beauty",
+ "uuid": "6a514e9a-ed92-4e01-8af7-579d659415a4",
+ "concepts": [
+ "datetime"
],
"prerequisites": [
- "basics"
- ],
- "difficulty": 3
+ "numbers",
+ "strings"
+ ]
},
{
- "slug": "scrabble-score",
- "name": "Scrabble Score",
- "uuid": "afae9f2d-8baf-4bd7-8d7c-d486337f7c97",
- "practices": [
- "arrays",
- "switch-statement",
- "chars"
+ "slug": "wizards-and-warriors-2",
+ "name": "Wizards and Warriors 2",
+ "uuid": "752d6968-8600-426f-ad26-fa7c53cf1ac2",
+ "concepts": [
+ "method-overloading"
],
"prerequisites": [
+ "classes",
"strings",
- "for-loops"
+ "enums"
],
- "difficulty": 3
+ "status": "active"
},
{
- "slug": "difference-of-squares",
- "name": "Difference of Squares",
- "uuid": "b0da59c6-1b55-405c-b163-007ebf09f5e8",
- "practices": [
- "numbers"
+ "slug": "secrets",
+ "name": "Secrets",
+ "uuid": "b6485b16-e94d-41ce-9689-d94b70266f5e",
+ "concepts": [
+ "bit-manipulation"
],
"prerequisites": [
- "basics"
- ],
- "difficulty": 3
+ "numbers"
+ ]
},
{
- "slug": "secret-handshake",
- "name": "Secret Handshake",
- "uuid": "71c7c174-7e2c-43c2-bdd6-7515fcb12a91",
- "practices": [
- "lists"
+ "slug": "gotta-snatch-em-all",
+ "name": "Gotta Snatch 'Em All",
+ "uuid": "a7938215-4597-4c51-8ceb-6514d5654485",
+ "concepts": [
+ "sets"
],
"prerequisites": [
- "numbers",
- "for-loops"
+ "lists",
+ "generic-types"
],
- "difficulty": 3
+ "status": "beta"
},
{
- "slug": "matrix",
- "name": "Matrix",
- "uuid": "c1d4e0b4-6a0f-4be9-8222-345966621f53",
- "practices": [
- "constructors",
- "arrays"
+ "slug": "international-calling-connoisseur",
+ "name": "International Calling Connoisseur",
+ "uuid": "03506c5a-601a-42cd-b037-c310208de84d",
+ "concepts": [
+ "maps"
],
"prerequisites": [
- "strings",
- "numbers",
- "for-loops"
- ],
- "difficulty": 4
+ "classes",
+ "foreach-loops",
+ "generic-types"
+ ]
+ }
+ ],
+ "practice": [
+ {
+ "slug": "accumulate",
+ "name": "Accumulate",
+ "uuid": "e58c29d2-80a7-40ef-bed0-4a184ae35f34",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "status": "deprecated"
},
{
- "slug": "triangle",
- "name": "Triangle",
- "uuid": "ec268d8e-997b-4553-8c67-8bdfa1ecb888",
- "practices": [
- "constructors"
- ],
+ "slug": "binary",
+ "name": "Binary",
+ "uuid": "9ea6e0fa-b91d-4d17-ac8f-6d2dc30fdd44",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "status": "deprecated"
+ },
+ {
+ "slug": "hello-world",
+ "name": "Hello World",
+ "uuid": "f77dc7e3-35a8-4300-a7c8-2c1765e9644d",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1
+ },
+ {
+ "slug": "hexadecimal",
+ "name": "Hexadecimal",
+ "uuid": "6fe53a08-c123-465d-864a-ef18217203c4",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "status": "deprecated"
+ },
+ {
+ "slug": "leap",
+ "name": "Leap",
+ "uuid": "61fe1fa6-246d-4e38-92d6-b74af64c88af",
+ "practices": [],
"prerequisites": [
- "numbers",
- "conditionals-if"
+ "booleans",
+ "numbers"
],
- "difficulty": 4
+ "difficulty": 1
},
{
- "slug": "rotational-cipher",
- "name": "Rotational Cipher",
- "uuid": "9eb41883-55ef-4681-b5ac-5c2259302772",
- "practices": [
+ "slug": "octal",
+ "name": "Octal",
+ "uuid": "14a29e82-f9b1-4662-b678-06992e306c01",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "status": "deprecated"
+ },
+ {
+ "slug": "reverse-string",
+ "name": "Reverse String",
+ "uuid": "2c8afeed-480e-41f3-ad58-590fa8f88029",
+ "practices": [],
+ "prerequisites": [
"chars"
],
+ "difficulty": 1
+ },
+ {
+ "slug": "strain",
+ "name": "Strain",
+ "uuid": "2d195cd9-1814-490a-8dd6-a2c676f1d4cd",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "status": "deprecated"
+ },
+ {
+ "slug": "trinary",
+ "name": "Trinary",
+ "uuid": "ee3a8abe-6b19-4b47-9cf6-4d39ae915fb7",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 1,
+ "status": "deprecated"
+ },
+ {
+ "slug": "two-fer",
+ "name": "Two Fer",
+ "uuid": "74515d45-565b-4be2-96c4-77e58efa9257",
+ "practices": [],
"prerequisites": [
"strings",
- "conditionals-if",
- "for-loops"
+ "if-else-statements"
],
- "difficulty": 4
+ "difficulty": 1
},
{
- "slug": "saddle-points",
- "name": "Saddle Points",
- "uuid": "8dfc2f0d-1141-46e9-95e2-6f35ccf6f160",
- "practices": [
- "lists"
- ],
+ "slug": "armstrong-numbers",
+ "name": "Armstrong Numbers",
+ "uuid": "8e1dd48c-e05e-4a72-bf0f-5aed8dd123f5",
+ "practices": [],
"prerequisites": [
- "numbers",
- "for-loops",
- "classes"
+ "numbers"
],
- "difficulty": 4
+ "difficulty": 2
},
{
- "slug": "flatten-array",
- "name": "Flatten Array",
- "uuid": "a732a838-8170-458a-a85e-d6b4c46f97a1",
- "practices": [
- "lists"
- ],
+ "slug": "darts",
+ "name": "Darts",
+ "uuid": "4d400a44-b190-4a0c-affb-99fad8ea18da",
+ "practices": [],
"prerequisites": [
- "conditionals-if",
- "for-loops",
- "classes"
+ "if-else-statements"
],
- "difficulty": 5
+ "difficulty": 2
},
{
- "slug": "word-count",
- "name": "Word Count",
- "uuid": "3603b770-87a5-4758-91f3-b4d1f9075bc1",
- "practices": [
- "strings"
- ],
+ "slug": "dnd-character",
+ "name": "D&D Character",
+ "uuid": "09bb515c-0270-4d34-8d56-89ee04588494",
+ "practices": [],
"prerequisites": [
- "for-loops",
"arrays",
- "classes"
+ "constructors",
+ "randomness"
],
- "difficulty": 5
+ "difficulty": 2
},
{
- "slug": "robot-name",
- "name": "Robot Name",
- "uuid": "d7c2eed9-64c7-4c4a-b45d-c787d460337f",
- "practices": [
- "strings"
- ],
+ "slug": "dot-dsl",
+ "name": "DOT DSL",
+ "uuid": "03e1070d-a3ed-4664-9559-283e535c6bc4",
+ "practices": [],
"prerequisites": [
- "constructors",
- "classes"
+ "classes",
+ "lists"
],
- "difficulty": 5
+ "difficulty": 2
},
{
- "slug": "binary-search",
- "name": "Binary Search",
- "uuid": "50136dc3-caf7-4fa1-b7bd-0cba1bea9176",
- "practices": [
- "lists"
- ],
+ "slug": "grains",
+ "name": "Grains",
+ "uuid": "5ee66f39-5e37-4907-a6d9-f55d38324c6c",
+ "practices": [],
"prerequisites": [
- "conditionals-if",
- "classes"
+ "if-else-statements",
+ "numbers"
],
- "difficulty": 6
+ "difficulty": 2
},
{
- "slug": "bank-account",
- "name": "Bank Account",
- "uuid": "a242efc5-159d-492b-861d-12a1459fb334",
- "practices": [
- "constructors",
- "classes"
- ],
+ "slug": "high-scores",
+ "name": "High Scores",
+ "uuid": "574d6323-5ff5-4019-9ebe-0067daafba13",
+ "practices": [],
"prerequisites": [
- "numbers",
- "classes"
+ "lists"
],
- "difficulty": 6
+ "difficulty": 2
},
{
- "slug": "linked-list",
- "name": "Linked List",
- "uuid": "7ba5084d-3b75-4406-a0d7-87c26387f9c0",
- "practices": [
- "lists",
- "classes",
- "generic-types"
- ],
+ "slug": "resistor-color",
+ "name": "Resistor Color",
+ "uuid": "e5e821ed-fc1f-4419-9c90-ad4a0b564bea",
+ "practices": [],
"prerequisites": [
- "lists",
- "classes",
- "generic-types",
- "for-loops"
+ "arrays"
],
- "difficulty": 6
+ "difficulty": 2
},
{
- "slug": "raindrops",
- "name": "Raindrops",
- "uuid": "d916e4f8-fda1-41c0-ab24-79e8fc3f1272",
- "practices": [
- "conditionals-if",
- "numbers",
- "strings"
- ],
+ "slug": "resistor-color-duo",
+ "name": "Resistor Color Duo",
+ "uuid": "0ae1989d-df46-414d-ad1f-4bd0f0f78421",
+ "practices": [],
"prerequisites": [
- "conditionals-if",
- "numbers",
- "strings"
+ "arrays"
],
- "difficulty": 3
+ "difficulty": 2
},
{
- "slug": "isogram",
- "name": "Isogram",
- "uuid": "c3e89c7c-3a8a-4ddc-b653-9b0ff9e1d7d8",
- "practices": [
- "conditionals-if",
- "for-loops",
- "strings"
- ],
+ "slug": "resistor-color-trio",
+ "name": "Resistor Color Trio",
+ "uuid": "e8e6c84e-4982-4bb0-af49-fd67e6f32920",
+ "practices": [],
"prerequisites": [
- "conditionals-if",
- "for-loops",
- "strings"
+ "arrays"
],
- "difficulty": 4
+ "difficulty": 2
},
{
- "slug": "pig-latin",
- "name": "Pig Latin",
- "uuid": "38bc80ae-d842-4c04-a797-48edf322504d",
- "practices": [
- "arrays",
- "lists",
- "strings"
- ],
+ "slug": "rna-transcription",
+ "name": "RNA Transcription",
+ "uuid": "8e983ed2-62f7-439a-a144-fb8fdbdf4d30",
+ "practices": [],
"prerequisites": [
- "arrays",
- "lists",
+ "foreach-loops",
"strings"
],
- "difficulty": 5
+ "difficulty": 2
},
{
- "slug": "anagram",
- "name": "Anagram",
- "uuid": "fb10dc2f-0ba1-44b6-8365-5e48e86d1283",
- "practices": [
- "arrays",
- "conditionals-if",
- "lists",
- "for-loops"
- ],
+ "slug": "acronym",
+ "name": "Acronym",
+ "uuid": "c31bbc6d-bdcf-44f7-bf35-5f98752e38d0",
+ "practices": [],
"prerequisites": [
- "arrays",
- "conditionals-if",
- "lists",
- "for-loops"
+ "for-loops",
+ "strings"
],
- "difficulty": 7
+ "difficulty": 3
},
{
- "slug": "reverse-string",
- "name": "Reverse String",
- "uuid": "2c8afeed-480e-41f3-ad58-590fa8f88029",
- "practices": [
- "strings",
- "chars"
- ],
+ "slug": "difference-of-squares",
+ "name": "Difference of Squares",
+ "uuid": "b0da59c6-1b55-405c-b163-007ebf09f5e8",
+ "practices": [],
"prerequisites": [
- "basics"
+ "numbers"
],
- "difficulty": 1
+ "difficulty": 3
},
{
- "slug": "darts",
- "name": "Darts",
- "uuid": "4d400a44-b190-4a0c-affb-99fad8ea18da",
- "practices": [
- "conditionals-if",
- "constructors"
- ],
+ "slug": "gigasecond",
+ "name": "Gigasecond",
+ "uuid": "bf1641c8-dc0d-4d38-9cfe-b4c132ea3553",
+ "practices": [],
"prerequisites": [
+ "datetime",
"numbers"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "dnd-character",
- "name": "D&D Character",
- "uuid": "09bb515c-0270-4d34-8d56-89ee04588494",
- "practices": [
- "arrays",
- "constructors"
- ],
+ "slug": "hamming",
+ "name": "Hamming",
+ "uuid": "ce899ca6-9cc7-47ba-b76f-1bbcf2630b76",
+ "practices": [],
"prerequisites": [
- "numbers",
"for-loops"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "grains",
- "name": "Grains",
- "uuid": "5ee66f39-5e37-4907-a6d9-f55d38324c6c",
- "practices": [
- "numbers"
- ],
+ "slug": "micro-blog",
+ "name": "Micro Blog",
+ "uuid": "8295ae71-5c0e-49d0-bbe9-9b43a85bf2dd",
+ "practices": [],
"prerequisites": [
- "conditionals-if",
- "for-loops"
+ "strings"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "resistor-color",
- "name": "Resistor Color",
- "uuid": "e5e821ed-fc1f-4419-9c90-ad4a0b564bea",
- "practices": [
- "arrays"
- ],
+ "slug": "pangram",
+ "name": "Pangram",
+ "uuid": "133b0f84-bdc7-4508-a0a1-5732a7db81ef",
+ "practices": [],
"prerequisites": [
- "basics"
+ "chars"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "resistor-color-duo",
- "name": "Resistor Color Duo",
- "uuid": "0ae1989d-df46-414d-ad1f-4bd0f0f78421",
- "practices": [
- "arrays"
- ],
+ "slug": "perfect-numbers",
+ "name": "Perfect Numbers",
+ "uuid": "d0dcc898-ec38-4822-9e3c-1a1829c9b2d9",
+ "practices": [],
"prerequisites": [
- "basics"
+ "enums",
+ "exceptions"
],
- "difficulty": 2
+ "difficulty": 3
},
{
- "slug": "micro-blog",
- "name": "Micro Blog",
- "uuid": "8295ae71-5c0e-49d0-bbe9-9b43a85bf2dd",
- "practices": [
- "strings"
- ],
+ "slug": "eliuds-eggs",
+ "name": "Eliud's Eggs",
+ "uuid": "2d5b6404-3315-48c1-892f-b594a960e7a1",
+ "practices": [],
"prerequisites": [
- "basics"
+ "numbers",
+ "bit-manipulation"
],
"difficulty": 3
},
@@ -613,127 +612,151 @@
"slug": "protein-translation",
"name": "Protein Translation",
"uuid": "331073b3-bd1a-4868-b767-a64ce9fd9d97",
- "practices": [
- "arrays"
- ],
+ "practices": [],
"prerequisites": [
- "strings",
- "for-loops"
+ "arrays",
+ "strings"
],
"difficulty": 3
},
{
- "slug": "diamond",
- "name": "Diamond",
- "uuid": "ecbd997b-86f4-4e11-9ff0-706ac2899415",
- "practices": [
- "for-loops",
- "lists"
- ],
+ "slug": "raindrops",
+ "name": "Raindrops",
+ "uuid": "d916e4f8-fda1-41c0-ab24-79e8fc3f1272",
+ "practices": [],
"prerequisites": [
+ "if-else-statements",
+ "numbers",
"strings"
],
- "difficulty": 4
+ "difficulty": 3
},
{
- "slug": "proverb",
- "name": "Proverb",
- "uuid": "9906491b-a638-408d-86a4-4ad320a92658",
- "practices": [
- "strings"
- ],
+ "slug": "say",
+ "name": "Say",
+ "uuid": "3c76a983-e689-4d82-8f1b-6d52f3c5434c",
+ "practices": [],
"prerequisites": [
- "for-loops",
- "arrays"
+ "numbers",
+ "strings"
],
- "difficulty": 4
+ "difficulty": 3
},
{
- "slug": "twelve-days",
- "name": "Twelve Days",
- "uuid": "581afdbb-dfb6-4dc5-9554-a025b5469a3c",
- "practices": [
- "strings"
- ],
+ "slug": "scrabble-score",
+ "name": "Scrabble Score",
+ "uuid": "afae9f2d-8baf-4bd7-8d7c-d486337f7c97",
+ "practices": [],
"prerequisites": [
- "for-loops",
- "arrays"
+ "arrays",
+ "switch-statement",
+ "chars"
],
- "difficulty": 4
+ "difficulty": 3
},
{
- "slug": "bob",
- "name": "Bob",
- "uuid": "34cd328c-cd96-492b-abd4-2b8716cdcd9a",
- "practices": [
- "conditionals-if"
- ],
+ "slug": "secret-handshake",
+ "name": "Secret Handshake",
+ "uuid": "71c7c174-7e2c-43c2-bdd6-7515fcb12a91",
+ "practices": [],
"prerequisites": [
- "strings",
- "classes"
+ "enums",
+ "lists"
],
- "difficulty": 5
+ "difficulty": 3
},
{
- "slug": "beer-song",
- "name": "Beer Song",
- "uuid": "56943095-de76-40bf-b42b-fa3e70f8df5e",
- "practices": [
- "strings"
- ],
+ "slug": "space-age",
+ "name": "Space Age",
+ "uuid": "e5b524cd-3a1c-468a-8981-13e8eeccb29d",
+ "practices": [],
"prerequisites": [
- "for-loops",
- "classes"
+ "numbers"
],
- "difficulty": 6
+ "difficulty": 3
},
{
- "slug": "food-chain",
- "name": "Food Chain",
- "uuid": "dd3e6fd6-5359-4978-acd7-c39374cead4d",
- "practices": [
- "arrays"
- ],
+ "slug": "collatz-conjecture",
+ "name": "Collatz Conjecture",
+ "uuid": "1500d39a-c9d9-4d3a-9e77-fdc1837fc6ad",
+ "practices": [],
"prerequisites": [
- "strings",
- "classes"
+ "exceptions",
+ "if-else-statements",
+ "numbers"
],
- "difficulty": 6
+ "difficulty": 4
},
{
- "slug": "house",
- "name": "House",
- "uuid": "6b51aca3-3451-4a64-ad7b-00b151d7548a",
- "practices": [
- "strings",
- "for-loops"
+ "slug": "diamond",
+ "name": "Diamond",
+ "uuid": "ecbd997b-86f4-4e11-9ff0-706ac2899415",
+ "practices": [],
+ "prerequisites": [
+ "lists"
],
+ "difficulty": 4
+ },
+ {
+ "slug": "error-handling",
+ "name": "Error Handling",
+ "uuid": "846ae792-7ca7-43e1-b523-bb1ec9fa08eb",
+ "practices": [],
"prerequisites": [
- "arrays",
- "classes"
+ "exceptions"
],
- "difficulty": 6
+ "difficulty": 4
},
{
"slug": "isbn-verifier",
"name": "ISBN Verifier",
"uuid": "838bc1d7-b2de-482a-9bfc-c881b4ccb04c",
- "practices": [
- "strings"
- ],
+ "practices": [],
"prerequisites": [
"chars",
"for-loops"
],
"difficulty": 4
},
+ {
+ "slug": "isogram",
+ "name": "Isogram",
+ "uuid": "c3e89c7c-3a8a-4ddc-b653-9b0ff9e1d7d8",
+ "practices": [],
+ "prerequisites": [
+ "if-else-statements",
+ "for-loops",
+ "strings"
+ ],
+ "difficulty": 4
+ },
+ {
+ "slug": "killer-sudoku-helper",
+ "name": "Killer Sudoku Helper",
+ "uuid": "8c45a47d-b3e3-484c-a124-90136ec838fd",
+ "practices": [],
+ "prerequisites": [
+ "numbers",
+ "lists"
+ ],
+ "difficulty": 4
+ },
+ {
+ "slug": "kindergarten-garden",
+ "name": "Kindergarten Garden",
+ "uuid": "df41c70c-daa1-4380-9729-638c17b4105d",
+ "practices": [],
+ "prerequisites": [
+ "enums",
+ "lists"
+ ],
+ "difficulty": 4
+ },
{
"slug": "largest-series-product",
"name": "Largest Series Product",
"uuid": "b7310b6e-435c-4d5f-b2bd-31e586d0f238",
- "practices": [
- "strings"
- ],
+ "practices": [],
"prerequisites": [
"chars",
"numbers",
@@ -745,9 +768,7 @@
"slug": "luhn",
"name": "Luhn",
"uuid": "5227a76c-8ecb-4e5f-b023-6af65a057c41",
- "practices": [
- "strings"
- ],
+ "practices": [],
"prerequisites": [
"numbers",
"for-loops"
@@ -755,912 +776,979 @@
"difficulty": 4
},
{
- "slug": "knapsack",
- "name": "Knapsack",
- "uuid": "89a6bf1e-66d5-4e39-9bc0-294b8b76cb2a",
- "practices": [
- "arrays",
- "lists"
- ],
+ "slug": "matrix",
+ "name": "Matrix",
+ "uuid": "c1d4e0b4-6a0f-4be9-8222-345966621f53",
+ "practices": [],
"prerequisites": [
- "conditionals-if",
- "classes"
+ "constructors",
+ "arrays"
],
- "difficulty": 5
+ "difficulty": 4
},
{
- "slug": "nucleotide-count",
- "name": "Nucleotide Count",
- "uuid": "2d80fdfc-5bd7-4b67-9fbe-8ab820d89051",
- "practices": [
- "strings"
- ],
+ "slug": "nth-prime",
+ "name": "Nth Prime",
+ "uuid": "2c69db99-648d-4bd7-8012-1fbdeff6ca0a",
+ "practices": [],
"prerequisites": [
- "conditionals-if",
+ "exceptions",
"for-loops",
- "classes"
+ "if-else-statements",
+ "numbers"
],
- "difficulty": 5
+ "difficulty": 4
},
{
- "slug": "phone-number",
- "name": "Phone Number",
- "uuid": "5f9139e7-9fbb-496a-a0d7-a946283033de",
- "practices": [
- "strings"
- ],
+ "slug": "proverb",
+ "name": "Proverb",
+ "uuid": "9906491b-a638-408d-86a4-4ad320a92658",
+ "practices": [],
"prerequisites": [
- "chars",
- "conditionals-if",
- "classes"
+ "for-loops",
+ "arrays"
],
- "difficulty": 5
+ "difficulty": 4
},
{
- "slug": "series",
- "name": "Series",
- "uuid": "af80d7f4-c7d0-4d0b-9c30-09da120f6bb9",
- "practices": [
- "for-loops",
- "lists"
- ],
+ "slug": "rate-limiter",
+ "name": "Rate Limiter",
+ "uuid": "b4b0c60e-4ce1-488e-948f-bcb6821c773c",
+ "practices": [],
"prerequisites": [
- "strings",
- "classes"
+ "generic-types"
],
- "difficulty": 5
+ "difficulty": 4
},
{
- "slug": "roman-numerals",
- "name": "Roman Numerals",
- "uuid": "3e728cd4-5e5f-4c69-8a53-bc36d020fcdb",
- "practices": [
- "arrays"
- ],
+ "slug": "rotational-cipher",
+ "name": "Rotational Cipher",
+ "uuid": "9eb41883-55ef-4681-b5ac-5c2259302772",
+ "practices": [],
"prerequisites": [
- "strings",
- "classes"
+ "chars",
+ "if-else-statements",
+ "for-loops"
],
- "difficulty": 6
+ "difficulty": 4
},
{
- "slug": "allergies",
- "name": "Allergies",
- "uuid": "6a617ddb-04e3-451c-bb30-27ccd0be9125",
- "practices": [
+ "slug": "saddle-points",
+ "name": "Saddle Points",
+ "uuid": "8dfc2f0d-1141-46e9-95e2-6f35ccf6f160",
+ "practices": [],
+ "prerequisites": [
"lists"
],
+ "difficulty": 4
+ },
+ {
+ "slug": "sieve",
+ "name": "Sieve",
+ "uuid": "6791d01f-bae4-4b63-ae76-86529ac49e36",
+ "practices": [],
"prerequisites": [
- "for-loops",
- "classes"
+ "lists",
+ "numbers"
],
- "difficulty": 5
+ "difficulty": 4
},
{
- "slug": "meetup",
- "name": "Meetup",
- "uuid": "602511d5-7e89-4def-b072-4dd311816810",
- "practices": [
- "for-loops"
- ],
+ "slug": "split-second-stopwatch",
+ "name": "Split-Second Stopwatch",
+ "uuid": "9510c0ae-9977-4260-8991-0e8e849094b0",
+ "practices": [],
"prerequisites": [
- "conditionals-if",
- "classes"
+ "exceptions",
+ "if-else-statements"
],
- "difficulty": 7
+ "difficulty": 4
},
{
- "slug": "yacht",
- "name": "Yacht",
- "uuid": "0cb45688-9598-49aa-accc-ed48c5d6962d",
- "practices": [
- "arrays"
+ "slug": "sum-of-multiples",
+ "name": "Sum of Multiples",
+ "uuid": "2f244afc-3e7b-4f89-92af-e2b427f4ef35",
+ "practices": [],
+ "prerequisites": [
+ "arrays",
+ "if-else-statements",
+ "numbers"
],
+ "difficulty": 4
+ },
+ {
+ "slug": "swift-scheduling",
+ "name": "Swift Scheduling",
+ "uuid": "7f5388dc-ce0e-40d4-98d1-7a00aeae018d",
+ "practices": [],
"prerequisites": [
- "switch-statement",
- "classes"
+ "if-else-statements",
+ "datetime",
+ "strings"
],
"difficulty": 4
},
{
- "slug": "bowling",
- "name": "Bowling",
- "uuid": "4b3f7771-c642-4278-a3d9-2fb958f26361",
- "practices": [
- "classes"
- ],
+ "slug": "triangle",
+ "name": "Triangle",
+ "uuid": "ec268d8e-997b-4553-8c67-8bdfa1ecb888",
+ "practices": [],
"prerequisites": [
- "for-loops",
- "arrays"
+ "constructors"
],
- "difficulty": 6
+ "difficulty": 4
},
{
- "slug": "minesweeper",
- "name": "Minesweeper",
- "uuid": "416a1489-12af-4593-8540-0f55285c96b4",
- "practices": [
- "lists",
- "constructors"
- ],
+ "slug": "twelve-days",
+ "name": "Twelve Days",
+ "uuid": "581afdbb-dfb6-4dc5-9554-a025b5469a3c",
+ "practices": [],
"prerequisites": [
- "strings",
"for-loops",
- "classes"
+ "arrays"
],
- "difficulty": 6
- },
- {
- "slug": "queen-attack",
- "name": "Queen Attack",
- "uuid": "5404109e-3ed9-4691-8eaf-af8b36024a44",
- "practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "arrays",
- "classes",
- "conditionals-if",
- "games",
- "matrices"
- ]
+ "difficulty": 4
},
{
- "slug": "dominoes",
- "name": "Dominoes",
- "uuid": "8e3cb20e-623b-4b4d-8a91-d1a51c0911b5",
+ "slug": "variable-length-quantity",
+ "name": "Variable Length Quantity",
+ "uuid": "d8a2c7ba-2040-4cfe-ab15-f90b3b61dd89",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "algorithms",
- "exception_handling",
- "games",
- "lists"
- ]
+ "prerequisites": [
+ "exceptions"
+ ],
+ "difficulty": 4
},
{
- "slug": "go-counting",
- "name": "Go Counting",
- "uuid": "2e760ae2-fadd-4d31-9639-c4554e2826e9",
+ "slug": "yacht",
+ "name": "Yacht",
+ "uuid": "0cb45688-9598-49aa-accc-ed48c5d6962d",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "algorithms",
- "conditionals-if",
- "games",
- "loops"
- ]
+ "prerequisites": [
+ "enums"
+ ],
+ "difficulty": 4
},
{
- "slug": "markdown",
- "name": "Markdown",
- "uuid": "cc18f2e5-4e36-47d6-aa60-8ca5ff54019a",
+ "slug": "allergies",
+ "name": "Allergies",
+ "uuid": "6a617ddb-04e3-451c-bb30-27ccd0be9125",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "conditionals-if",
- "pattern_matching",
- "refactoring",
- "strings"
- ]
+ "prerequisites": [
+ "for-loops",
+ "enums",
+ "bit-manipulation"
+ ],
+ "difficulty": 5
},
{
- "slug": "poker",
- "name": "Poker",
- "uuid": "57eeac00-741c-4843-907a-51f0ac5ea4cb",
+ "slug": "atbash-cipher",
+ "name": "Atbash Cipher",
+ "uuid": "d36ce010-210f-4e9a-9d6c-cb933e0a59af",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "games",
- "parsing",
- "sorting"
- ]
+ "prerequisites": [
+ "chars",
+ "foreach-loops"
+ ],
+ "difficulty": 5
},
{
- "slug": "word-search",
- "name": "Word Search",
- "uuid": "b53bde52-cb5f-4d43-86ec-18aa509d62f9",
+ "slug": "bob",
+ "name": "Bob",
+ "uuid": "34cd328c-cd96-492b-abd4-2b8716cdcd9a",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "games",
- "logic",
- "matrices",
- "pattern_matching",
- "searching",
+ "prerequisites": [
+ "if-else-statements",
"strings"
- ]
- },
- {
- "slug": "perfect-numbers",
- "name": "Perfect Numbers",
- "uuid": "d0dcc898-ec38-4822-9e3c-1a1829c9b2d9",
- "practices": [],
- "prerequisites": [],
- "difficulty": 3,
- "topics": [
- "enumerations",
- "exception_handling",
- "filtering",
- "integers",
- "math"
- ]
- },
- {
- "slug": "say",
- "name": "Say",
- "uuid": "3c76a983-e689-4d82-8f1b-6d52f3c5434c",
- "practices": [],
- "prerequisites": [],
- "difficulty": 3,
- "topics": [
- "integers"
- ]
+ ],
+ "difficulty": 5
},
{
- "slug": "sieve",
- "name": "Sieve",
- "uuid": "6791d01f-bae4-4b63-ae76-86529ac49e36",
+ "slug": "flatten-array",
+ "name": "Flatten Array",
+ "uuid": "a732a838-8170-458a-a85e-d6b4c46f97a1",
"practices": [],
- "prerequisites": [],
- "difficulty": 4,
- "topics": [
- "algorithms",
- "integers",
+ "prerequisites": [
"lists",
- "loops",
- "math"
- ]
+ "generic-types"
+ ],
+ "difficulty": 5
},
{
- "slug": "sum-of-multiples",
- "name": "Sum of Multiples",
- "uuid": "2f244afc-3e7b-4f89-92af-e2b427f4ef35",
+ "slug": "game-of-life",
+ "name": "Conway's Game of Life",
+ "uuid": "749de7fc-3dcb-4231-9b4f-115d153af74f",
"practices": [],
- "prerequisites": [],
- "difficulty": 4,
- "topics": [
+ "prerequisites": [
"arrays",
- "conditionals-if",
- "integers",
- "loops",
- "math"
- ]
+ "if-statements"
+ ],
+ "difficulty": 5
},
{
- "slug": "variable-length-quantity",
- "name": "Variable Length Quantity",
- "uuid": "d8a2c7ba-2040-4cfe-ab15-f90b3b61dd89",
+ "slug": "grep",
+ "name": "Grep",
+ "uuid": "9c15ddab-7b52-43d4-b38c-0bf635b363c3",
"practices": [],
- "prerequisites": [],
- "difficulty": 4,
- "topics": [
- "bitwise_operations",
- "conditionals-if",
- "exception_handling",
+ "prerequisites": [
"lists",
- "loops",
- "transforming"
- ]
+ "strings"
+ ],
+ "difficulty": 5
},
{
- "slug": "alphametics",
- "name": "Alphametics",
- "uuid": "0639a1f8-5af4-4877-95c1-5db8e97c30bf",
+ "slug": "knapsack",
+ "name": "Knapsack",
+ "uuid": "89a6bf1e-66d5-4e39-9bc0-294b8b76cb2a",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "conditionals-if",
- "logic"
- ]
+ "prerequisites": [
+ "lists"
+ ],
+ "difficulty": 5
},
{
- "slug": "robot-simulator",
- "name": "Robot Simulator",
- "uuid": "993bde9d-7774-4e7a-a381-9eee75f28ecb",
+ "slug": "ledger",
+ "name": "Ledger",
+ "uuid": "6597548e-176d-49c6-be33-789f4c43867a",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
+ "prerequisites": [
"classes",
- "enumerations",
- "logic",
- "loops"
- ]
- },
- {
- "slug": "wordy",
- "name": "Wordy",
- "uuid": "3310a3cd-c0cb-45fa-8c51-600178be904a",
- "practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "exception_handling",
- "integers",
- "logic",
- "parsing",
- "pattern_matching",
- "regular_expressions",
- "strings",
- "transforming"
- ]
+ "datetime",
+ "strings"
+ ],
+ "difficulty": 5
},
{
- "slug": "forth",
- "name": "Forth",
- "uuid": "f0c0316d-3fb5-455e-952a-91161e7fb298",
+ "slug": "matching-brackets",
+ "name": "Matching Brackets",
+ "uuid": "85aa50ac-0141-49eb-bc6f-62f3f7a97647",
"practices": [],
- "prerequisites": [],
- "difficulty": 9,
- "topics": [
- "exception_handling",
- "lists",
- "logic",
- "parsing",
- "stacks",
+ "prerequisites": [
+ "foreach-loops",
"strings"
- ]
+ ],
+ "difficulty": 5
},
{
- "slug": "kindergarten-garden",
- "name": "Kindergarten Garden",
- "uuid": "df41c70c-daa1-4380-9729-638c17b4105d",
+ "slug": "nucleotide-count",
+ "name": "Nucleotide Count",
+ "uuid": "2d80fdfc-5bd7-4b67-9fbe-8ab820d89051",
"practices": [],
- "prerequisites": [],
- "difficulty": 4,
- "topics": [
- "arrays",
- "enumerations",
- "lists",
- "logic",
- "loops",
- "pattern_recognition",
- "strings"
- ]
+ "prerequisites": [
+ "if-else-statements",
+ "for-loops",
+ "maps"
+ ],
+ "difficulty": 5
},
{
"slug": "pascals-triangle",
"name": "Pascal's Triangle",
"uuid": "d2a76905-1c8c-4b03-b4f7-4fbff19329f3",
"practices": [],
- "prerequisites": [],
- "difficulty": 5,
- "topics": [
- "algorithms",
- "arrays",
- "exception_handling",
- "integers",
- "math",
- "matrices"
- ]
- },
- {
- "slug": "spiral-matrix",
- "name": "Spiral Matrix",
- "uuid": "163fcc6b-c054-4232-a88b-0aded846a6eb",
- "practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
+ "prerequisites": [
"arrays",
- "integers",
- "loops",
- "matrices"
- ]
+ "exceptions",
+ "numbers"
+ ],
+ "difficulty": 5
},
{
- "slug": "tournament",
- "name": "Tournament",
- "uuid": "486d342e-c834-40fc-b691-a4dab3f790da",
+ "slug": "phone-number",
+ "name": "Phone Number",
+ "uuid": "5f9139e7-9fbb-496a-a0d7-a946283033de",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "loops",
- "maps",
- "parsing",
- "sorting",
- "text_formatting"
- ]
+ "prerequisites": [
+ "chars",
+ "if-else-statements"
+ ],
+ "difficulty": 5
},
{
- "slug": "transpose",
- "name": "Transpose",
- "uuid": "57b76837-4610-466f-9373-d5c2697625f1",
+ "slug": "pig-latin",
+ "name": "Pig Latin",
+ "uuid": "38bc80ae-d842-4c04-a797-48edf322504d",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
+ "prerequisites": [
"arrays",
"lists",
- "loops",
- "matrices",
- "strings",
- "text_formatting"
- ]
- },
- {
- "slug": "collatz-conjecture",
- "name": "Collatz Conjecture",
- "uuid": "1500d39a-c9d9-4d3a-9e77-fdc1837fc6ad",
- "practices": [],
- "prerequisites": [],
- "difficulty": 4,
- "topics": [
- "conditionals-if",
- "exception_handling",
- "integers",
- "math",
- "recursion"
- ]
- },
- {
- "slug": "error-handling",
- "name": "Error Handling",
- "uuid": "846ae792-7ca7-43e1-b523-bb1ec9fa08eb",
- "practices": [],
- "prerequisites": [],
- "difficulty": 4,
- "topics": [
- "exception_handling",
- "optional_values"
- ]
- },
- {
- "slug": "nth-prime",
- "name": "Nth Prime",
- "uuid": "2c69db99-648d-4bd7-8012-1fbdeff6ca0a",
- "practices": [],
- "prerequisites": [],
- "difficulty": 4,
- "topics": [
- "arrays",
- "exception_handling",
- "integers",
- "lists",
- "loops",
- "math"
- ]
+ "strings"
+ ],
+ "difficulty": 5
},
{
"slug": "prime-factors",
"name": "Prime Factors",
"uuid": "599c08ec-7338-46ed-99f8-a76f78f724e6",
"practices": [],
- "prerequisites": [],
- "difficulty": 5,
- "topics": [
- "arrays",
- "conditionals-if",
- "integers",
+ "prerequisites": [
+ "if-else-statements",
"lists",
- "loops",
- "math"
- ]
+ "numbers"
+ ],
+ "difficulty": 5
},
{
- "slug": "two-bucket",
- "name": "Two Bucket",
- "uuid": "210bf628-b385-443b-8329-3483cc6e8d7e",
- "practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "algorithms",
- "conditionals-if",
- "loops"
- ]
+ "slug": "relative-distance",
+ "name": "Relative Distance",
+ "uuid": "a3cf95fd-c7c1-4199-a253-7bae8d1aba9a",
+ "practices": [
+ "maps"
+ ],
+ "prerequisites": [
+ "lists",
+ "maps"
+ ],
+ "difficulty": 5
},
{
- "slug": "complex-numbers",
- "name": "Complex Numbers",
- "uuid": "52d11278-0d65-4b5b-b387-1374fced3243",
+ "slug": "robot-name",
+ "name": "Robot Name",
+ "uuid": "d7c2eed9-64c7-4c4a-b45d-c787d460337f",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "floating_point_numbers",
- "math"
- ]
+ "prerequisites": [
+ "constructors",
+ "classes",
+ "randomness"
+ ],
+ "difficulty": 5
},
{
- "slug": "rational-numbers",
- "name": "Rational Numbers",
- "uuid": "50ed54ce-3047-4590-9fda-0a7e9aeeba30",
+ "slug": "run-length-encoding",
+ "name": "Run-Length Encoding",
+ "uuid": "4499a3f9-73a7-48bf-8753-d5b6abf588c9",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "algorithms",
- "floating_point_numbers",
- "math"
- ]
+ "prerequisites": [
+ "chars",
+ "if-else-statements",
+ "for-loops"
+ ],
+ "difficulty": 5
},
{
- "slug": "pythagorean-triplet",
- "name": "Pythagorean Triplet",
- "uuid": "88505f95-89e5-4a08-8ed2-208eb818cdf1",
+ "slug": "series",
+ "name": "Series",
+ "uuid": "af80d7f4-c7d0-4d0b-9c30-09da120f6bb9",
"practices": [],
- "prerequisites": [],
- "difficulty": 9,
- "topics": [
- "integers",
+ "prerequisites": [
"lists",
- "logic",
- "math"
- ]
- },
- {
- "slug": "atbash-cipher",
- "name": "Atbash Cipher",
- "uuid": "d36ce010-210f-4e9a-9d6c-cb933e0a59af",
- "practices": [],
- "prerequisites": [],
- "difficulty": 5,
- "topics": [
- "cryptography",
- "security",
"strings"
- ]
+ ],
+ "difficulty": 5
},
{
- "slug": "run-length-encoding",
- "name": "Run-Length Encoding",
- "uuid": "4499a3f9-73a7-48bf-8753-d5b6abf588c9",
+ "slug": "square-root",
+ "name": "Square Root",
+ "uuid": "61886554-ec84-422a-bbf9-aeee37c45bb6",
"practices": [],
- "prerequisites": [],
- "difficulty": 5,
- "topics": [
- "integers",
- "pattern_matching",
- "strings",
- "transforming"
- ]
+ "prerequisites": [
+ "numbers"
+ ],
+ "difficulty": 5
},
{
- "slug": "affine-cipher",
- "name": "Affine Cipher",
- "uuid": "e6e3faaf-54c2-4782-93af-bb8d95403f2a",
+ "slug": "word-count",
+ "name": "Word Count",
+ "uuid": "3603b770-87a5-4758-91f3-b4d1f9075bc1",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "cryptography",
- "security",
- "strings",
- "text_formatting"
- ]
+ "prerequisites": [
+ "for-loops",
+ "arrays",
+ "maps"
+ ],
+ "difficulty": 5
},
{
- "slug": "rail-fence-cipher",
- "name": "Rail Fence Cipher",
- "uuid": "6e4ad4ed-cc02-4132-973d-b9163ba0ea3d",
+ "slug": "state-of-tic-tac-toe",
+ "name": "State Of Tic Tac Toe",
+ "uuid": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "conditionals-if",
- "loops",
+ "prerequisites": [
+ "arrays",
+ "for-loops",
+ "if-else-statements",
"strings"
- ]
- },
- {
- "slug": "crypto-square",
- "name": "Crypto Square",
- "uuid": "162bebdc-9bf2-43c0-8460-a91f5fc16147",
- "practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "cryptography",
- "lists",
- "security",
- "strings",
- "text_formatting"
- ]
+ ],
+ "difficulty": 5
},
{
- "slug": "simple-cipher",
- "name": "Simple Cipher",
- "uuid": "f654831f-c34b-44f5-9dd5-054efbb486f2",
+ "slug": "affine-cipher",
+ "name": "Affine Cipher",
+ "uuid": "e6e3faaf-54c2-4782-93af-bb8d95403f2a",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "cryptography",
- "exception_handling",
- "randomness",
- "security",
- "strings"
- ]
+ "prerequisites": [
+ "chars",
+ "exceptions",
+ "for-loops",
+ "if-else-statements",
+ "numbers"
+ ],
+ "difficulty": 6
},
{
"slug": "all-your-base",
"name": "All Your Base",
"uuid": "f7c2e4b5-1995-4dfe-b827-c9aff8ac5332",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
+ "prerequisites": [
"arrays",
- "conditionals-if",
- "exception_handling",
- "integers",
- "loops",
- "math"
- ]
+ "exceptions",
+ "for-loops",
+ "if-else-statements",
+ "numbers"
+ ],
+ "difficulty": 6
},
{
- "slug": "clock",
- "name": "Clock",
- "uuid": "97c8fcd4-85b6-41cb-9de2-b4e1b4951222",
+ "slug": "alphametics",
+ "name": "Alphametics",
+ "uuid": "0639a1f8-5af4-4877-95c1-5db8e97c30bf",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "equality",
- "integers",
- "logic",
- "object_oriented_programming",
- "strings",
- "time"
- ]
+ "prerequisites": [
+ "chars",
+ "exceptions",
+ "maps"
+ ],
+ "difficulty": 6
},
{
- "slug": "zebra-puzzle",
- "name": "Zebra Puzzle",
- "uuid": "b1e2bd39-3f4b-44c1-b7e2-258d4ee241f8",
+ "slug": "baffling-birthdays",
+ "name": "Baffling Birthdays",
+ "uuid": "b534049a-5920-4906-9091-0fa6d81a3636",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "logic"
- ]
+ "prerequisites": [
+ "for-loops",
+ "lists",
+ "sets",
+ "randomness",
+ "datetime"
+ ],
+ "difficulty": 6
},
{
- "slug": "palindrome-products",
- "name": "Palindrome Products",
- "uuid": "873c05de-b5f5-4c5b-97f0-d1ede8a82832",
+ "slug": "bank-account",
+ "name": "Bank Account",
+ "uuid": "a242efc5-159d-492b-861d-12a1459fb334",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "conditionals-if",
- "integers",
- "lists",
- "loops",
- "maps",
- "math"
- ]
+ "prerequisites": [
+ "constructors"
+ ],
+ "difficulty": 6
},
{
- "slug": "matching-brackets",
- "name": "Matching Brackets",
- "uuid": "85aa50ac-0141-49eb-bc6f-62f3f7a97647",
+ "slug": "beer-song",
+ "name": "Beer Song",
+ "uuid": "56943095-de76-40bf-b42b-fa3e70f8df5e",
"practices": [],
"prerequisites": [],
- "difficulty": 5,
- "topics": [
- "stacks",
- "strings"
- ]
+ "difficulty": 6,
+ "status": "deprecated"
},
{
- "slug": "book-store",
- "name": "Book Store",
- "uuid": "e5f05d00-fe5b-4d78-b2fa-934c1c9afb32",
+ "slug": "binary-search",
+ "name": "Binary Search",
+ "uuid": "50136dc3-caf7-4fa1-b7bd-0cba1bea9176",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "algorithms",
- "floating_point_numbers",
- "integers",
+ "prerequisites": [
"lists"
- ]
+ ],
+ "difficulty": 6
},
{
- "slug": "change",
- "name": "Change",
- "uuid": "bac1f4bc-eea9-43c1-8e95-097347f5925e",
+ "slug": "bottle-song",
+ "name": "Bottle Song",
+ "uuid": "3fa6750f-cf01-4542-a494-df9a8c658733",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "algorithms",
- "exception_handling",
- "integers",
- "lists"
- ]
+ "prerequisites": [
+ "strings"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "bowling",
+ "name": "Bowling",
+ "uuid": "4b3f7771-c642-4278-a3d9-2fb958f26361",
+ "practices": [],
+ "prerequisites": [
+ "for-loops",
+ "arrays"
+ ],
+ "difficulty": 6
},
{
"slug": "etl",
"name": "ETL",
"uuid": "76d28d97-75d3-47eb-bb77-3d347b76f1b6",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "lists",
+ "prerequisites": [
+ "foreach-loops",
"maps",
- "transforming"
- ]
+ "strings"
+ ],
+ "difficulty": 6
},
{
- "slug": "grade-school",
- "name": "Grade School",
- "uuid": "b4af5da1-601f-4b0f-bfb8-4a381851090c",
+ "slug": "flower-field",
+ "name": "Flower Field",
+ "uuid": "bddd180a-d634-454a-af03-4d625f77e1e2",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "conditionals-if",
+ "prerequisites": [
+ "constructors",
"lists",
- "maps",
- "sorting",
"strings"
- ]
+ ],
+ "difficulty": 6
},
{
- "slug": "grep",
- "name": "Grep",
- "uuid": "9c15ddab-7b52-43d4-b38c-0bf635b363c3",
+ "slug": "food-chain",
+ "name": "Food Chain",
+ "uuid": "dd3e6fd6-5359-4978-acd7-c39374cead4d",
+ "practices": [],
+ "prerequisites": [
+ "arrays"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "grade-school",
+ "name": "Grade School",
+ "uuid": "b4af5da1-601f-4b0f-bfb8-4a381851090c",
+ "practices": [],
+ "prerequisites": [
+ "if-else-statements",
+ "lists",
+ "strings"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "house",
+ "name": "House",
+ "uuid": "6b51aca3-3451-4a64-ad7b-00b151d7548a",
+ "practices": [],
+ "prerequisites": [
+ "strings",
+ "for-loops"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "intergalactic-transmission",
+ "name": "Intergalactic Transmission",
+ "uuid": "b1c6dfc2-414b-45b2-9277-8b9f8bb3bcf3",
"practices": [],
"prerequisites": [],
- "difficulty": 5,
- "topics": [
- "files",
- "filtering",
- "pattern_matching",
- "searching",
+ "difficulty": 6
+ },
+ {
+ "slug": "linked-list",
+ "name": "Linked List",
+ "uuid": "7ba5084d-3b75-4406-a0d7-87c26387f9c0",
+ "practices": [],
+ "prerequisites": [
+ "lists",
+ "classes",
+ "generic-types"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "minesweeper",
+ "name": "Minesweeper",
+ "uuid": "416a1489-12af-4593-8540-0f55285c96b4",
+ "practices": [],
+ "prerequisites": [],
+ "difficulty": 6,
+ "status": "deprecated"
+ },
+ {
+ "slug": "parallel-letter-frequency",
+ "name": "Parallel Letter Frequency",
+ "uuid": "38a405e8-619d-400f-b53c-2f06461fdf9d",
+ "practices": [],
+ "prerequisites": [
+ "maps",
"strings"
- ]
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "piecing-it-together",
+ "name": "Piecing It Together",
+ "uuid": "be303729-ad8a-4f4c-a235-6828a6734f05",
+ "practices": [],
+ "prerequisites": [
+ "exceptions",
+ "for-loops",
+ "if-else-statements"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "queen-attack",
+ "name": "Queen Attack",
+ "uuid": "5404109e-3ed9-4691-8eaf-af8b36024a44",
+ "practices": [],
+ "prerequisites": [
+ "constructors",
+ "exceptions"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "rail-fence-cipher",
+ "name": "Rail Fence Cipher",
+ "uuid": "6e4ad4ed-cc02-4132-973d-b9163ba0ea3d",
+ "practices": [],
+ "prerequisites": [
+ "arrays",
+ "chars",
+ "foreach-loops",
+ "if-else-statements"
+ ],
+ "difficulty": 6
},
{
"slug": "rest-api",
"name": "REST API",
"uuid": "809c0e3d-3494-4a85-843d-2bafa8752ce8",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "strings",
- "parsing"
- ]
+ "prerequisites": [
+ "classes",
+ "constructors",
+ "lists"
+ ],
+ "difficulty": 6
},
{
- "slug": "ocr-numbers",
- "name": "OCR Numbers",
- "uuid": "5cbc6a67-3a53-4aad-ae68-ff469525437f",
+ "slug": "robot-simulator",
+ "name": "Robot Simulator",
+ "uuid": "993bde9d-7774-4e7a-a381-9eee75f28ecb",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "exception_handling",
+ "prerequisites": [
+ "enums"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "roman-numerals",
+ "name": "Roman Numerals",
+ "uuid": "3e728cd4-5e5f-4c69-8a53-bc36d020fcdb",
+ "practices": [],
+ "prerequisites": [
+ "numbers",
+ "strings"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "spiral-matrix",
+ "name": "Spiral Matrix",
+ "uuid": "163fcc6b-c054-4232-a88b-0aded846a6eb",
+ "practices": [],
+ "prerequisites": [
+ "for-loops",
+ "numbers"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "tournament",
+ "name": "Tournament",
+ "uuid": "486d342e-c834-40fc-b691-a4dab3f790da",
+ "practices": [],
+ "prerequisites": [
+ "strings"
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "transpose",
+ "name": "Transpose",
+ "uuid": "57b76837-4610-466f-9373-d5c2697625f1",
+ "practices": [],
+ "prerequisites": [
+ "for-loops",
"lists",
- "loops",
- "parsing",
"strings"
- ]
+ ],
+ "difficulty": 6
},
{
- "slug": "rectangles",
- "name": "Rectangles",
- "uuid": "64cda115-919a-4eef-9a45-9ec5d1af98cc",
+ "slug": "wordy",
+ "name": "Wordy",
+ "uuid": "3310a3cd-c0cb-45fa-8c51-600178be904a",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "arrays",
- "logic",
- "pattern_recognition",
+ "prerequisites": [
+ "exceptions",
+ "numbers",
"strings"
- ]
+ ],
+ "difficulty": 6
+ },
+ {
+ "slug": "anagram",
+ "name": "Anagram",
+ "uuid": "fb10dc2f-0ba1-44b6-8365-5e48e86d1283",
+ "practices": [],
+ "prerequisites": [
+ "arrays",
+ "if-else-statements",
+ "lists",
+ "for-loops"
+ ],
+ "difficulty": 7
},
{
"slug": "binary-search-tree",
"name": "Binary Search Tree",
"uuid": "0a2d18aa-7b5e-4401-a952-b93d2060694f",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "generics",
- "graphs",
- "searching",
- "sorting",
- "trees"
- ]
+ "prerequisites": [
+ "classes",
+ "generic-types",
+ "lists"
+ ],
+ "difficulty": 7
},
{
- "slug": "parallel-letter-frequency",
- "name": "Parallel Letter Frequency",
- "uuid": "38a405e8-619d-400f-b53c-2f06461fdf9d",
+ "slug": "clock",
+ "name": "Clock",
+ "uuid": "97c8fcd4-85b6-41cb-9de2-b4e1b4951222",
"practices": [],
- "prerequisites": [],
- "difficulty": 6,
- "topics": [
- "concurrency",
- "maps",
+ "prerequisites": [
+ "constructors",
+ "if-else-statements",
+ "numbers",
"strings"
- ]
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "crypto-square",
+ "name": "Crypto Square",
+ "uuid": "162bebdc-9bf2-43c0-8460-a91f5fc16147",
+ "practices": [],
+ "prerequisites": [
+ "chars",
+ "constructors",
+ "for-loops",
+ "if-else-statements",
+ "numbers"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "dominoes",
+ "name": "Dominoes",
+ "uuid": "8e3cb20e-623b-4b4d-8a91-d1a51c0911b5",
+ "practices": [],
+ "prerequisites": [
+ "exceptions",
+ "lists"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "go-counting",
+ "name": "Go Counting",
+ "uuid": "2e760ae2-fadd-4d31-9639-c4554e2826e9",
+ "practices": [],
+ "prerequisites": [
+ "enums",
+ "maps"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "markdown",
+ "name": "Markdown",
+ "uuid": "cc18f2e5-4e36-47d6-aa60-8ca5ff54019a",
+ "practices": [],
+ "prerequisites": [
+ "if-else-statements",
+ "strings"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "meetup",
+ "name": "Meetup",
+ "uuid": "602511d5-7e89-4def-b072-4dd311816810",
+ "practices": [],
+ "prerequisites": [
+ "datetime",
+ "enums"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "poker",
+ "name": "Poker",
+ "uuid": "57eeac00-741c-4843-907a-51f0ac5ea4cb",
+ "practices": [],
+ "prerequisites": [
+ "constructors",
+ "if-else-statements",
+ "lists"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "sgf-parsing",
+ "name": "SGF Parsing",
+ "uuid": "0d6325d1-c0a3-456e-9a92-cea0559e82ed",
+ "practices": [],
+ "prerequisites": [
+ "strings",
+ "chars",
+ "if-else-statements",
+ "lists",
+ "for-loops",
+ "maps"
+ ],
+ "difficulty": 7
},
{
"slug": "simple-linked-list",
"name": "Simple Linked List",
"uuid": "e3e5ffe5-cfc1-467e-a28a-da0302130144",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "algorithms",
- "exception_handling",
- "generics",
+ "prerequisites": [
+ "constructors",
+ "exceptions",
+ "generic-types",
"lists"
- ]
+ ],
+ "difficulty": 7
},
{
"slug": "sublist",
"name": "Sublist",
"uuid": "d2aedbd7-092a-43d0-8a5e-ae3ebd5b9c7f",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "enumerations",
- "generics",
- "lists",
- "loops",
- "searching"
- ]
+ "prerequisites": [
+ "enums",
+ "generic-types"
+ ],
+ "difficulty": 7
},
{
"slug": "tree-building",
"name": "Tree Building",
"uuid": "cb9540e2-a980-4275-924e-bdb504f04363",
"practices": [],
+ "prerequisites": [
+ "classes",
+ "exceptions",
+ "if-else-statements",
+ "lists"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "two-bucket",
+ "name": "Two Bucket",
+ "uuid": "210bf628-b385-443b-8329-3483cc6e8d7e",
+ "practices": [],
+ "prerequisites": [
+ "constructors",
+ "if-else-statements",
+ "numbers"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "word-search",
+ "name": "Word Search",
+ "uuid": "b53bde52-cb5f-4d43-86ec-18aa509d62f9",
+ "practices": [],
+ "prerequisites": [
+ "arrays",
+ "strings",
+ "if-else-statements",
+ "maps"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "zebra-puzzle",
+ "name": "Zebra Puzzle",
+ "uuid": "b1e2bd39-3f4b-44c1-b7e2-258d4ee241f8",
+ "practices": [],
"prerequisites": [],
- "difficulty": 7,
- "topics": [
- "maps",
- "records",
- "refactoring",
- "sorting",
- "trees"
- ]
+ "difficulty": 7
},
{
"slug": "zipper",
"name": "Zipper",
"uuid": "25d2c7a5-1ec8-464a-b3de-ad1b27464ef1",
"practices": [],
- "prerequisites": [],
- "difficulty": 7,
- "topics": [
- "integers",
- "graphs",
- "trees"
- ]
+ "prerequisites": [
+ "classes",
+ "constructors",
+ "if-else-statements",
+ "numbers",
+ "strings"
+ ],
+ "difficulty": 7
+ },
+ {
+ "slug": "book-store",
+ "name": "Book Store",
+ "uuid": "e5f05d00-fe5b-4d78-b2fa-934c1c9afb32",
+ "practices": [],
+ "prerequisites": [
+ "exceptions",
+ "if-else-statements",
+ "lists",
+ "numbers"
+ ],
+ "difficulty": 8
+ },
+ {
+ "slug": "change",
+ "name": "Change",
+ "uuid": "bac1f4bc-eea9-43c1-8e95-097347f5925e",
+ "practices": [],
+ "prerequisites": [
+ "exceptions",
+ "lists",
+ "numbers"
+ ],
+ "difficulty": 8
+ },
+ {
+ "slug": "circular-buffer",
+ "name": "Circular Buffer",
+ "uuid": "626dc25a-062c-4053-a8c1-788e4dc44ca0",
+ "practices": [],
+ "prerequisites": [
+ "classes",
+ "exceptions",
+ "generic-types"
+ ],
+ "difficulty": 8
+ },
+ {
+ "slug": "complex-numbers",
+ "name": "Complex Numbers",
+ "uuid": "52d11278-0d65-4b5b-b387-1374fced3243",
+ "practices": [],
+ "prerequisites": [
+ "numbers"
+ ],
+ "difficulty": 8
},
{
- "slug": "circular-buffer",
- "name": "Circular Buffer",
- "uuid": "626dc25a-062c-4053-a8c1-788e4dc44ca0",
+ "slug": "connect",
+ "name": "Connect",
+ "uuid": "1532b9a8-7e0d-479f-ad55-636e01e9d19f",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "classes",
- "exception_handling",
- "queues"
- ]
+ "prerequisites": [
+ "enums"
+ ],
+ "difficulty": 8
},
{
"slug": "diffie-hellman",
@@ -1669,233 +1757,179 @@
"practices": [],
"prerequisites": [],
"difficulty": 8,
- "topics": [
- "algorithms",
- "integers",
- "math",
- "transforming"
- ]
+ "status": "deprecated"
},
{
"slug": "hangman",
"name": "Hangman",
"uuid": "ab3f8bf4-cfae-4f7a-b134-bb0fa4fafa63",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "strings",
- "reactive_programming",
- "functional_programming"
- ]
+ "prerequisites": [
+ "enums"
+ ],
+ "difficulty": 8
},
{
"slug": "list-ops",
"name": "List Ops",
"uuid": "a9836565-5c39-4285-b83a-53408be36ccc",
"practices": [],
- "prerequisites": [],
- "difficulty": 8,
- "topics": [
- "filtering",
- "functional_programming",
- "generics",
- "lists",
- "loops"
- ]
- },
- {
- "slug": "custom-set",
- "name": "Custom Set",
- "uuid": "377fe38b-08ad-4f3a-8118-a43c10f7b9b2",
- "practices": [],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "equality",
- "generics",
- "sets"
- ]
- },
- {
- "slug": "satellite",
- "name": "Satellite",
- "uuid": "a5f8aef3-9661-49c7-9eb3-786ef9fe0e85",
- "practices": [],
- "prerequisites": [],
- "difficulty": 10,
- "topics": [
- "trees",
- "graphs"
- ]
- },
- {
- "slug": "accumulate",
- "name": "Accumulate",
- "uuid": "e58c29d2-80a7-40ef-bed0-4a184ae35f34",
- "practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": null,
- "status": "deprecated"
- },
- {
- "slug": "binary",
- "name": "Binary",
- "uuid": "9ea6e0fa-b91d-4d17-ac8f-6d2dc30fdd44",
- "practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": null,
- "status": "deprecated"
+ "prerequisites": [
+ "generic-types",
+ "lists"
+ ],
+ "difficulty": 8
},
{
- "slug": "hexadecimal",
- "name": "Hexadecimal",
- "uuid": "6fe53a08-c123-465d-864a-ef18217203c4",
+ "slug": "mazy-mice",
+ "name": "Mazy Mice",
+ "uuid": "1bac7473-9ee8-4cfc-928b-77792102ffc1",
"practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": null,
- "status": "deprecated"
+ "prerequisites": [
+ "for-loops",
+ "randomness",
+ "strings"
+ ],
+ "difficulty": 8,
+ "status": "beta"
},
{
- "slug": "octal",
- "name": "Octal",
- "uuid": "14a29e82-f9b1-4662-b678-06992e306c01",
+ "slug": "ocr-numbers",
+ "name": "OCR Numbers",
+ "uuid": "5cbc6a67-3a53-4aad-ae68-ff469525437f",
"practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": null,
- "status": "deprecated"
+ "prerequisites": [
+ "exceptions",
+ "lists",
+ "strings"
+ ],
+ "difficulty": 8
},
{
- "slug": "strain",
- "name": "Strain",
- "uuid": "2d195cd9-1814-490a-8dd6-a2c676f1d4cd",
+ "slug": "palindrome-products",
+ "name": "Palindrome Products",
+ "uuid": "873c05de-b5f5-4c5b-97f0-d1ede8a82832",
"practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": null,
- "status": "deprecated"
+ "prerequisites": [
+ "exceptions",
+ "for-loops",
+ "if-else-statements",
+ "maps",
+ "numbers"
+ ],
+ "difficulty": 8
},
{
- "slug": "trinary",
- "name": "Trinary",
- "uuid": "ee3a8abe-6b19-4b47-9cf6-4d39ae915fb7",
+ "slug": "pov",
+ "name": "POV",
+ "uuid": "cae26d37-2977-42c4-af10-b6bb83adef19",
"practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": null,
- "status": "deprecated"
+ "prerequisites": [
+ "strings",
+ "if-else-statements",
+ "lists",
+ "for-loops"
+ ],
+ "difficulty": 8
},
{
- "slug": "leap",
- "name": "Leap",
- "uuid": "61fe1fa6-246d-4e38-92d6-b74af64c88af",
+ "slug": "rational-numbers",
+ "name": "Rational Numbers",
+ "uuid": "50ed54ce-3047-4590-9fda-0a7e9aeeba30",
"practices": [],
- "prerequisites": [],
- "difficulty": 1,
- "topics": [
- "booleans",
- "integers",
- "logic"
- ]
+ "prerequisites": [
+ "numbers"
+ ],
+ "difficulty": 8
},
{
- "slug": "armstrong-numbers",
- "name": "Armstrong Numbers",
- "uuid": "8e1dd48c-e05e-4a72-bf0f-5aed8dd123f5",
+ "slug": "react",
+ "name": "React",
+ "uuid": "33531718-1d8a-4abd-bd2a-090303ad0c39",
"practices": [],
- "prerequisites": [],
- "difficulty": 2,
- "topics": [
- "integers",
- "math"
- ]
+ "prerequisites": [
+ "classes",
+ "generic-types"
+ ],
+ "difficulty": 8
},
{
- "slug": "rna-transcription",
- "name": "RNA Transcription",
- "uuid": "8e983ed2-62f7-439a-a144-fb8fdbdf4d30",
+ "slug": "rectangles",
+ "name": "Rectangles",
+ "uuid": "64cda115-919a-4eef-9a45-9ec5d1af98cc",
"practices": [],
- "prerequisites": [],
- "difficulty": 2,
- "topics": [
- "loops",
- "maps",
+ "prerequisites": [
+ "arrays",
+ "exceptions",
"strings"
- ]
+ ],
+ "difficulty": 8
},
{
- "slug": "acronym",
- "name": "Acronym",
- "uuid": "c31bbc6d-bdcf-44f7-bf35-5f98752e38d0",
+ "slug": "simple-cipher",
+ "name": "Simple Cipher",
+ "uuid": "f654831f-c34b-44f5-9dd5-054efbb486f2",
"practices": [],
- "prerequisites": [],
- "difficulty": 3,
- "topics": [
- "loops",
- "parsing",
- "searching",
- "strings"
- ]
+ "prerequisites": [
+ "chars",
+ "exceptions",
+ "for-loops",
+ "if-else-statements",
+ "numbers",
+ "randomness"
+ ],
+ "difficulty": 8
},
{
- "slug": "pangram",
- "name": "Pangram",
- "uuid": "133b0f84-bdc7-4508-a0a1-5732a7db81ef",
+ "slug": "forth",
+ "name": "Forth",
+ "uuid": "f0c0316d-3fb5-455e-952a-91161e7fb298",
"practices": [],
- "prerequisites": [],
- "difficulty": 3,
- "topics": [
- "pattern_matching",
- "regular_expressions",
+ "prerequisites": [
+ "exceptions",
+ "lists",
"strings"
- ]
+ ],
+ "difficulty": 9
},
{
- "slug": "space-age",
- "name": "Space Age",
- "uuid": "e5b524cd-3a1c-468a-8981-13e8eeccb29d",
+ "slug": "pythagorean-triplet",
+ "name": "Pythagorean Triplet",
+ "uuid": "88505f95-89e5-4a08-8ed2-208eb818cdf1",
"practices": [],
- "prerequisites": [],
- "difficulty": 3,
- "topics": [
- "conditionals-if",
- "floating_point_numbers"
- ]
+ "prerequisites": [
+ "constructors",
+ "lists",
+ "numbers"
+ ],
+ "difficulty": 9
},
{
- "slug": "connect",
- "name": "Connect",
- "uuid": "1532b9a8-7e0d-479f-ad55-636e01e9d19f",
- "practices": ["enums", "switch-statement"],
+ "slug": "custom-set",
+ "name": "Custom Set",
+ "uuid": "377fe38b-08ad-4f3a-8118-a43c10f7b9b2",
+ "practices": [],
"prerequisites": [
- "strings",
- "chars",
- "enums",
- "arrays",
- "conditionals-if"
+ "generic-types",
+ "if-else-statements"
],
- "difficulty": 8
+ "difficulty": 10
},
{
- "slug": "sgf-parsing",
- "name": "SGF Parsing",
- "uuid": "0d6325d1-c0a3-456e-9a92-cea0559e82ed",
+ "slug": "satellite",
+ "name": "Satellite",
+ "uuid": "a5f8aef3-9661-49c7-9eb3-786ef9fe0e85",
"practices": [],
"prerequisites": [
- "strings",
- "chars",
- "conditionals-if",
- "lists",
- "for-loops"
+ "classes",
+ "lists"
],
- "difficulty": 7
+ "difficulty": 10
}
],
- "foregone": []
+ "foregone": [
+ "lens-person"
+ ]
},
"concepts": [
{
@@ -1908,6 +1942,11 @@
"slug": "basics",
"name": "Basics"
},
+ {
+ "uuid": "3491358c-26fd-4c08-91c4-a6f76b3ed01a",
+ "slug": "bit-manipulation",
+ "name": "Bit Manipulation"
+ },
{
"uuid": "ea12527d-7a9d-461e-93b1-bf639652430e",
"slug": "booleans",
@@ -1925,14 +1964,29 @@
},
{
"uuid": "4ce7cdbb-4ff3-4988-89db-8fbcef380500",
- "slug": "conditionals-if",
- "name": "Conditionals If"
+ "slug": "if-else-statements",
+ "name": "If-Else Statements"
},
{
"uuid": "e552c05c-6360-4b2f-a438-7ec7e855c0f5",
"slug": "constructors",
"name": "Constructors"
},
+ {
+ "uuid": "16e1b053-f99d-410e-8b3c-054e764da953",
+ "slug": "datetime",
+ "name": "Date-Time"
+ },
+ {
+ "uuid": "cb753863-b7c1-4ff6-adeb-ae9a1f39deca",
+ "slug": "enums",
+ "name": "Enums"
+ },
+ {
+ "uuid": "b7f8e129-303c-45b8-ac07-297bfd5a9906",
+ "slug": "exceptions",
+ "name": "Exceptions"
+ },
{
"uuid": "3a1f7a96-b4a5-43d3-99e2-7c248e74e6c8",
"slug": "foreach-loops",
@@ -1963,11 +2017,36 @@
"slug": "lists",
"name": "Lists"
},
+ {
+ "uuid": "2f6fdedb-a0ac-4bab-92d6-3be61520b9bc",
+ "slug": "maps",
+ "name": "Maps"
+ },
+ {
+ "uuid": "54118389-9c01-431b-a850-f47da498f845",
+ "slug": "method-overloading",
+ "name": "Method Overloading"
+ },
+ {
+ "uuid": "0718bff1-25ad-42bb-860d-1b0834beb9fc",
+ "slug": "nullability",
+ "name": "Nullability"
+ },
{
"uuid": "58529dab-0ef2-4943-ac12-a98ca79b922b",
"slug": "numbers",
"name": "Numbers"
},
+ {
+ "uuid": "07674a94-5f10-4b99-8f18-36c841e4aff8",
+ "slug": "randomness",
+ "name": "Randomness"
+ },
+ {
+ "uuid": "775572da-46f5-4d8a-b154-f35ae344ea40",
+ "slug": "sets",
+ "name": "Sets"
+ },
{
"uuid": "8a468b14-724a-4036-8edb-d19a02809840",
"slug": "strings",
@@ -1986,51 +2065,51 @@
],
"key_features": [
{
- "icon": "evolving",
"title": "Modern",
- "content": "Java is a modern, fast-evolving language with releases every 6 months."
+ "content": "Java is a modern, fast-evolving language with releases every 6 months.",
+ "icon": "evolving"
},
{
- "icon": "statically-typed",
"title": "Statically-typed",
- "content": "Every expression has a type known at compile time."
+ "content": "Every expression has a type known at compile time.",
+ "icon": "statically-typed"
},
{
- "icon": "multi-paradigm",
"title": "Multi-paradigm",
- "content": "Java is primarily an object-oriented language, but has many functional features introduced in v1.8."
+ "content": "Java is primarily an object-oriented language, but has many functional features introduced in v1.8.",
+ "icon": "multi-paradigm"
},
{
- "icon": "general-purpose",
"title": "General purpose",
- "content": "Java is used for a variety of workloads like web, cloud, mobile and game applications."
+ "content": "Java is used for a variety of workloads like web, cloud, mobile and game applications.",
+ "icon": "general-purpose"
},
{
- "icon": "portable",
"title": "Portable",
- "content": "Java was designed to be cross-platform with the slogan \"Write once, run anywhere\"."
+ "content": "Java was designed to be cross-platform with the slogan \"Write once, run anywhere\".",
+ "icon": "portable"
},
{
- "icon": "garbage-collected",
"title": "Garbage Collection",
- "content": "Java programs perform automatic memory management for their lifecycles."
+ "content": "Java programs perform automatic memory management for their lifecycles.",
+ "icon": "garbage-collected"
}
],
"tags": [
+ "execution_mode/compiled",
"paradigm/functional",
"paradigm/imperative",
"paradigm/object_oriented",
- "typing/static",
- "execution_mode/compiled",
- "platform/windows",
- "platform/mac",
- "platform/linux",
"platform/android",
+ "platform/linux",
+ "platform/mac",
+ "platform/windows",
"runtime/jvm",
+ "typing/static",
+ "used_for/artificial_intelligence",
"used_for/backends",
"used_for/cross_platform_development",
- "used_for/mobile",
- "used_for/artificial_intelligence",
- "used_for/games"
+ "used_for/games",
+ "used_for/mobile"
]
}
diff --git a/config/exercise-readme-insert.md b/config/exercise-readme-insert.md
deleted file mode 100644
index d8716cd80..000000000
--- a/config/exercise-readme-insert.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Setup
-
-Go through the setup instructions for Java to install the necessary
-dependencies:
-
-[https://exercism.io/tracks/java/installation](https://exercism.io/tracks/java/installation)
-
-# Running the tests
-
-You can run all the tests for an exercise by entering the following in your
-terminal:
-
-```sh
-$ gradle test
-```
-
-In the test suites all tests but the first have been skipped.
-
-Once you get a test passing, you can enable the next one by removing the
-`@Ignore("Remove to run test")` annotation.
diff --git a/config/exercise_readme.go.tmpl b/config/exercise_readme.go.tmpl
deleted file mode 100644
index 5fe39b139..000000000
--- a/config/exercise_readme.go.tmpl
+++ /dev/null
@@ -1,19 +0,0 @@
-# {{ .Spec.Name }}
-
-{{ .Spec.Description -}}
-{{- with .Hints }}
-# Tips
-
-{{ . }}
-{{ end }}
-{{- with .TrackInsert }}
-{{ . }}
-{{ end }}
-{{- with .Spec.Credits -}}
-## Source
-
-{{ . }}
-{{ end }}
-## Submitting Incomplete Solutions
-It's possible to submit an incomplete solution so you can see how others have
-completed the exercise.
diff --git a/docs/ABOUT.md b/docs/ABOUT.md
index 02817ea99..f1c0e8715 100644
--- a/docs/ABOUT.md
+++ b/docs/ABOUT.md
@@ -1,19 +1,19 @@
# About
-[Java](https://go.java/index.html) is among the most popular available programming languages, thanks to its versatility and compatibility.
+[Java](https://go.java/index.html) is among the most popular available programming languages, thanks to its versatility and compatibility.
It is widely used for software development, mobile applications and developing larger systems.
-Java was born in 1995 and is maintained by [Oracle](https://www.oracle.com/index.html).
-Despite the fact that it isn't as young as some of the fresh languages out there, Java is still really popular.
-It was designed to be fast, secure, reliable, beginner-friendly and highly portable.
-This portability perk exists because Java is executed on a cross-platform compatible [Java Virtual Machine - JVM](https://en.wikipedia.org/wiki/Java_virtual_machine).
-Android apps are also developed using Java, since the [Android Operating System](https://en.wikipedia.org/wiki/Android_(operating_system)) runs on a Java language environment.
+Java was born in 1995 and is maintained by [Oracle](https://www.oracle.com/index.html).
+Despite the fact that it isn't as young as some of the fresh languages out there, Java is still really popular.
+It was designed to be fast, secure, reliable, beginner-friendly and highly portable.
+This portability perk exists because Java is executed on a cross-platform compatible [Java Virtual Machine - JVM](https://en.wikipedia.org/wiki/Java_virtual_machine).
+Android apps are also developed using Java, since the [Android Operating System]() runs on a Java language environment.
The Java community is huge!
-GitHub for example has over 1.5 million Java projects.
-It's also worth mentioning that Java has the second largest community in [StackOverflow](https://stackoverflow.com/questions/tagged/java)!
+GitHub for example has over 1.5 million Java projects.
+It's also worth mentioning that Java has the second largest community in [StackOverflow](https://stackoverflow.com/questions/tagged/java)!
This is important because the larger a programming language community is, the more support you'd be likely to get.
Java also has a powerful and well-designed set of built-in [APIs - Application Programming Interfaces](https://docs.oracle.com/en/java/javase/11/docs/api/index.html), which can be used for various activities like Database connection, networking, I/O, XML parsing, utilities, and much more.
-From laptops to datacenters, game consoles to scientific supercomputers, cell phones to the Internet, [Java is everywhere](https://en.wikipedia.org/wiki/Write_once,_run_anywhere)!
+From laptops to data centers, game consoles to scientific supercomputers, cell phones to the Internet, [Java is everywhere](https://en.wikipedia.org/wiki/Write_once,_run_anywhere)!
diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md
index 7f1593bd4..57cf6d012 100644
--- a/docs/INSTALLATION.md
+++ b/docs/INSTALLATION.md
@@ -1,256 +1,40 @@
# Installing Java
-In addition to the exercism CLI and your favorite text editor, practicing with Exercism exercises in Java requires:
+In addition to the Exercism CLI and your favorite text editor,
+practicing with Exercism exercises in Java requires the **Java Development Kit** (JDK).
+The JDK includes both a Java Runtime _and_ development tools (most notably, the Java compiler).
-* the **Java Development Kit** (JDK) — which includes both a Java Runtime *and* development tools (most notably, the Java compiler); and
-* **Gradle** — a build tool specifically for Java projects.
+There are many flavors of the JDK nowadays, some of which are open-source.
+Here at Exercism we recommend using [Eclipse Temurin by Adoptium][adoptium].
-Choose your operating system:
+## Supported Java versions
-* [Windows](#windows)
-* [macOS](#macos)
-* [Linux](#linux)
+Exercism's Java track supports Java 25 LTS and earlier.
-... or ...
-* if you prefer to not use a package manager, you can [install manually](#install-manually).
+## Installing Eclipse Temurin
-Optionally, you can also use a [Java IDE](#java-ides).
+To install Eclipse Temurin on your system, head on over to their excellent [installation instructions][adoptium-installation-guide].
+Here you will find instructions on how to install the JDK using your favorite package manager,
+and it also contains links to downloadable installers for all major operating systems.
-----
+## Java IDEs
-# Windows
+There are many free IDEs available for Java.
+Here are some popular choices:
-Open an administrative command prompt. (If you need assistance opening an administrative prompt, see [open an elevated prompt in Windows 8+](http://www.howtogeek.com/194041/how-to-open-the-command-prompt-as-administrator-in-windows-8.1/) (or [Windows 7](http://www.howtogeek.com/howto/windows-vista/run-a-command-as-administrator-from-the-windows-vista-run-box/)).
-
-1. If you have not installed Chocolatey, do so now:
-
- ```batchfile
- C:\Windows\system32> @powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin
- ```
-
-2. Install the JDK:
-
- ```batchfile
- C:\Windows\system32> choco install openjdk11
- ...
- C:\Windows\system32> refreshenv
- ...
- ```
-3. Install Gradle:
-
- ```batchfile
- C:\Windows\system32>choco install gradle
- ...
- ```
-
-We recommend closing the administrative command prompt and opening a new command prompt -- you do not require administrator privileges to practice Exercism exercises.
-
-You now are ready to get started with the Java track of Exercism!
-
-To get started, see "[Running the Tests](https://exercism.org/docs/tracks/java/tests)".
-
-----
-
-# macOS
-
-Below are instructions for install using the most common method - using Homebrew. If you'd rather, you can also [install on macOS without Homebrew](#installing-on-macos-without-homebrew).
-
-## Installing
-
-1. If you haven't installed [Homebrew](http://brew.sh), yet, do so now:
-
- ```sh
- $ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- ```
-
-2. Tap the [Homebrew Cask](https://caskroom.github.io/) — this allows us to install pre-built binaries like the JDK.
-
- ```
- $ brew tap adoptopenjdk/openjdk
- ```
-
-3. Install the JDK:
-
- ```
- $ brew install --cask adoptopenjdk11
- ```
-
-4. Install Gradle:
-
- ```
- $ brew install gradle
- ```
-
-You now are ready to get started with the Java track of Exercism!
-
-To get started, see "[Running the Tests](https://exercism.org/docs/tracks/java/tests)".
-
-----
-
-# Linux
-
-Below are instructions for install using the package manager of your distro. If you'd rather, you can also [install on Linux without a package manager](#installing-on-linux-without-a-package-manager).
-
-* [Debian](#debian)
-* [Fedora](#fedora)
-
-## Debian
-
-If you are using Debian or its derivatives (like Ubuntu), use APT:
-
-*(verified on: Ubuntu 14, 16 and 18)*
-
-1. Install the JDK:
-
- ```sh
- $ sudo apt-get update
- $ sudo apt-get install software-properties-common
- $ sudo add-apt-repository ppa:openjdk-r/ppa
- $ sudo apt-get update
- $ sudo apt-get install openjdk-11-jdk
- ```
-
-2. Install Gradle:
-
- ```sh
- $ sudo add-apt-repository ppa:cwchien/gradle
- $ sudo apt-get update
- $ sudo apt-get install gradle
- ```
-
-You now are ready to get started with the Java track of Exercism!
-
-To get started, see "[Running the Tests](https://exercism.org/docs/tracks/java/tests)".
-
-----
-
-## Other Linux distributions
-
-There are a lot of ways to install Jdk 11, but one of the easiest ways is to use SDKMAN, which lets you install
-both OpenJdk11 and the latest Gradle with ease. Use the following steps:
-
-1. Install SDKMAN:
- ```sh
- $ curl -s "https://get.sdkman.io" | bash
- ```
- (if that doesn't work, take a look at the instructions found here: https://sdkman.io/install )
-1. Install openjdk11:
- ```
- $ sdk install java 11.0.2-open
- ```
-1. Install Gradle:
- ```sh
- $ sdk install gradle
- ```
-
-You now are ready to get started with the Java track of Exercism!
-
-To get started, see "[Running the Tests](https://exercism.org/docs/tracks/java/tests)".
-
-----
-
-# Install Manually
-
-* [Installing on Windows manually](#installing-on-windows-manually)
-* [Installing on macOS without Homebrew](#installing-on-macos-without-homebrew)
-* [Installing on Linux without a package manager](#installing-on-linux-without-a-package-manager)
-
-----
-
-## Installing on Windows manually
-
-*NOTE: these instructions are intended for experienced Windows users. If you don't already know how to set environment variables or feel comfortable managing the directory structure, we highly recommend you use the Chocolatey-based install, [above](#windows).*
-
-1. Install the JDK:
- 1. Download "**OpenJDK 11 (LTS)**" from [AdoptOpenJDK](https://adoptopenjdk.net/releases.html?variant=openjdk11#x64_win) (choose **"Install JDK"**).
- - Run the installer, using all the defaults.
-2. Install Gradle:
- - Download "**Binary only distribution**" from the [Gradle download page](https://gradle.org/gradle-download/).
- - Unzip the archive. We recommend a place like `C:\Users\JohnDoe\Tools`.
- - Add a new system environment variable named `GRADLE_HOME` and set it to the path you just created (e.g. `C:\Users\JohnDoe\Tools\gradle-x.y`).
- - Update the system `Path` to include the `bin` directory from Gradle's home (e.g. `Path`=`...;%GRADLE_HOME%\bin`).
+- [IntelliJ IDEA Community Edition][intellij-idea]
+- [Eclipse][eclipse]
+- [Visual Studio Code with the Java extension][vscode-java]
+## Next steps
You now are ready to get started with the Java track of Exercism!
-To get started, see "[Running the Tests](https://exercism.org/docs/tracks/java/tests)".
-
-----
-
-## Installing on macOS without Homebrew
-
-*NOTE: these instructions are intended for experienced macOS users. Unless you specifically do not want to use a package manager, we highly recommend using the Homebrew-based installation instructions, [above](#macos).*
-
-1. Install the JDK:
- 1. Download "**OpenJDK 11 (LTS)**" from [AdoptOpenJDK](https://adoptopenjdk.net/releases.html?variant=openjdk11#x64_mac) (choose **"Install JDK"**).
- 2. Run the installer, using all the defaults.
-2. Install Gradle:
- 1. Download "**Binary only distribution**" from the [Gradle download page](https://gradle.org/gradle-download/).
- 2. Unpack Gradle:
-
- ```sh
- $ mkdir ~/tools
- $ cd ~/tools
- $ unzip ~/Downloads/gradle-*-bin.zip
- $ cd gradle*
- ```
-
- 3. Configure Gradle and add it to the path:
-
- ```sh
- $ cat << DONE >> ~/.bashrc
- export GRADLE_HOME=`pwd`
- export PATH=\$PATH:\$GRADLE_HOME/bin
- DONE
- ```
-
-
-You now are ready to get started with the Java track of Exercism!
-
-To get started, see "[Running the Tests](https://exercism.org/docs/tracks/java/tests)".
-
-----
-
-## Installing on Linux without a package manager
-
-*NOTE: these instructions are intended for experienced Linux users. Unless you specifically do not want to use a package manager, we highly recommend using the installation instructions, [above](#linux).*
-
-1. Install the JDK:
- 1. Choose your distribution and download "**OpenJDK 11 (LTS)**" from [AdoptOpenJDK](https://adoptopenjdk.net/releases.html?variant=openjdk11) (choose **"Install JDK"**).
- 2. Run the installer, using all the defaults.
-2. Install Gradle:
- 1. Download "**Binary only distribution**" from the [Gradle download page](https://gradle.org/gradle-download/).
- 2. Unpack Gradle:
-
- ```sh
- $ mkdir ~/tools
- $ cd ~/tools
- $ unzip ~/Downloads/gradle-*-bin.zip
- $ cd gradle*
- ```
-
- 3. Configure Gradle and add it to the path:
-
- ```sh
- $ cat << DONE >> ~/.bashrc
- export GRADLE_HOME=`pwd`
- export PATH=\$PATH:\$GRADLE_HOME/bin
- DONE
- ```
-
-You now are ready to get started with the Java track of Exercism!
-
-To get started, see "[Running the Tests](https://exercism.org/docs/tracks/java/tests)".
-
-----
-
-# Java IDEs
-
-There are many Java IDEs available. The three most popular are:
-
-* [IntelliJ IDEA](https://www.jetbrains.com/idea/download/) (download the "Community" edition)
-- [Eclipse](https://www.eclipse.org/downloads/)
-- [NetBeans](https://netbeans.org/downloads/) (download the "Java SE" bundle)
-
-and there are [others](https://en.wikibooks.org/wiki/Java_Programming/Java_IDEs).
+To get started, see "[Running the Tests][exercism-java-tests-docs]".
+[eclipse]: https://www.eclipse.org/downloads/
+[exercism-java-tests-docs]: https://exercism.org/docs/tracks/java/tests
+[intellij-idea]: https://www.jetbrains.com/idea/download/
+[adoptium]: https://adoptium.net
+[adoptium-installation-guide]: https://adoptium.net/en-GB/installation/
+[vscode-java]: https://code.visualstudio.com/docs/languages/java
diff --git a/docs/LEARNING.md b/docs/LEARNING.md
index a6d73ab46..10a4fcfed 100644
--- a/docs/LEARNING.md
+++ b/docs/LEARNING.md
@@ -1,10 +1,10 @@
# Recommended learning resources
-* [The Java Tutorials](https://docs.oracle.com/javase/tutorial/index.html)
-* [Java SE API Documentation](https://docs.oracle.com/en/java/javase/11/docs/api/index.html)
-* [Learn X in Y minutes](https://learnxinyminutes.com/docs/java/)
-* [Free Programming Books - Java](https://github.com/EbookFoundation/free-programming-books/blob/main/books/free-programming-books-langs.md#java)
-* [Intro to Java Programming](https://www.udacity.com/course/intro-to-java-programming--cs046)
-* [Java Tutorial for Complete Beginners](https://www.udemy.com/java-tutorial/)
-* [Stack Overflow](https://stackoverflow.com/questions/tagged/java)
-* [Dev.Java (Revamped Documentation for Java)](https://dev.java/learn/)
+- [The Java Tutorials](https://docs.oracle.com/javase/tutorial/index.html)
+- [Java SE API Documentation](https://docs.oracle.com/en/java/javase/11/docs/api/index.html)
+- [Learn X in Y minutes](https://learnxinyminutes.com/docs/java/)
+- [Free Programming Books - Java](https://github.com/EbookFoundation/free-programming-books/blob/main/books/free-programming-books-langs.md#java)
+- [Intro to Java Programming](https://www.udacity.com/course/intro-to-java-programming--cs046)
+- [Java Tutorial for Complete Beginners](https://www.udemy.com/java-tutorial/)
+- [Stack Overflow](https://stackoverflow.com/questions/tagged/java)
+- [Dev.Java (Revamped Documentation for Java)](https://dev.java/learn/)
diff --git a/docs/MAINTAINING.md b/docs/MAINTAINING.md
index 69629d3df..8ca455985 100644
--- a/docs/MAINTAINING.md
+++ b/docs/MAINTAINING.md
@@ -1,4 +1,4 @@
-# Welcome!
+# Maintaining
This is a guide/reference for maintainers of the Java track.
@@ -6,9 +6,9 @@ This is a guide/reference for maintainers of the Java track.
- [Maintainer Guides](#maintainer-guides)
- [Miscellaneous](#miscellaneous)
-## Your Permissions
+## Your Permissions
-As a maintainer, you have write access to several repositories. "write access" means you can: review, reject, accept and merge PRs; and push changes to these repos. Despite having permissions to push directly, we tend to seek review of even our own PRs.
+As a maintainer, you have write access to several repositories. "write access" means you can: review, reject, accept and merge PRs; and push changes to these repos. Despite having permissions to push directly, we tend to seek review of even our own PRs.
### Track-specific
@@ -17,7 +17,7 @@ As a maintainer, you have write access to several repositories. "write access"
### Exercism-wide
- [problem-specifications](https://github.com/exercism/problem-specifications) — the library of canonical exercises.
-- [discussions](https://github.com/exercism/discussions) — the place where project-wide conversations happen.
+- [discussions](https://github.com/exercism/discussions) — the place where project-wide conversations happen.
[issues sorted by most recently updated.](https://github.com/exercism/discussions/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
- [docs](https://github.com/exercism/docs) - the place to find project-wide documentation.
@@ -29,8 +29,8 @@ As a maintainer, you have write access to several repositories. "write access"
## Miscellaneous
-- Issues marked with "policy" are current "team agreements": [exercism?label:policy](https://github.com/search?q=org%3Aexercism+label%3Apolicy).
+- Issues marked with `policy` are current "team agreements": [exercism?label:policy](https://github.com/search?q=org%3Aexercism+label%3Apolicy).
This label is described in [discussions#96](https://github.com/exercism/discussions/issues/96).
-- Exercism has a Twitter account: [@Exercism.io](https://twitter.com/exercism_io); and a mailing list: https://tinyletter.com/exercism
+- Exercism has a Twitter account: [@Exercism.io](https://twitter.com/exercism_io); and a mailing list: [tinyletter.com/exercism](https://tinyletter.com/exercism).
**Please feel free to ask any questions. In our thinking, asking questions is far smarter than guessing.**
diff --git a/docs/RESOURCES.md b/docs/RESOURCES.md
index a90eed0af..42df6a4b0 100644
--- a/docs/RESOURCES.md
+++ b/docs/RESOURCES.md
@@ -1,6 +1,6 @@
# Useful Resources
-* [Stack Overflow](http://stackoverflow.com/questions/tagged/java).
-* [The Java subreddit](https://www.reddit.com/r/java)
-* [Official Java documentation](https://docs.oracle.com/en/java/javase/11/docs/api/index.html)
-* [Java Programming Books](https://github.com/EbookFoundation/free-programming-books/blob/main/books/free-programming-books-langs.md#java)
+- [Stack Overflow](http://stackoverflow.com/questions/tagged/java).
+- [The Java subreddit](https://www.reddit.com/r/java)
+- [Official Java documentation](https://docs.oracle.com/en/java/javase/11/docs/api/index.html)
+- [Java Programming Books](https://github.com/EbookFoundation/free-programming-books/blob/main/books/free-programming-books-langs.md#java)
diff --git a/docs/TESTS.md b/docs/TESTS.md
index 24295c954..5eca6a558 100644
--- a/docs/TESTS.md
+++ b/docs/TESTS.md
@@ -2,108 +2,101 @@
Choose your operating system:
-* [Windows](#windows)
-* [macOS](#macos)
-* [Linux](#linux)
+- [Windows](#windows)
+- [macOS](#macos)
+- [Linux](#linux)
-----
-
-# Windows
+## Windows
1. Open a Command Prompt.
2. Get the first exercise:
- ```batchfile
- C:\Users\JohnDoe>exercism download --exercise hello-world --track java
-
- Not Submitted: 1 problem
- java (Hello World) C:\Users\JohnDoe\exercism\java\hello-world
-
- New: 1 problem
- java (Hello World) C:\Users\JohnDoe\exercism\java\hello-world
-
- unchanged: 0, updated: 0, new: 1
+ ```batchfile
+ C:\Users\JohnDoe>exercism download --exercise hello-world --track java
```
3. Change directory into the exercism:
- ```batchfile
- C:\Users\JohnDoe>cd C:\Users\JohnDoe\exercism\java\hello-world
- ```
-
-4. Run the tests:
+ ```batchfile
+ C:\Users\JohnDoe>cd C:\Users\JohnDoe\exercism\java\hello-world
+ ```
- ```batchfile
- C:\Users\JohnDoe>gradle test
- ```
- *(Don't worry about the tests failing, at first, this is how you begin each exercise.)*
+4. Run the tests:
-5. Solve the exercise. Find and work through the `instructions.append.md` guide ([view on GitHub](https://github.com/exercism/java/blob/main/exercises/practice/hello-world/.docs/instructions.append.md#tutorial)).
+ ```batchfile
+ C:\Users\JohnDoe>gradlew.bat test
+ ```
+ _(Don't worry about the tests failing, at first, this is how you begin each exercise.)_
-Good luck! Have fun!
+5. Solve the exercise. Find and work through the `instructions.append.md` guide ([view on GitHub][hello-world-tutorial]).
-----
+Good luck! Have fun!
-# macOS
+## macOS
1. In the terminal window, get the first exercise:
- ```
- $ exercism download --exercise hello-world --track java
-
- New: 1 problem
- Java (Etl) /Users/johndoe/exercism/java/hello-world
-
- unchanged: 0, updated: 0, new: 1
- ```
+ ```sh
+ exercism download --exercise hello-world --track java
+ ```
2. Change directory into the exercise:
- ```
- $ cd /Users/johndoe/exercism/java/hello-world
- ```
+ ```sh
+ cd /Users/johndoe/exercism/java/hello-world
+ ```
3. Run the tests:
- ```
- $ gradle test
- ```
- *(Don't worry about the tests failing, at first, this is how you begin each exercise.)*
+ ```sh
+ ./gradlew test
+ ```
-4. Solve the exercise. Find and work through the `instructions.append.md` guide ([view on GitHub](https://github.com/exercism/java/blob/main/exercises/practice/hello-world/.docs/instructions.append.md#tutorial)).
+ _(Don't worry about the tests failing, at first, this is how you begin each exercise.)_
-Good luck! Have fun!
+4. Solve the exercise. Find and work through the `instructions.append.md` guide ([view on GitHub][hello-world-tutorial]).
-----
+Good luck! Have fun!
-# Linux
+## Linux
1. In the terminal window, get the first exercise:
- ```
- $ exercism download --exercise hello-world --track java
+ ```sh
+ exercism download --exercise hello-world --track java
+ ```
- New: 1 problem
- Java (Etl) /home/johndoe/exercism/java/hello-world
+2. Change directory into the exercise:
- unchanged: 0, updated: 0, new: 1
+ ```sh
+ cd /home/johndoe/exercism/java/hello-world
+ ```
- ```
+3. Run the tests:
-2. Change directory into the exercise:
+ ```sh
+ ./gradlew test
+ ```
- ```
- $ cd /home/johndoe/exercism/java/hello-world
- ```
+ _(Don't worry about the tests failing, at first, this is how you begin each exercise.)_
-3. Run the tests:
+ If you get the following error:
+
+ ```sh
+ ./gradlew: Permission denied
+ ```
+
+ Then the file is missing the execute permission. To fis this, run:
+
+ ```sh
+ chmod +x ./gradlew
+ ```
+
+ And now you should be able to run the previous command.
- ```
- $ gradle test
- ```
- *(Don't worry about the tests failing, at first, this is how you begin each exercise.)*
+4. Solve the exercise. Find and work through the `instructions.append.md` guide ([view on GitHub][hello-world-tutorial]).
-4. Solve the exercise. Find and work through the `instructions.append.md` guide ([view on GitHub](https://github.com/exercism/java/blob/main/exercises/practice/hello-world/.docs/instructions.append.md#tutorial)).
+Good luck! Have fun!
-Good luck! Have fun!
+[hello-world-tutorial]: https://github.com/exercism/java/blob/main/exercises/practice/hello-world/.docs/instructions.append.md#tutorial
diff --git a/exercises/build.gradle b/exercises/build.gradle
index 0adf54f5e..c35dc4fc1 100644
--- a/exercises/build.gradle
+++ b/exercises/build.gradle
@@ -12,18 +12,14 @@ subprojects {
apply plugin: "checkstyle"
- // Add a task that copies test source files into a build directory. All @Ignore annotations
- // are removed during this copying, so that when we run the copied tests, none are skipped and
- // all should execute.
- //
- // Note that journey-test.sh also strips @Ignore annotations using sed. The
- // stripping implementations here and in journey-test.sh should be kept
- // consistent.
- task copyTestsFilteringIgnores(type: Copy) {
+ // Add a task that copies test source files into a build directory.
+ // All @Disabled annotations are removed during this copying,
+ // so that when we run the copied tests, none are skipped and all should execute.
+ tasks.register('copyTestsFilteringIgnores', Copy) {
from "src/test/java"
into generatedTestSourceDir
filter { line ->
- line.contains("@Ignore") ? null : line
+ line ==~ /.*@Disabled.*/ ? null : line
}
}
@@ -31,7 +27,14 @@ subprojects {
// skip project named 'concept' or 'practice'
// they are only folders containing exercises
if(project.name == 'concept' || project.name == 'practice')
- return;
+ return
+
+ // Add a task that creates Gradle wrappers for all exercises,
+ // matching the Gradle version of the project root.
+ tasks.register('allWrappers', Wrapper) {
+ gradleVersion = "$gradle.gradleVersion"
+ validateDistributionUrl = true
+ }
sourceSets {
// Set the directory containing the reference solution as the default source set. Default
@@ -40,6 +43,12 @@ subprojects {
java.srcDirs = [".meta/src/reference/java"]
}
+ // Set the directory containing the @Disabled-stripped tests as the default test source set.
+ // Default test tasks will now run against this source set.
+ test {
+ java.srcDirs = [generatedTestSourceDir]
+ }
+
// Define a custom source set named "starterSource" that points to the starter implementations
// delivered to users. We can then use the generated "compileStarterSourceJava" task to verify
// that the starter source compiles as-is.
@@ -47,23 +56,30 @@ subprojects {
java.srcDirs = ["src/main/java"]
}
- // Set the directory containing the @Ignore-stripped tests as the default test source set.
- // Default test tasks will now run against this source set.
- test {
- java.srcDirs = [generatedTestSourceDir]
+ // Define a custom source set named "starterTest" that points to the tests delivered to users.
+ // We can then use the generated "compileStarterTestJava" task to verify that the tests compile
+ // cleanly against the starter source.
+ starterTest {
+ java.srcDirs = ["src/test/java"]
+ compileClasspath += starterSource.runtimeClasspath
}
// Log the source paths associated with each source set to verify they are what we expect.
- logCompileTaskSourcePath(project, "compileJava") // Corresponds to the "main" source set.
- logCompileTaskSourcePath(project, "compileStarterSourceJava")
+ logCompileTaskSourcePath(project, "compileJava")
logCompileTaskSourcePath(project, "compileTestJava")
+ logCompileTaskSourcePath(project, "compileStarterSourceJava")
+ logCompileTaskSourcePath(project, "compileStarterTestJava")
+ }
+ configurations {
+ starterSourceImplementation.extendsFrom implementation
+ starterTestImplementation.extendsFrom testImplementation
}
// configuration of the linter
checkstyle {
- toolVersion '7.8.1'
- configFile file("checkstyle.xml")
+ toolVersion '10.7.0'
+ configFile file("$rootDir/checkstyle.xml")
sourceSets = [project.sourceSets.main, project.sourceSets.test]
}
@@ -71,13 +87,17 @@ subprojects {
source = "src/test/java"
}
- // When running the standard test task, make sure we prepopulate the test source set with the
- // @Ignore-stripped tests.
- test.dependsOn(copyTestsFilteringIgnores)
- }
-
-
+ // When running the standard test task, make sure we pre-populate the test source set with the
+ // @Disabled-stripped tests.
+ compileTestJava.dependsOn(copyTestsFilteringIgnores)
+ // Enable test logging when running test task from the console
+ test {
+ testLogging {
+ events "passed", "skipped", "failed"
+ }
+ }
+ }
}
def logCompileTaskSourcePath(Project project, String taskName) {
diff --git a/exercises/checkstyle.xml b/exercises/checkstyle.xml
index b4a3569fd..8746c0e28 100644
--- a/exercises/checkstyle.xml
+++ b/exercises/checkstyle.xml
@@ -10,6 +10,21 @@ page at http://checkstyle.sourceforge.net/config.html -->
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -143,7 +155,7 @@ page at http://checkstyle.sourceforge.net/config.html -->
-
+
@@ -172,21 +184,6 @@ page at http://checkstyle.sourceforge.net/config.html -->
-->
-
-
-
-
-
-
-
-
-
-
@@ -310,12 +307,13 @@ page at http://checkstyle.sourceforge.net/config.html -->
-
+
+
+
+
+
+
-
-
-
-
-
+
diff --git a/exercises/concept/annalyns-infiltration/.docs/instructions.md b/exercises/concept/annalyns-infiltration/.docs/instructions.md
index 6f5503cfa..f8e35fd96 100644
--- a/exercises/concept/annalyns-infiltration/.docs/instructions.md
+++ b/exercises/concept/annalyns-infiltration/.docs/instructions.md
@@ -1,26 +1,35 @@
# Instructions
-In this exercise, you'll be implementing the quest logic for a new RPG game a friend is developing. The game's main character is Annalyn, a brave girl with a fierce and loyal pet dog. Unfortunately, disaster strikes, as her best friend was kidnapped while searching for berries in the forest. Annalyn will try to find and free her best friend, optionally taking her dog with her on this quest.
+In this exercise, you'll implement the quest logic for a new RPG game that a friend is developing.
+The game's main character is Annalyn, a brave girl with a fierce and loyal pet dog.
+Unfortunately, disaster strikes: her best friend was kidnapped while searching for berries in the forest.
+Annalyn will try to find and rescue her friend, optionally taking her dog along on the quest.
-After some time spent following her best friend's trail, she finds the camp in which her best friend is imprisoned. It turns out there are two kidnappers: a mighty knight and a cunning archer.
+After some time spent following the trail, Annalyn discovers the camp where her friend is imprisoned.
+It turns out there are two kidnappers: a mighty knight and a cunning archer.
Having found the kidnappers, Annalyn considers which of the following actions she can engage in:
-- Fast attack: a fast attack can be made if the knight is sleeping, as it takes time for him to get his armor on, so he will be vulnerable.
-- Spy: the group can be spied upon if at least one of them is awake. Otherwise, spying is a waste of time.
-- Signal prisoner: the prisoner can be signalled using bird sounds if the prisoner is awake and the archer is sleeping, as archers are trained in bird signaling, so they could intercept the message.
-- _Free prisoner_: Annalyn can try sneaking into the camp to free the prisoner.
- This is a risky thing to do and can only succeed in one of two ways:
- - If Annalyn has her pet dog with her she can rescue the prisoner if the archer is asleep.
+- Fast attack: a fast attack can be made if the knight is sleeping, as it takes time for him to put on his armor, leaving him vulnerable.
+- Spy: the group can be spied upon if at least one of them is awake.
+ Otherwise, spying is a waste of time.
+- Signal prisoner: the prisoner can be signaled using bird sounds if the prisoner is awake and the archer is sleeping.
+ Archers are trained in bird signaling and could intercept the message if they are awake.
+- _Free prisoner_: Annalyn can attempt to sneak into the camp to free the prisoner.
+ This is risky and can only succeed in one of two ways:
+ - If Annalyn has her pet dog, she can rescue the prisoner if the archer is asleep.
The knight is scared of the dog and the archer will not have time to get ready before Annalyn and the prisoner can escape.
- - If Annalyn does not have her dog then she and the prisoner must be very sneaky!
- Annalyn can free the prisoner if the prisoner is awake and the knight and archer are both sleeping, but if the prisoner is sleeping they can't be rescued: the prisoner would be startled by Annalyn's sudden appearance and wake up the knight and archer.
+ - If Annalyn does not have her pet dog, then she and the prisoner must be very sneaky!
+ Annalyn can free the prisoner if the prisoner is awake and both the knight and archer are sleeping.
+ However, if the prisoner is sleeping, they can't be rescued, as the prisoner would be startled by Annalyn's sudden appearance and wake up the knight and archer.
-You have four tasks: to implement the logic for determining if the above actions are available based on the state of the three characters found in the forest and whether Annalyn's pet dog is present or not.
+You have four tasks: to implement the logic for determining if the above actions are available based on the state of the three characters in the forest and whether Annalyn's pet dog is present or not.
## 1. Check if a fast attack can be made
-Implement the (_static_) `AnnalynsInfiltration.canFastAttack()` method that takes a boolean value that indicates if the knight is awake. This method returns `true` if a fast attack can be made based on the state of the knight. Otherwise, returns `false`:
+Implement the (_static_) `AnnalynsInfiltration.canFastAttack()` method, which takes a boolean value indicating whether the knight is awake.
+This method returns `true` if a fast attack can be made based on the state of the knight.
+Otherwise, it returns `false`:
```java
boolean knightIsAwake = true;
@@ -30,7 +39,9 @@ AnnalynsInfiltration.canFastAttack(knightIsAwake);
## 2. Check if the group can be spied upon
-Implement the (_static_) `AnnalynsInfiltration.canSpy()` method that takes three boolean values, indicating if the knight, archer and the prisoner, respectively, are awake. The method returns `true` if the group can be spied upon, based on the state of the three characters. Otherwise, returns `false`:
+Implement the (_static_) `AnnalynsInfiltration.canSpy()` method, which takes three boolean values indicating whether the knight, archer, and prisoner, respectively, are awake.
+The method returns `true` if the group can be spied upon based on the state of the three characters.
+Otherwise, it returns `false`:
```java
boolean knightIsAwake = false;
@@ -40,9 +51,11 @@ AnnalynsInfiltration.canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake);
// => true
```
-## 3. Check if the prisoner can be signalled
+## 3. Check if the prisoner can be signaled
-Implement the (_static_) `AnnalynsInfiltration.canSignalPrisoner()` method that takes two boolean values, indicating if the archer and the prisoner, respectively, are awake. The method returns `true` if the prisoner can be signalled, based on the state of the two characters. Otherwise, returns `false`:
+Implement the (_static_) `AnnalynsInfiltration.canSignalPrisoner()` method, which takes two boolean values indicating whether the archer and the prisoner, respectively, are awake.
+The method returns `true` if the prisoner can be signaled based on the state of the two characters.
+Otherwise, it returns `false`:
```java
boolean archerIsAwake = false;
@@ -53,7 +66,11 @@ AnnalynsInfiltration.canSignalPrisoner(archerIsAwake, prisonerIsAwake);
## 4. Check if the prisoner can be freed
-Implement the (_static_) `AnnalynsInfiltration.canFreePrisoner()` method that takes four boolean values. The first three parameters indicate if the knight, archer and the prisoner, respectively, are awake. The last parameter indicates if Annalyn's pet dog is present. The method returns `true` if the prisoner can be freed based on the state of the three characters and Annalyn's pet dog presence. Otherwise, it returns `false`:
+Implement the (_static_) `AnnalynsInfiltration.canFreePrisoner()` method, which takes four boolean values.
+The first three parameters indicate whether the knight, archer, and prisoner, respectively, are awake.
+The last parameter indicates whether Annalyn's pet dog is present.
+The method returns `true` if the prisoner can be freed based on the state of the three characters and the presence of Annalyn's pet dog.
+Otherwise, it returns `false`:
```java
boolean knightIsAwake = false;
diff --git a/exercises/concept/annalyns-infiltration/.docs/introduction.md b/exercises/concept/annalyns-infiltration/.docs/introduction.md
index 5d7fc347e..6ff36c4db 100644
--- a/exercises/concept/annalyns-infiltration/.docs/introduction.md
+++ b/exercises/concept/annalyns-infiltration/.docs/introduction.md
@@ -1,5 +1,22 @@
# Introduction
+## Booleans
+
Booleans in Java are represented by the `boolean` type, which values can be either `true` or `false`.
-Java supports three boolean operators: `!` (NOT), `&&` (AND), and `||` (OR).
+Java supports three boolean operators:
+
+- `!` (NOT): negates the boolean
+- `&&` (AND): takes two booleans and returns `true` if they're both `true`
+- `||` (OR): returns `true` if any of the two booleans is `true`
+
+### Examples
+
+```java
+!true // => false
+!false // => true
+true && false // => false
+true && true // => true
+false || false // => false
+false || true // => true
+```
diff --git a/exercises/concept/annalyns-infiltration/.docs/introduction.md.tpl b/exercises/concept/annalyns-infiltration/.docs/introduction.md.tpl
new file mode 100644
index 000000000..4fe2cd597
--- /dev/null
+++ b/exercises/concept/annalyns-infiltration/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:booleans}
diff --git a/exercises/concept/annalyns-infiltration/.meta/config.json b/exercises/concept/annalyns-infiltration/.meta/config.json
index ba44093c8..d58dd0728 100644
--- a/exercises/concept/annalyns-infiltration/.meta/config.json
+++ b/exercises/concept/annalyns-infiltration/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Learn about booleans while helping Annalyn rescue her friend.",
"authors": [
"mikedamay"
],
@@ -12,9 +11,13 @@
],
"exemplar": [
".meta/src/reference/java/AnnalynsInfiltration.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"csharp/annalyns-infiltration"
- ]
+ ],
+ "blurb": "Learn about booleans while helping Annalyn rescue her friend."
}
diff --git a/exercises/concept/annalyns-infiltration/.meta/design.md b/exercises/concept/annalyns-infiltration/.meta/design.md
index 4fb590e67..d4084b93b 100644
--- a/exercises/concept/annalyns-infiltration/.meta/design.md
+++ b/exercises/concept/annalyns-infiltration/.meta/design.md
@@ -17,3 +17,30 @@ Nothing to report
## Prerequisites
- `basics`: know how to define methods.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `essential`: If student returns a boolean literal, tell them it is possible to directly return the result of a expression. For example:
+
+ ```java
+ // instead of
+ if (knightIsAwake) {
+ return true;
+ } else {
+ return false;
+ }
+
+ // ... return the expression directly
+ return knightIsAwake;
+ ```
+
+- `essential`: If the student compares a boolean variable with a boolean literal (e.g. `knightIsAwake == true` or `archerIsAwake == false`), tell them this can be simplified to just the variables (e.g. `knightIsAwake` or `archerIsAwake`).
+- `essential`: If the student uses an `if` statement or the ternary operator, tell them this exercise was to explore booleans and boolean operators and this exercise can be solved without them.
+- `informative`: If the student uses an `||` expression to OR two smaller expressions and either expression is surrounded by parentheses and only ANDs some terms together (e.g. `knightIsAwake || (archerIsAwake && !prisonerIsAwake)`), tell them the parentheses is unnecessary because `&&` has the higher precedence over `||`.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/annalyns-infiltration/build.gradle b/exercises/concept/annalyns-infiltration/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/annalyns-infiltration/build.gradle
+++ b/exercises/concept/annalyns-infiltration/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/annalyns-infiltration/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/annalyns-infiltration/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/annalyns-infiltration/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/annalyns-infiltration/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/annalyns-infiltration/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/annalyns-infiltration/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/annalyns-infiltration/gradlew b/exercises/concept/annalyns-infiltration/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/annalyns-infiltration/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/annalyns-infiltration/gradlew.bat b/exercises/concept/annalyns-infiltration/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/annalyns-infiltration/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/annalyns-infiltration/src/test/java/AnnalynsInfiltrationTest.java b/exercises/concept/annalyns-infiltration/src/test/java/AnnalynsInfiltrationTest.java
index 30d133b0d..7514451bf 100644
--- a/exercises/concept/annalyns-infiltration/src/test/java/AnnalynsInfiltrationTest.java
+++ b/exercises/concept/annalyns-infiltration/src/test/java/AnnalynsInfiltrationTest.java
@@ -1,21 +1,31 @@
-import org.junit.Test;
-import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.*;
public class AnnalynsInfiltrationTest {
+
@Test
- public void cannot_execute_fast_attack_if_knight_is_awake() {
+ @Tag("task:1")
+ @DisplayName("The canFastAttack method returns false when knight is awake")
+ public void cannotExecuteFastAttackIfKnightIsAwake() {
boolean knightIsAwake = true;
assertThat(AnnalynsInfiltration.canFastAttack(knightIsAwake)).isFalse();
}
@Test
- public void can_execute_fast_attack_if_knight_is_sleeping() {
+ @Tag("task:1")
+ @DisplayName("The canFastAttack method returns true when knight is sleeping")
+ public void canExecuteFastAttackIfKnightIsSleeping() {
boolean knightIsAwake = false;
assertThat(AnnalynsInfiltration.canFastAttack(knightIsAwake)).isTrue();
}
@Test
- public void cannot_spy_if_everyone_is_sleeping() {
+ @Tag("task:2")
+ @DisplayName("The canSpy method returns false when everyone is sleeping")
+ public void cannotSpyIfEveryoneIsSleeping() {
boolean knightIsAwake = false;
boolean archerIsAwake = false;
boolean prisonerIsAwake = false;
@@ -23,7 +33,9 @@ public void cannot_spy_if_everyone_is_sleeping() {
}
@Test
- public void can_spy_if_everyone_but_knight_is_sleeping() {
+ @Tag("task:2")
+ @DisplayName("The canSpy method returns true when everyone but knight is sleeping")
+ public void canSpyIfEveryoneButKnightIsSleeping() {
boolean knightIsAwake = true;
boolean archerIsAwake = false;
boolean prisonerIsAwake = false;
@@ -31,7 +43,9 @@ public void can_spy_if_everyone_but_knight_is_sleeping() {
}
@Test
- public void can_spy_if_everyone_but_archer_is_sleeping() {
+ @Tag("task:2")
+ @DisplayName("The canSpy method returns true when everyone but archer is sleeping")
+ public void canSpyIfEveryoneButArcherIsSleeping() {
boolean knightIsAwake = false;
boolean archerIsAwake = true;
boolean prisonerIsAwake = false;
@@ -39,7 +53,9 @@ public void can_spy_if_everyone_but_archer_is_sleeping() {
}
@Test
- public void can_spy_if_everyone_but_prisoner_is_sleeping() {
+ @Tag("task:2")
+ @DisplayName("The canSpy method returns true when everyone but prisoner is sleeping")
+ public void canSpyIfEveryoneButPrisonerIsSleeping() {
boolean knightIsAwake = false;
boolean archerIsAwake = false;
boolean prisonerIsAwake = true;
@@ -47,7 +63,9 @@ public void can_spy_if_everyone_but_prisoner_is_sleeping() {
}
@Test
- public void can_spy_if_only_knight_is_sleeping() {
+ @Tag("task:2")
+ @DisplayName("The canSpy method returns true when only knight is sleeping")
+ public void canSpyIfOnlyKnightIsSleeping() {
boolean knightIsAwake = false;
boolean archerIsAwake = true;
boolean prisonerIsAwake = true;
@@ -55,7 +73,9 @@ public void can_spy_if_only_knight_is_sleeping() {
}
@Test
- public void can_spy_if_only_archer_is_sleeping() {
+ @Tag("task:2")
+ @DisplayName("The canSpy method returns true when only archer is sleeping")
+ public void canSpyIfOnlyArcherIsSleeping() {
boolean knightIsAwake = true;
boolean archerIsAwake = false;
boolean prisonerIsAwake = true;
@@ -63,7 +83,9 @@ public void can_spy_if_only_archer_is_sleeping() {
}
@Test
- public void can_spy_if_only_prisoner_is_sleeping() {
+ @Tag("task:2")
+ @DisplayName("The canSpy method returns true when only prisoner is sleeping")
+ public void canSpyIfOnlyPrisonerIsSleeping() {
boolean knightIsAwake = true;
boolean archerIsAwake = true;
boolean prisonerIsAwake = false;
@@ -71,7 +93,9 @@ public void can_spy_if_only_prisoner_is_sleeping() {
}
@Test
- public void can_spy_if_everyone_is_awake() {
+ @Tag("task:2")
+ @DisplayName("The canSpy method returns true when everyone is awake")
+ public void canSpyIfEveryoneIsAwake() {
boolean knightIsAwake = true;
boolean archerIsAwake = true;
boolean prisonerIsAwake = true;
@@ -79,35 +103,45 @@ public void can_spy_if_everyone_is_awake() {
}
@Test
- public void can_signal_prisoner_if_archer_is_sleeping_and_prisoner_is_awake() {
+ @Tag("task:3")
+ @DisplayName("The canSignalPrisoner method returns true when prisoner is awake and archer is sleeping")
+ public void canSignalPrisonerIfArcherIsSleepingAndPrisonerIsAwake() {
boolean archerIsAwake = false;
boolean prisonerIsAwake = true;
assertThat(AnnalynsInfiltration.canSignalPrisoner(archerIsAwake, prisonerIsAwake)).isTrue();
}
@Test
- public void cannot_signal_prisoner_if_archer_is_awake_and_prisoner_is_sleeping() {
+ @Tag("task:3")
+ @DisplayName("The canSignalPrisoner method returns false when prisoner is sleeping and archer is awake")
+ public void cannotSignalPrisonerIfArcherIsAwakeAndPrisonerIsSleeping() {
boolean archerIsAwake = true;
boolean prisonerIsAwake = false;
assertThat(AnnalynsInfiltration.canSignalPrisoner(archerIsAwake, prisonerIsAwake)).isFalse();
}
@Test
- public void cannot_signal_prisoner_if_archer_and_prisoner_are_both_sleeping() {
+ @Tag("task:3")
+ @DisplayName("The canSignalPrisoner method returns false when both prisoner and archer are sleeping")
+ public void cannotSignalPrisonerIfArcherAndPrisonerAreBothSleeping() {
boolean archerIsAwake = false;
boolean prisonerIsAwake = false;
assertThat(AnnalynsInfiltration.canSignalPrisoner(archerIsAwake, prisonerIsAwake)).isFalse();
}
@Test
- public void cannot_signal_prisoner_if_archer_and_prisoner_are_both_awake() {
+ @Tag("task:3")
+ @DisplayName("The canSignalPrisoner method returns false when both prisoner and archer are awake")
+ public void cannotSignalPrisonerIfArcherAndPrisonerAreBothAwake() {
boolean archerIsAwake = true;
boolean prisonerIsAwake = true;
assertThat(AnnalynsInfiltration.canSignalPrisoner(archerIsAwake, prisonerIsAwake)).isFalse();
}
@Test
- public void cannot_release_prisoner_if_everyone_is_awake_and_pet_dog_is_present() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when everyone is awake and pet dog is present")
+ public void cannotReleasePrisonerIfEveryoneIsAwakeAndPetDogIsPresent() {
boolean knightIsAwake = true;
boolean archerIsAwake = true;
boolean prisonerIsAwake = true;
@@ -117,7 +151,9 @@ public void cannot_release_prisoner_if_everyone_is_awake_and_pet_dog_is_present(
}
@Test
- public void cannot_release_prisoner_if_everyone_is_awake_and_pet_dog_is_absent() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when everyone is awake and pet dog is absent")
+ public void cannotReleasePrisonerIfEveryoneIsAwakeAndPetDogIsAbsent() {
boolean knightIsAwake = true;
boolean archerIsAwake = true;
boolean prisonerIsAwake = true;
@@ -127,7 +163,9 @@ public void cannot_release_prisoner_if_everyone_is_awake_and_pet_dog_is_absent()
}
@Test
- public void can_release_prisoner_if_everyone_is_asleep_and_pet_dog_is_present() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns true when everyone is sleeping and pet dog is present")
+ public void canReleasePrisonerIfEveryoneIsAsleepAndPetDogIsPresent() {
boolean knightIsAwake = false;
boolean archerIsAwake = false;
boolean prisonerIsAwake = false;
@@ -137,7 +175,9 @@ public void can_release_prisoner_if_everyone_is_asleep_and_pet_dog_is_present()
}
@Test
- public void cannot_release_prisoner_if_everyone_is_asleep_and_pet_dog_is_absent() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when everyone is sleeping and pet dog is absent")
+ public void cannotReleasePrisonerIfEveryoneIsAsleepAndPetDogIsAbsent() {
boolean knightIsAwake = false;
boolean archerIsAwake = false;
boolean prisonerIsAwake = false;
@@ -147,7 +187,9 @@ public void cannot_release_prisoner_if_everyone_is_asleep_and_pet_dog_is_absent(
}
@Test
- public void can_release_prisoner_if_only_prisoner_is_awake_and_pet_dog_is_present() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns true when only prisoner is awake and pet dog is present")
+ public void canReleasePrisonerIfOnlyPrisonerIsAwakeAndPetDogIsPresent() {
boolean knightIsAwake = false;
boolean archerIsAwake = false;
boolean prisonerIsAwake = true;
@@ -157,7 +199,9 @@ public void can_release_prisoner_if_only_prisoner_is_awake_and_pet_dog_is_presen
}
@Test
- public void can_release_prisoner_if_only_prisoner_is_awake_and_pet_dog_is_absent() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns true when only prisoner is awake and pet dog is absent")
+ public void canReleasePrisonerIfOnlyPrisonerIsAwakeAndPetDogIsAbsent() {
boolean knightIsAwake = false;
boolean archerIsAwake = false;
boolean prisonerIsAwake = true;
@@ -167,7 +211,9 @@ public void can_release_prisoner_if_only_prisoner_is_awake_and_pet_dog_is_absent
}
@Test
- public void cannot_release_prisoner_if_only_archer_is_awake_and_pet_dog_is_present() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when only archer is awake and pet dog is present")
+ public void cannotReleasePrisonerIfOnlyArcherIsAwakeAndPetDogIsPresent() {
boolean knightIsAwake = false;
boolean archerIsAwake = true;
boolean prisonerIsAwake = false;
@@ -177,7 +223,9 @@ public void cannot_release_prisoner_if_only_archer_is_awake_and_pet_dog_is_prese
}
@Test
- public void cannot_release_prisoner_if_only_archer_is_awake_and_pet_dog_is_absent() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when only archer is awake and pet dog is absent")
+ public void cannotReleasePrisonerIfOnlyArcherIsAwakeAndPetDogIsAbsent() {
boolean knightIsAwake = false;
boolean archerIsAwake = true;
boolean prisonerIsAwake = false;
@@ -187,7 +235,9 @@ public void cannot_release_prisoner_if_only_archer_is_awake_and_pet_dog_is_absen
}
@Test
- public void can_release_prisoner_if_only_knight_is_awake_and_pet_dog_is_present() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns true when only knight is awake and pet dog is present")
+ public void canReleasePrisonerIfOnlyKnightIsAwakeAndPetDogIsPresent() {
boolean knightIsAwake = true;
boolean archerIsAwake = false;
boolean prisonerIsAwake = false;
@@ -197,7 +247,9 @@ public void can_release_prisoner_if_only_knight_is_awake_and_pet_dog_is_present(
}
@Test
- public void cannot_release_prisoner_if_only_knight_is_awake_and_pet_dog_is_absent() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when only knight is awake and pet dog is absent")
+ public void cannotReleasePrisonerIfOnlyKnightIsAwakeAndPetDogIsAbsent() {
boolean knightIsAwake = true;
boolean archerIsAwake = false;
boolean prisonerIsAwake = false;
@@ -207,7 +259,9 @@ public void cannot_release_prisoner_if_only_knight_is_awake_and_pet_dog_is_absen
}
@Test
- public void cannot_release_prisoner_if_only_knight_is_asleep_and_pet_dog_is_present() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when only knight is sleeping and pet dog is present")
+ public void cannotReleasePrisonerIfOnlyKnightIsAsleepAndPetDogIsPresent() {
boolean knightIsAwake = false;
boolean archerIsAwake = true;
boolean prisonerIsAwake = true;
@@ -217,7 +271,9 @@ public void cannot_release_prisoner_if_only_knight_is_asleep_and_pet_dog_is_pres
}
@Test
- public void cannot_release_prisoner_if_only_knight_is_asleep_and_pet_dog_is_absent() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when only knight is sleeping and pet dog is absent")
+ public void cannotReleasePrisonerIfOnlyKnightIsAsleepAndPetDogIsAbsent() {
boolean knightIsAwake = false;
boolean archerIsAwake = true;
boolean prisonerIsAwake = true;
@@ -227,7 +283,9 @@ public void cannot_release_prisoner_if_only_knight_is_asleep_and_pet_dog_is_abse
}
@Test
- public void can_release_prisoner_if_only_archer_is_asleep_and_pet_dog_is_present() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns true when only archer is sleeping and pet dog is present")
+ public void canReleasePrisonerIfOnlyArcherIsAsleepAndPetDogIsPresent() {
boolean knightIsAwake = true;
boolean archerIsAwake = false;
boolean prisonerIsAwake = true;
@@ -237,7 +295,9 @@ public void can_release_prisoner_if_only_archer_is_asleep_and_pet_dog_is_present
}
@Test
- public void cannot_release_prisoner_if_only_archer_is_asleep_and_pet_dog_is_absent() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when only archer is sleeping and pet dog is absent")
+ public void cannotReleasePrisonerIfOnlyArcherIsAsleepAndPetDogIsAbsent() {
boolean knightIsAwake = true;
boolean archerIsAwake = false;
boolean prisonerIsAwake = true;
@@ -247,7 +307,9 @@ public void cannot_release_prisoner_if_only_archer_is_asleep_and_pet_dog_is_abse
}
@Test
- public void cannot_release_prisoner_if_only_prisoner_is_asleep_and_pet_dog_is_present() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when only prisoner is sleeping and pet dog is present")
+ public void cannotReleasePrisonerIfOnlyPrisonerIsAsleepAndPetDogIsPresent() {
boolean knightIsAwake = true;
boolean archerIsAwake = true;
boolean prisonerIsAwake = false;
@@ -257,7 +319,9 @@ public void cannot_release_prisoner_if_only_prisoner_is_asleep_and_pet_dog_is_pr
}
@Test
- public void cannot_release_prisoner_if_only_prisoner_is_asleep_and_pet_dog_is_absent() {
+ @Tag("task:4")
+ @DisplayName("The canFreePrisoner method returns false when only prisoner is sleeping and pet dog is absent")
+ public void cannotReleasePrisonerIfOnlyPrisonerIsAsleepAndPetDogIsAbsent() {
boolean knightIsAwake = true;
boolean archerIsAwake = true;
boolean prisonerIsAwake = false;
diff --git a/exercises/concept/bird-watcher/.docs/instructions.md b/exercises/concept/bird-watcher/.docs/instructions.md
index 44df8b2f6..df2fb1fb7 100644
--- a/exercises/concept/bird-watcher/.docs/instructions.md
+++ b/exercises/concept/bird-watcher/.docs/instructions.md
@@ -1,6 +1,6 @@
# Instructions
-You're an avid bird watcher that keeps track of how many birds have visited your garden in the last seven days.
+You're an avid bird watcher who keeps track of how many birds have visited your garden in the last seven days.
You have six tasks, all dealing with the numbers of birds that visited your garden.
@@ -49,7 +49,7 @@ birdCount.hasDayWithoutBirds();
## 5. Calculate the number of visiting birds for the first number of days
-Implement the `BirdWatcher.getCountForFirstDays()` method that returns the number of birds that have visited your garden from the start of the week, but limit the count to the specified number of days from the start of the week.
+Implement the `BirdWatcher.getCountForFirstDays()` method that returns the number of birds that have visited your garden from the start of the week, but limit the count to the specified number of days from the beginning of the week.
```java
int[] birdsPerDay = { 2, 5, 0, 7, 4, 1 };
@@ -60,7 +60,7 @@ birdCount.getCountForFirstDays(4);
## 6. Calculate the number of busy days
-Some days are busier that others. A busy day is one where five or more birds have visited your garden.
+Some days are busier than others. A busy day is one where five or more birds have visited your garden.
Implement the `BirdWatcher.getBusyDays()` method to return the number of busy days:
```java
diff --git a/exercises/concept/bird-watcher/.docs/introduction.md b/exercises/concept/bird-watcher/.docs/introduction.md
index c9cd337e8..9cf029fc5 100644
--- a/exercises/concept/bird-watcher/.docs/introduction.md
+++ b/exercises/concept/bird-watcher/.docs/introduction.md
@@ -1,34 +1,68 @@
-# Arrays
+# Introduction
-In Java, data structures that can hold zero or more elements are known as _collections_. An **array** is a collection that has a fixed size/length and whose elements must all be of the same type. Elements can be assigned to an array or retrieved from it using an index. Java arrays are zero-based, meaning that the first element's index is always zero:
+## Arrays
+
+In Java, arrays are a way to store multiple values of the same type in a single structure.
+Unlike other data structures, arrays have a fixed size once created.
+Elements can be assigned to an array or retrieved from it using an index.
+Java arrays use zero-based indexing: the first element's index is 0, the second element's index is 1, etc.
+
+Here is the standard syntax for initializing an array:
+
+```java
+type[] variableName = new type[size];
+```
+
+The `type` is the type of elements in the array which may be a primitive type (e.g. `int`) or a class (e.g. `String`).
+
+The `size` is the number of elements this array will hold (which cannot be changed later).
+After array creation, the elements are initialized to their default values (typically `0`, `false` or `null`).
```java
// Declare array with explicit size (size is 2)
int[] twoInts = new int[2];
+```
+
+Arrays can also be defined using a shortcut notation that allows you to both create the array and set its value:
+```java
+// Two equivalent ways to declare and initialize an array (size is 3)
+int[] threeIntsV1 = new int[] { 4, 9, 7 };
+int[] threeIntsV2 = { 4, 9, 7 };
+```
+
+As the compiler can now tell how many elements the array will have, the length can be omitted.
+
+Array elements may be assigned and accessed using a bracketed index notation:
+
+```java
// Assign second element by index
twoInts[1] = 8;
// Retrieve the second element by index and assign to the int element
-int element = twoInts[1];
+int secondElement = twoInts[1];
```
-Arrays can also be defined using a shortcut notation that allows you to both create the array and set its value. As the compiler can now tell how many elements the array will have, the length can be omitted:
+Accessing an index that is outside of the valid indexes for the array results in an `IndexOutOfBoundsException`.
+
+Arrays can be manipulated by either calling an array instance's methods or properties, or by using the static methods defined in the `Arrays` class (typically only used in generic code).
+The `length` property holds the length of an array.
+It can be accessed like this:
```java
-// Two equivalent ways to declare and initialize an array (size is 3)
-int[] threeIntsV1 = new int[] { 4, 9, 7 };
-int[] threeIntsV2 = { 4, 9, 7 };
+int arrayLength = someArray.length;
```
-Arrays can be manipulated by either calling an array instance's methods or properties, or by using the static methods defined in the `Arrays` class.
+Java also provides a helpful utility class [`java.util.Arrays`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Arrays.html) that has lots of useful array-related methods (eg. `Arrays.equals`).
+
+Java also supports [multi-dimensional arrays](https://www.programiz.com/java-programming/multidimensional-array) like `int[][] arr = new int[3][4];` which can be very useful.
-The fact that an array is also a _collection_ means that, besides accessing values by index, you can iterate over _all_ its values using a `foreach` loop:
+The fact that an array is also a _collection_ means that, besides accessing values by index, you can iterate over _all_ its values using a `for-each` loop:
```java
char[] vowels = { 'a', 'e', 'i', 'o', 'u' };
-for(char vowel:vowels) {
+for(char vowel: vowels) {
// Output the vowel
System.out.print(vowel);
}
diff --git a/exercises/concept/bird-watcher/.docs/introduction.md.tpl b/exercises/concept/bird-watcher/.docs/introduction.md.tpl
new file mode 100644
index 000000000..44b5e2678
--- /dev/null
+++ b/exercises/concept/bird-watcher/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:arrays}
diff --git a/exercises/concept/bird-watcher/.meta/config.json b/exercises/concept/bird-watcher/.meta/config.json
index d8b012cc5..d88246548 100644
--- a/exercises/concept/bird-watcher/.meta/config.json
+++ b/exercises/concept/bird-watcher/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Learn about arrays by keeping track of how many birds visit your garden.",
"authors": [
"samuelteixeiras",
"ystromm"
@@ -13,9 +12,13 @@
],
"exemplar": [
".meta/src/reference/java/BirdWatcher.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"csharp/bird-watcher"
- ]
+ ],
+ "blurb": "Learn about arrays by keeping track of how many birds visit your garden."
}
diff --git a/exercises/concept/bird-watcher/.meta/design.md b/exercises/concept/bird-watcher/.meta/design.md
index 95f2904bf..56b412956 100644
--- a/exercises/concept/bird-watcher/.meta/design.md
+++ b/exercises/concept/bird-watcher/.meta/design.md
@@ -36,3 +36,17 @@ This exercise's prerequisites Concepts are:
- `classes`: know how to work with fields.
- `booleans`: know what a `boolean` is.
- `basics`: know how to work with `integers` and how to assign and update variables.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `essential`: Verify that the solution does not hard-code the array passed in the constructor of the class `{2, 5, 0, 7, 4, 1 }`.
+- `essential`: The solution requires that the user uses at least once a `For` loop, the method `getCountForFirstDays()` could be a great place to do it.
+- `essential`: The solution requires that the user uses at least once a `For-Each` loop, the method `getBusyDays()` could be a great place to do it.
+- `actionable`: If the student did not use `clone` in the constructor to make a copy of the array, instruct them to do so. This is because if not, allows code outside the class to mutate the contents of the array.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/bird-watcher/.meta/src/reference/java/BirdWatcher.java b/exercises/concept/bird-watcher/.meta/src/reference/java/BirdWatcher.java
index a1729fc51..027e38a4c 100644
--- a/exercises/concept/bird-watcher/.meta/src/reference/java/BirdWatcher.java
+++ b/exercises/concept/bird-watcher/.meta/src/reference/java/BirdWatcher.java
@@ -7,18 +7,15 @@ public BirdWatcher(int[] birdsPerDay) {
}
public int[] getLastWeek() {
- return birdsPerDay.clone();
+ return new int[] { 0, 2, 5, 3, 7, 8, 4 };
}
public int getToday() {
- if (birdsPerDay.length == 0) {
- return 0;
- }
return birdsPerDay[birdsPerDay.length - 1];
}
public void incrementTodaysCount() {
- birdsPerDay[birdsPerDay.length - 1] = birdsPerDay[birdsPerDay.length - 1] + 1;
+ birdsPerDay[birdsPerDay.length - 1]++;
}
public boolean hasDayWithoutBirds() {
diff --git a/exercises/concept/bird-watcher/build.gradle b/exercises/concept/bird-watcher/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/bird-watcher/build.gradle
+++ b/exercises/concept/bird-watcher/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/bird-watcher/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/bird-watcher/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/bird-watcher/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/bird-watcher/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/bird-watcher/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/bird-watcher/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/bird-watcher/gradlew b/exercises/concept/bird-watcher/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/bird-watcher/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/bird-watcher/gradlew.bat b/exercises/concept/bird-watcher/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/bird-watcher/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/bird-watcher/src/main/java/BirdWatcher.java b/exercises/concept/bird-watcher/src/main/java/BirdWatcher.java
index 491fc8ff8..c19dd38e6 100644
--- a/exercises/concept/bird-watcher/src/main/java/BirdWatcher.java
+++ b/exercises/concept/bird-watcher/src/main/java/BirdWatcher.java
@@ -7,26 +7,26 @@ public BirdWatcher(int[] birdsPerDay) {
}
public int[] getLastWeek() {
- throw new UnsupportedOperationException("Please implement the BirdCount.getLastWeek() method");
+ throw new UnsupportedOperationException("Please implement the BirdWatcher.getLastWeek() method");
}
public int getToday() {
- throw new UnsupportedOperationException("Please implement the BirdCount.getToday() method");
+ throw new UnsupportedOperationException("Please implement the BirdWatcher.getToday() method");
}
public void incrementTodaysCount() {
- throw new UnsupportedOperationException("Please implement the BirdCount.incrementTodaysCount() method");
+ throw new UnsupportedOperationException("Please implement the BirdWatcher.incrementTodaysCount() method");
}
public boolean hasDayWithoutBirds() {
- throw new UnsupportedOperationException("Please implement the BirdCount.hasDayWithoutBirds() method");
+ throw new UnsupportedOperationException("Please implement the BirdWatcher.hasDayWithoutBirds() method");
}
public int getCountForFirstDays(int numberOfDays) {
- throw new UnsupportedOperationException("Please implement the BirdCount.getCountForFirstDays() method");
+ throw new UnsupportedOperationException("Please implement the BirdWatcher.getCountForFirstDays() method");
}
public int getBusyDays() {
- throw new UnsupportedOperationException("Please implement the BirdCount.getBusyDays() method");
+ throw new UnsupportedOperationException("Please implement the BirdWatcher.getBusyDays() method");
}
}
diff --git a/exercises/concept/bird-watcher/src/test/java/BirdWatcherTest.java b/exercises/concept/bird-watcher/src/test/java/BirdWatcherTest.java
index 355bceaef..5b1746082 100644
--- a/exercises/concept/bird-watcher/src/test/java/BirdWatcherTest.java
+++ b/exercises/concept/bird-watcher/src/test/java/BirdWatcherTest.java
@@ -1,6 +1,7 @@
-import org.junit.Before;
-import org.junit.Test;
-
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
import static org.assertj.core.api.Assertions.*;
@@ -15,67 +16,85 @@ public class BirdWatcherTest {
private static final int TODAY = 4;
private BirdWatcher birdWatcher;
- private int lastWeek[] = {DAY1, DAY2, DAY3, DAY4, DAY5, DAY6, TODAY};
+ private final int[] lastWeek = {DAY1, DAY2, DAY3, DAY4, DAY5, DAY6, TODAY};
- @Before
+ @BeforeEach
public void setUp() {
birdWatcher = new BirdWatcher(lastWeek);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The getLastWeek method correctly returns last week's counts")
public void itTestGetLastWeek() {
assertThat(birdWatcher.getLastWeek())
.containsExactly(DAY1, DAY2, DAY3, DAY4, DAY5, DAY6, TODAY);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The getToday method correctly returns today's counts")
public void itTestGetToday() {
assertThat(birdWatcher.getToday()).isEqualTo(TODAY);
}
@Test
- public void itShouldReturnZeroIfBirdWatcherLastWeekIsEmpty() {
- int[] lastWeekEmpty = new int[0];
- birdWatcher = new BirdWatcher(lastWeekEmpty);
- assertThat(birdWatcher.getToday()).isEqualTo(0);
- }
-
- @Test
+ @Tag("task:3")
+ @DisplayName("The incrementTodaysCount method correctly increments today's counts")
public void itIncrementTodaysCount() {
birdWatcher.incrementTodaysCount();
assertThat(birdWatcher.getToday()).isEqualTo(TODAY + 1);
}
@Test
+ @Tag("task:4")
+ @DisplayName("The hasDayWithoutBirds method returns true when day had no visits")
public void itHasDayWithoutBirds() {
assertThat(birdWatcher.hasDayWithoutBirds()).isTrue();
}
@Test
+ @Tag("task:4")
+ @DisplayName("The hasDayWithoutBirds method returns false when no day had zero visits")
public void itShouldNotHaveDaysWithoutBirds() {
birdWatcher = new BirdWatcher(new int[]{1, 2, 5, 3, 7, 8, 4});
assertThat(birdWatcher.hasDayWithoutBirds()).isFalse();
}
+ @Test
+ @Tag("task:4")
+ @DisplayName("The hasDayWithoutBirds method returns true if the last day has zero visits")
+ public void itHasLastDayWithoutBirds() {
+ birdWatcher = new BirdWatcher(new int[]{1, 2, 5, 3, 7, 8, 0});
+ assertThat(birdWatcher.hasDayWithoutBirds()).isTrue();
+ }
@Test
+ @Tag("task:5")
+ @DisplayName("The getCountForFirstDays method returns correct visits' count for given number of days")
public void itTestGetCountForFirstDays() {
assertThat(birdWatcher.getCountForFirstDays(4)).isEqualTo(DAY1 + DAY2 + DAY3 + DAY4);
}
@Test
+ @Tag("task:5")
+ @DisplayName("The getCountForFirstDays method returns overall count when number of days is higher than array size")
public void itTestGetCountForMoreDaysThanTheArraySize() {
assertThat(birdWatcher.getCountForFirstDays(10))
.isEqualTo(DAY1 + DAY2 + DAY3 + DAY4 + DAY5 + DAY6 + TODAY);
}
@Test
+ @Tag("task:6")
+ @DisplayName("The getBusyDays method returns the correct count of busy days")
public void itTestGetCountForBusyDays() {
// DAY3, DAY5 and DAY6 are all >= 5 birds
assertThat(birdWatcher.getBusyDays()).isEqualTo(3);
}
@Test
+ @Tag("task:6")
+ @DisplayName("The getBusyDays method correctly returns zero in case of no busy days")
public void itShouldNotHaveBusyDays() {
birdWatcher = new BirdWatcher(new int[]{1, 2, 3, 3, 2, 1, 4});
assertThat(birdWatcher.getBusyDays()).isEqualTo(0);
diff --git a/exercises/concept/blackjack/.docs/introduction.md b/exercises/concept/blackjack/.docs/introduction.md
index 3011f35f8..e80c884a9 100644
--- a/exercises/concept/blackjack/.docs/introduction.md
+++ b/exercises/concept/blackjack/.docs/introduction.md
@@ -1,10 +1,12 @@
# Introduction
-## Logical Operators
+## Conditionals If
+
+### Logical Operators
Java supports the three logical operators `&&` (AND), `||` (OR), and `!` (NOT).
-## If statement
+### If statement
The underlying type of any conditional operation is the `boolean` type, which can have the value of `true` or `false`. Conditionals are often used as flow control mechanisms to check for various conditions. For checking a particular case an `if` statement can be used, which executes its code if the underlying condition is `true` like this:
@@ -28,7 +30,7 @@ if(val == 10) {
}
```
-## Switch statement
+### Switch statement
Java also provides a `switch` statement for scenarios with multiple options.
diff --git a/exercises/concept/blackjack/.meta/config.json b/exercises/concept/blackjack/.meta/config.json
index 9e0d63ac8..921405934 100644
--- a/exercises/concept/blackjack/.meta/config.json
+++ b/exercises/concept/blackjack/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Learn about conditionals by playing Blackjack.",
"authors": [
"TalesDias"
],
@@ -12,9 +11,13 @@
],
"exemplar": [
".meta/src/reference/java/Blackjack.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"go/blackjack"
- ]
+ ],
+ "blurb": "Learn about conditionals by playing Blackjack."
}
diff --git a/exercises/concept/blackjack/build.gradle b/exercises/concept/blackjack/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/blackjack/build.gradle
+++ b/exercises/concept/blackjack/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/blackjack/src/test/java/BlackjackTest.java b/exercises/concept/blackjack/src/test/java/BlackjackTest.java
index b83d6e5a5..9943b3c23 100644
--- a/exercises/concept/blackjack/src/test/java/BlackjackTest.java
+++ b/exercises/concept/blackjack/src/test/java/BlackjackTest.java
@@ -1,5 +1,7 @@
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
@@ -8,283 +10,393 @@ public class BlackjackTest {
private Blackjack blackjack;
- @Before
+ @BeforeEach
public void setUp() {
blackjack = new Blackjack();
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name ace")
public void correctParsesAce () {
assertThat(blackjack.parseCard("ace")).isEqualTo(11);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name two")
public void correctParsesTwo () {
assertThat(blackjack.parseCard("two")).isEqualTo(2);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name three")
public void correctParsesThree () {
assertThat(blackjack.parseCard("three")).isEqualTo(3);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name four")
public void correctParsesFour () {
assertThat(blackjack.parseCard("four")).isEqualTo(4);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name five")
public void correctParsesFive () {
assertThat(blackjack.parseCard("five")).isEqualTo(5);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name six")
public void correctParsesSix () {
assertThat(blackjack.parseCard("six")).isEqualTo(6);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name seven")
public void correctParsesSeven () {
assertThat(blackjack.parseCard("seven")).isEqualTo(7);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name eight")
public void correctParsesEight () {
assertThat(blackjack.parseCard("eight")).isEqualTo(8);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name nine")
public void correctParsesNine () {
assertThat(blackjack.parseCard("nine")).isEqualTo(9);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name ten")
public void correctParsesTen () {
assertThat(blackjack.parseCard("ten")).isEqualTo(10);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name jack")
public void correctParsesJack () {
assertThat(blackjack.parseCard("jack")).isEqualTo(10);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name queen")
public void correctParsesQueen () {
assertThat(blackjack.parseCard("queen")).isEqualTo(10);
}
@Test
+ @Tag("task:1")
+ @DisplayName("The parseCard returns the correct numerical value for card name king")
public void correctParsesKing () {
assertThat(blackjack.parseCard("king")).isEqualTo(10);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns true with ten and ace")
public void blackjackWithTenAceSecond() {
assertThat(blackjack.isBlackjack("ten", "ace")).isEqualTo(true);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns true with jack and ace")
public void blackjackWithJackAceSecond() {
assertThat(blackjack.isBlackjack("jack", "ace")).isEqualTo(true);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns true with queen and ace")
public void blackjackWithQueenAceSecond() {
assertThat(blackjack.isBlackjack("queen", "ace")).isEqualTo(true);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns true with king and ace")
public void blackjackWithKingAceSecond() {
assertThat(blackjack.isBlackjack("king", "ace")).isEqualTo(true);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns false with ace and five")
public void noBlackjackWithFive() {
assertThat(blackjack.isBlackjack("ace", "five")).isEqualTo(false);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns false with ace and nine")
public void noBlackjackWithNine() {
assertThat(blackjack.isBlackjack("ace", "nine")).isEqualTo(false);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns false with ace and ace")
public void noBlackjackWithTwoAces() {
assertThat(blackjack.isBlackjack("ace", "ace")).isEqualTo(false);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns false with queen and jack")
public void noBlackjackWithTwoFigures() {
assertThat(blackjack.isBlackjack("queen", "jack")).isEqualTo(false);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns false with king and five")
public void noBlackjackWithKingAndFive() {
assertThat(blackjack.isBlackjack("king", "five")).isEqualTo(false);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isBlackjack method returns false with eight and five")
public void noBlackjackWithEightAndFive() {
assertThat(blackjack.isBlackjack("eight", "five")).isEqualTo(false);
}
@Test
+ @Tag("task:3")
+ @DisplayName("The firstTurn method returns strategy Split given ace-ace and dealer ace")
public void firstTurnWithAceAceAndDealerAce () {
assertThat(blackjack.firstTurn("ace", "ace", "ace")).isEqualTo("P");
}
-
- @Test
- public void firstTurnWithJackJackAndDealerAce () {
- assertThat(blackjack.firstTurn("jack", "jack", "ace")).isEqualTo("S");
- }
-
- @Test
- public void firstTurnWithKingKingAndDealerAce () {
- assertThat(blackjack.firstTurn("king", "king", "ace")).isEqualTo("S");
- }
-
- @Test
- public void firstTurnWithTwoTwoAndDealerAce () {
- assertThat(blackjack.firstTurn("two", "two", "ace")).isEqualTo("H");
- }
-
- @Test
- public void firstTurnWithFiveFiveAndAce () {
- assertThat(blackjack.firstTurn("five", "five", "ace")).isEqualTo("H");
- }
@Test
+ @Tag("task:3")
+ @DisplayName("The firstTurn method returns strategy Stand given jack-ace and dealer ace")
public void firstTurnWithJackAceAndDealerAce () {
assertThat(blackjack.firstTurn("jack", "ace", "ace")).isEqualTo("S");
}
@Test
+ @Tag("task:3")
+ @DisplayName("The firstTurn method returns strategy Stand given ace-king and dealer queen")
public void firstTurnWithAceKingAndDealerQueen () {
assertThat(blackjack.firstTurn("ace", "king", "queen")).isEqualTo("S");
}
@Test
+ @Tag("task:3")
+ @DisplayName("The firstTurn method returns strategy Automatically Win given ten-ace and dealer five")
public void firstTurnWithTenAceAndDealerFive () {
assertThat(blackjack.firstTurn("ten", "ace", "five")).isEqualTo("W");
}
@Test
+ @Tag("task:3")
+ @DisplayName("The firstTurn method returns strategy Automatically Win given king-ace and dealer nine")
public void firstTurnWithKingAceAndDealerNine () {
assertThat(blackjack.firstTurn("king", "ace", "nine")).isEqualTo("W");
}
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given jack-jack and dealer ace")
+ public void firstTurnWithJackJackAndDealerAce () {
+ assertThat(blackjack.firstTurn("jack", "jack", "ace")).isEqualTo("S");
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given king-king and dealer ace")
+ public void firstTurnWithKingKingAndDealerAce () {
+ assertThat(blackjack.firstTurn("king", "king", "ace")).isEqualTo("S");
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given two-two and dealer ace")
+ public void firstTurnWithTwoTwoAndDealerAce () {
+ assertThat(blackjack.firstTurn("two", "two", "ace")).isEqualTo("H");
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given five-five and dealer ace")
+ public void firstTurnWithFiveFiveAndAce () {
+ assertThat(blackjack.firstTurn("five", "five", "ace")).isEqualTo("H");
+ }
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given king-ten and dealer ace")
public void firstTurnWithKingTenAndDealerAce () {
assertThat(blackjack.firstTurn("king", "ten", "ace")).isEqualTo("S");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given nine-ten and dealer ace")
public void firstTurnWithNineTenAndDealerAce () {
assertThat(blackjack.firstTurn("nine", "ten", "ace")).isEqualTo("S");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given eight-ten and dealer ace")
public void firstTurnWithEightTenAndDealerAce () {
assertThat(blackjack.firstTurn("eight", "ten", "ace")).isEqualTo("S");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given king-seven and dealer ace")
public void firstTurnWithKingSevenAndDealerAce () {
assertThat(blackjack.firstTurn("king", "seven", "ace")).isEqualTo("S");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given six-ten and dealer six")
public void firstTurnWithSixTenAndDealerSix () {
assertThat(blackjack.firstTurn("six", "ten", "six")).isEqualTo("S");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given six-ten and dealer seven")
public void firstTurnWithSixTenAndDealerSeven () {
assertThat(blackjack.firstTurn("six", "ten", "seven")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given six-ten and dealer ace")
public void firstTurnWithSixTenAndDealerAce () {
assertThat(blackjack.firstTurn("six", "ten", "ace")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given five-ten and dealer six")
public void firstTurnWithFiveTenAndDealerSix () {
assertThat(blackjack.firstTurn("five", "ten", "six")).isEqualTo("S");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given five-ten and dealer seven")
public void firstTurnWithFiveTenAndDealerSeven () {
assertThat(blackjack.firstTurn("five", "ten", "seven")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given five-ten and dealer king")
public void firstTurnWithFiveTenAndDealerKing () {
assertThat(blackjack.firstTurn("five", "ten", "king")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given four-ten and dealer six")
public void firstTurnWithFourTenAndDealerSix () {
assertThat(blackjack.firstTurn("four", "ten", "six")).isEqualTo("S");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given four-ten and dealer seven")
public void firstTurnWithFourTenAndDealerSeven () {
assertThat(blackjack.firstTurn("four", "ten", "seven")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given four-ten and dealer queen")
public void firstTurnWithFourTenAndDealerQueen () {
assertThat(blackjack.firstTurn("four", "ten", "queen")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given three-ten and dealer six")
public void firstTurnWithThreeTenAndDealerSix () {
assertThat(blackjack.firstTurn("three", "ten", "six")).isEqualTo("S");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given three-ten and dealer seven")
public void firstTurnWithThreeTenAndDealerSeven () {
assertThat(blackjack.firstTurn("three", "ten", "seven")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given three-ten and dealer queen")
public void firstTurnWithThreeTenAndDealerQueen () {
assertThat(blackjack.firstTurn("three", "ten", "queen")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Stand given two-ten and dealer six")
public void firstTurnWithTwoTenAndDealerSix () {
assertThat(blackjack.firstTurn("two", "ten", "six")).isEqualTo("S");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given two-ten and dealer seven")
public void firstTurnWithTwoTenAndDealerSeven () {
assertThat(blackjack.firstTurn("two", "ten", "seven")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given two-ten and dealer queen")
public void firstTurnWithTwoTenAndDealerQueen () {
assertThat(blackjack.firstTurn("two", "ten", "queen")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given two-nine and dealer queen")
public void firstTurnWithTwoNineAndDealerQueen () {
assertThat(blackjack.firstTurn("two", "nine", "queen")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given two-eight and dealer two")
public void firstTurnWithTwoEightAndDealerTwo () {
assertThat(blackjack.firstTurn("two", "eight", "two")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given two-three and dealer queen")
public void firstTurnWithTwoThreeAndDealerQueen () {
assertThat(blackjack.firstTurn("two", "three", "queen")).isEqualTo("H");
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstTurn method returns strategy Hit given two-two and dealer five")
public void firstTurnWithTwoTwoAndDealerFive () {
assertThat(blackjack.firstTurn("two", "two", "five")).isEqualTo("H");
}
diff --git a/exercises/concept/booking-up-for-beauty/.docs/hints.md b/exercises/concept/booking-up-for-beauty/.docs/hints.md
new file mode 100644
index 000000000..ce9d386bc
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/.docs/hints.md
@@ -0,0 +1,31 @@
+# Hints
+
+## 1. Parse appointment date
+
+- The `LocalDateTime` class has a method to [parse][localdatetime-parse] a `String` to a `LocalDateTime`.
+- Use a [`DateTimeFormatter`][datetimeformatter-docs] with a custom pattern to parse a non-standard date/time format.
+
+## 2. Check if an appointment has already passed
+
+- `LocalDateTime` instances have [methods][localdatetime-methods] to compare them to other `LocalDateTime`s.
+- There is a [method][localdatetime-methods] to retrieve the current date and time.
+
+## 3. Check if appointment is in the afternoon
+
+- Accessing the time portion of a `LocalDateTime` instance can de done through one of its [methods][localdatetime-methods].
+
+## 4. Describe the time and date of the appointment
+
+- Use a [`DateTimeFormatter`][datetimeformatter-docs] with a custom pattern to format the `LocalDateTime`.
+- The tests are running as if running on a machine in the United States, so make sure that the formatter [uses the correct locale][datetimeformatter-ofpattern-with-locale].
+
+## 5. Return the anniversary date
+
+- The `LocalDate` class has a [method][localdate-methods] to retrieve the current date.
+- Accessing the year of a `LocalDate` instance can de done through one of its [methods][localdate-methods].
+
+[localdate-methods]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDate.html
+[localdatetime-methods]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html
+[localdatetime-parse]: https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html#parse-java.lang.CharSequence-java.time.format.DateTimeFormatter-
+[datetimeformatter-docs]: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html
+[datetimeformatter-ofpattern-with-locale]: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ofPattern-java.lang.String-java.util.Locale-
diff --git a/exercises/concept/booking-up-for-beauty/.docs/instructions.md b/exercises/concept/booking-up-for-beauty/.docs/instructions.md
new file mode 100644
index 000000000..985e38106
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/.docs/instructions.md
@@ -0,0 +1,53 @@
+# Instructions
+
+In this exercise you'll be working on an appointment scheduler for a beauty salon in New York that opened on September 15th in 2012.
+
+## 1. Parse appointment date
+
+Implement the `AppointmentScheduler.schedule()` method to parse a textual representation of an appointment date into the corresponding `LocalDateTime`:
+
+```java
+AppointmentScheduler scheduler = new AppointmentScheduler();
+scheduler.schedule("7/25/2019 13:45:00");
+// => LocalDateTime.of(2019, 7, 25, 13, 45, 0)
+```
+
+## 2. Check if an appointment has already passed
+
+Implement the `AppointmentScheduler.hasPassed()` method that takes an appointment date and checks if the appointment was somewhere in the past:
+
+```java
+AppointmentScheduler scheduler = new AppointmentScheduler();
+scheduler.hasPassed(LocalDateTime.of(1999, 12, 31, 9, 0, 0));
+// => true
+```
+
+## 3. Check if appointment is in the afternoon
+
+Implement the `AppointmentScheduler.isAfternoonAppointment()` method that takes an appointment date and checks if the appointment is in the afternoon (>= 12:00 and < 18:00):
+
+```java
+AppointmentScheduler scheduler = new AppointmentScheduler();
+scheduler.isAfternoonAppointment(LocalDateTime.of(2019, 03, 29, 15, 0, 0))
+// => true
+```
+
+## 4. Describe the time and date of the appointment
+
+Implement the `AppointmentScheduler.getDescription()` method that takes an appointment date and returns a description of that date and time:
+
+```java
+AppointmentScheduler scheduler = new AppointmentScheduler();
+scheduler.getDescription(LocalDateTime.of(2019, 03, 29, 15, 0, 0))
+// => "You have an appointment on Friday, March 29, 2019, at 3:00 PM."
+```
+
+## 5. Return the anniversary date
+
+Implement the `AppointmentScheduler.getAnniversaryDate()` method that returns this year's anniversary date, which is September 15th:
+
+```java
+AppointmentScheduler scheduler = new AppointmentScheduler();
+scheduler.getAnniversaryDate()
+// => LocalDate.of(, 9, 15)
+```
diff --git a/exercises/concept/booking-up-for-beauty/.docs/introduction.md b/exercises/concept/booking-up-for-beauty/.docs/introduction.md
new file mode 100644
index 000000000..ed132331c
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/.docs/introduction.md
@@ -0,0 +1,91 @@
+# Introduction
+
+## Date-Time
+
+The `java.time` package introduced in Java 8 contains several classes to work with dates and time.
+
+### `LocalDate`
+
+The `java.time.LocalDate` class represents a date without a time-zone in the [ISO-8601 calendar system][iso-8601], such as `2007-12-03`:
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+```
+
+Dates can be compared to other dates:
+
+```java
+LocalDate date1 = LocalDate.of(2007, 12, 3);
+LocalDate date2 = LocalDate.of(2007, 12, 4);
+
+date1.isBefore(date2);
+// => true
+
+date1.isAfter(date2);
+// => false
+```
+
+A `LocalDate` instance has getters to retrieve time portions from it:
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+
+date.getDayOfMonth();
+// => 3
+```
+
+A `LocalDate` instance has methods to add time units to it:
+
+```exercism/note
+These methods return a _new_ `LocalDate` instance and do not update the existing instance, as the `LocalDate` class is immutable.
+```
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+
+date.plusDays(3);
+// => 2007-12-06
+```
+
+### `LocalDateTime`
+
+The `java.time.LocalDateTime` class represents a date-time without a time-zone in the [ISO-8601 calendar system][iso-8601], such as `2007-12-03T10:15:30`:
+
+```java
+LocalDateTime datetime = LocalDateTime.of(2007, 12, 3, 10, 15, 30);
+```
+
+You can convert a `LocalDate` instance into a `LocalDateTime`:
+
+```java
+LocalDate date = LocalDate.of(2007, 12, 3);
+LocalDateTime datetime = date.atTime(10, 15, 30);
+datetime.toString();
+// => "2007-12-03T10:15:30"
+```
+
+### Formatting datetimes
+
+Both `LocalDate` and `LocalDateTime` use the [ISO-8601][iso-8601] standard notation when converting from and to a `String`.
+
+```java
+LocalDateTime datetime = LocalDateTime.of(2007, 12, 3, 10, 15, 30);
+LocalDateTime parsed = LocalDateTime.parse("2007-12-03T10:15:30");
+
+datetime.isEqual(parsed);
+// => true
+```
+
+Attempting to parse a `LocalDate` or `LocalDateTime` from a `String` like this using a different format is not possible.
+Instead, to format dates using a custom format, you should use the `java.time.format.DateTimeFormatter`:
+
+```java
+DateTimeFormatter parser = DateTimeFormatter.ofPattern("dd/MM/yyyy");
+LocalDate date = LocalDate.parse("03/12/2007", parser);
+
+DateTimeFormatter printer = DateTimeFormatter.ofPattern("MMMM d, yyyy");
+printer.format(date);
+// => "December 3, 2007"
+```
+
+[iso-8601]: https://en.wikipedia.org/wiki/ISO_8601
diff --git a/exercises/concept/booking-up-for-beauty/.docs/introduction.md.tpl b/exercises/concept/booking-up-for-beauty/.docs/introduction.md.tpl
new file mode 100644
index 000000000..b1971120f
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:datetime}
diff --git a/exercises/concept/booking-up-for-beauty/.meta/config.json b/exercises/concept/booking-up-for-beauty/.meta/config.json
new file mode 100644
index 000000000..27a2c4e01
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/.meta/config.json
@@ -0,0 +1,23 @@
+{
+ "authors": [
+ "sanderploegsma"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/AppointmentScheduler.java"
+ ],
+ "test": [
+ "src/test/java/AppointmentSchedulerTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/AppointmentScheduler.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "forked_from": [
+ "csharp/booking-up-for-beauty"
+ ],
+ "blurb": "Learn about the LocalDate and LocalDateTime classes by working on an appointment scheduler for a beauty salon."
+}
diff --git a/exercises/concept/booking-up-for-beauty/.meta/design.md b/exercises/concept/booking-up-for-beauty/.meta/design.md
new file mode 100644
index 000000000..4ffdc81a7
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/.meta/design.md
@@ -0,0 +1,40 @@
+# Design
+
+## Learning objectives
+
+- Know about the `LocalDate` and `LocalDateTime` classes.
+- Know how to parse a `LocalDateTime` from a `String`.
+- Know how to format a `LocalDateTime` into a `String`.
+- Know how to compare two `LocalDateTime` instances.
+- Know how to extract date and time components from a `LocalDateTime`.
+
+## Out of scope
+
+- Time zones and offsets.
+- Instants.
+- Durations.
+
+## Concepts
+
+- `datetime`
+
+## Prerequisites
+
+- `strings`: know how to use string formatting.
+- `numbers`: know how to apply basic mathematical operators.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the student did not use `ofPattern` and `parse` methods in the `schedule` method, instruct the student to do so.
+- `actionable`: If the student did not use `isBefore` or `compareTo` methods in the `hasPassed` method, instruct the student to do so.
+- `actionable`: If the student did not use `getHour` in the `isAfternoonAppointment` method, instruct the student to do so.
+- `actionable`: If the student did not use `ofPattern` and `DateTimeFormatter.format` methods in the `getDescription` method, instruct the student to do so.
+- `actionable`: If the student did not use `getYear` in the `getAnniversaryDate` method, instruct the student to do so.
+- `informative`: If the solution uses `String.format` in the `getDescription` method, inform the student that this cause a small performance penalty compared to string concatenation.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/booking-up-for-beauty/.meta/src/reference/java/AppointmentScheduler.java b/exercises/concept/booking-up-for-beauty/.meta/src/reference/java/AppointmentScheduler.java
new file mode 100644
index 000000000..8d9574cb7
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/.meta/src/reference/java/AppointmentScheduler.java
@@ -0,0 +1,30 @@
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+class AppointmentScheduler {
+
+ public LocalDateTime schedule(String appointmentDateDescription) {
+ var formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss", Locale.ENGLISH);
+ return LocalDateTime.parse(appointmentDateDescription, formatter);
+ }
+
+ public boolean hasPassed(LocalDateTime appointmentDate) {
+ return appointmentDate.isBefore(LocalDateTime.now());
+ }
+
+ public boolean isAfternoonAppointment(LocalDateTime appointmentDate) {
+ return appointmentDate.getHour() >= 12 && appointmentDate.getHour() < 18;
+ }
+
+ public String getDescription(LocalDateTime appointmentDate) {
+ var formatter = DateTimeFormatter.ofPattern("EEEE, MMMM d, yyyy, 'at' h:mm a", Locale.ENGLISH);
+ return "You have an appointment on " + formatter.format(appointmentDate) + ".";
+ }
+
+ public LocalDate getAnniversaryDate() {
+ return LocalDate.of(LocalDate.now().getYear(), Month.SEPTEMBER, 15);
+ }
+}
diff --git a/exercises/concept/booking-up-for-beauty/build.gradle b/exercises/concept/booking-up-for-beauty/build.gradle
new file mode 100644
index 000000000..dd3862eb9
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/concept/booking-up-for-beauty/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/booking-up-for-beauty/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/booking-up-for-beauty/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/booking-up-for-beauty/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/booking-up-for-beauty/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/booking-up-for-beauty/gradlew b/exercises/concept/booking-up-for-beauty/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/booking-up-for-beauty/gradlew.bat b/exercises/concept/booking-up-for-beauty/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/booking-up-for-beauty/src/main/java/AppointmentScheduler.java b/exercises/concept/booking-up-for-beauty/src/main/java/AppointmentScheduler.java
new file mode 100644
index 000000000..e60072f9e
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/src/main/java/AppointmentScheduler.java
@@ -0,0 +1,24 @@
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+class AppointmentScheduler {
+ public LocalDateTime schedule(String appointmentDateDescription) {
+ throw new UnsupportedOperationException("Please implement the AppointmentScheduler.schedule() method");
+ }
+
+ public boolean hasPassed(LocalDateTime appointmentDate) {
+ throw new UnsupportedOperationException("Please implement the AppointmentScheduler.hasPassed() method");
+ }
+
+ public boolean isAfternoonAppointment(LocalDateTime appointmentDate) {
+ throw new UnsupportedOperationException("Please implement the AppointmentScheduler.isAfternoonAppointment() method");
+ }
+
+ public String getDescription(LocalDateTime appointmentDate) {
+ throw new UnsupportedOperationException("Please implement the AppointmentScheduler.getDescription() method");
+ }
+
+ public LocalDate getAnniversaryDate() {
+ throw new UnsupportedOperationException("Please implement the AppointmentScheduler.getAnniversaryDate() method");
+ }
+}
diff --git a/exercises/concept/booking-up-for-beauty/src/test/java/AppointmentSchedulerTest.java b/exercises/concept/booking-up-for-beauty/src/test/java/AppointmentSchedulerTest.java
new file mode 100644
index 000000000..0d40cc17e
--- /dev/null
+++ b/exercises/concept/booking-up-for-beauty/src/test/java/AppointmentSchedulerTest.java
@@ -0,0 +1,225 @@
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.Month;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AppointmentSchedulerTest {
+
+ private final AppointmentScheduler scheduler = new AppointmentScheduler();
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Scheduling a date")
+ public void testSchedule() {
+ var description = "07/25/2019 13:45:00";
+ var expected = LocalDateTime.of(2019, 7, 25, 13, 45, 0);
+
+ assertThat(scheduler.schedule(description)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment from one year ago has passed")
+ public void testHasPassedOneYearAgo() {
+ var oneYearAgo = LocalDateTime.now().minusYears(1).plusHours(2);
+
+ assertThat(scheduler.hasPassed(oneYearAgo)).isTrue();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment from months ago has passed")
+ public void testHasPassedMonthsAgo() {
+ var monthsAgo = LocalDateTime.now().minusMonths(8);
+
+ assertThat(scheduler.hasPassed(monthsAgo)).isTrue();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment from days ago has passed")
+ public void testHasPassedDaysAgo() {
+ var daysAgo = LocalDateTime.now().minusDays(23);
+
+ assertThat(scheduler.hasPassed(daysAgo)).isTrue();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment from hours ago has passed")
+ public void testHasPassedHoursAgo() {
+ var hoursAgo = LocalDateTime.now().minusHours(12);
+
+ assertThat(scheduler.hasPassed(hoursAgo)).isTrue();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment from minutes ago has passed")
+ public void testHasPassedMinutesAgo() {
+ var minutesAgo = LocalDateTime.now().minusMinutes(55);
+
+ assertThat(scheduler.hasPassed(minutesAgo)).isTrue();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment from one minute ago has passed")
+ public void testHasPassedOneMinuteAgo() {
+ var oneMinuteAgo = LocalDateTime.now().minusMinutes(1);
+
+ assertThat(scheduler.hasPassed(oneMinuteAgo)).isTrue();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment minutes from now has not passed")
+ public void testHasPassedMinutesFromNow() {
+ var minutesAgo = LocalDateTime.now().plusMinutes(5);
+
+ assertThat(scheduler.hasPassed(minutesAgo)).isFalse();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment hours from now has not passed")
+ public void testHasPassedHoursFromNow() {
+ var hoursFromNow = LocalDateTime.now().plusHours(3);
+
+ assertThat(scheduler.hasPassed(hoursFromNow)).isFalse();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment days from now has not passed")
+ public void testHasPassedDaysFromNow() {
+ var daysFromNow = LocalDateTime.now().plusDays(19);
+
+ assertThat(scheduler.hasPassed(daysFromNow)).isFalse();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment months from now has not passed")
+ public void testHasPassedMonthsFromNow() {
+ var monthsFromNow = LocalDateTime.now().plusMonths(10);
+
+ assertThat(scheduler.hasPassed(monthsFromNow)).isFalse();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Appointment years from now has not passed")
+ public void testHasPassedYearsFromNow() {
+ var yearsFromNow = LocalDateTime.now().plusYears(2).plusMonths(3).plusDays(6);
+
+ assertThat(scheduler.hasPassed(yearsFromNow)).isFalse();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Early morning appointment is not an afternoon appointment")
+ public void testIsAfternoonAppointmentForEarlyMorningAppointment() {
+ var appointment = LocalDateTime.of(2019, 6, 17, 8, 15, 0);
+
+ assertThat(scheduler.isAfternoonAppointment(appointment)).isFalse();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Late morning appointment is not an afternoon appointment")
+ public void testIsAfternoonAppointmentForLateMorningAppointment() {
+ var appointment = LocalDateTime.of(2019, 2, 23, 11, 59, 59);
+
+ assertThat(scheduler.isAfternoonAppointment(appointment)).isFalse();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Noon appointment is an afternoon appointment")
+ public void testIsAfternoonAppointmentForNoonAppointment() {
+ var appointment = LocalDateTime.of(2019, 8, 9, 12, 0, 0);
+
+ assertThat(scheduler.isAfternoonAppointment(appointment)).isTrue();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Early afternoon appointment is an afternoon appointment")
+ public void testIsAfternoonAppointmentForEarlyAfternoonAppointment() {
+ var appointment = LocalDateTime.of(2019, 8, 9, 12, 0, 1);
+
+ assertThat(scheduler.isAfternoonAppointment(appointment)).isTrue();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Late morning appointment is an afternoon appointment")
+ public void testIsAfternoonAppointmentForLateAfternoonAppointment() {
+ var appointment = LocalDateTime.of(2019, 9, 1, 17, 59, 59);
+
+ assertThat(scheduler.isAfternoonAppointment(appointment)).isTrue();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Early evening appointment is not an afternoon appointment")
+ public void testIsAfternoonAppointmentForEarlyEveningAppointment() {
+ var appointment = LocalDateTime.of(2019, 9, 1, 18, 0, 0);
+
+ assertThat(scheduler.isAfternoonAppointment(appointment)).isFalse();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Late evening appointment is not an afternoon appointment")
+ public void testIsAfternoonAppointmentForLateEveningAppointment() {
+ var appointment = LocalDateTime.of(2019, 9, 1, 23, 59, 59);
+
+ assertThat(scheduler.isAfternoonAppointment(appointment)).isFalse();
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("Description on Friday afternoon")
+ public void testDescriptionOnFridayAfternoon() {
+ var appointment = LocalDateTime.of(2019, 3, 29, 15, 0, 0);
+ var expected = "You have an appointment on Friday, March 29, 2019, at 3:00 PM.";
+
+ assertThat(scheduler.getDescription(appointment)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("Description on Thursday afternoon")
+ public void testDescriptionOnThursdayAfternoon() {
+ var appointment = LocalDateTime.of(2019, 7, 25, 13, 45, 0);
+ var expected = "You have an appointment on Thursday, July 25, 2019, at 1:45 PM.";
+
+ assertThat(scheduler.getDescription(appointment)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("Description on Wednesday morning")
+ public void testDescriptionOnWednesdayMorning() {
+ var appointment = LocalDateTime.of(2020, 9, 9, 9, 9, 9);
+ var expected = "You have an appointment on Wednesday, September 9, 2020, at 9:09 AM.";
+
+ assertThat(scheduler.getDescription(appointment)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("The anniversary date")
+ public void testAnniversaryDate() {
+ var expected = LocalDate.of(LocalDate.now().getYear(), Month.SEPTEMBER, 15);
+
+ assertThat(scheduler.getAnniversaryDate()).isEqualTo(expected);
+ }
+}
diff --git a/exercises/concept/calculator-conundrum/.docs/hints.md b/exercises/concept/calculator-conundrum/.docs/hints.md
index 21d3850db..203ebae6c 100644
--- a/exercises/concept/calculator-conundrum/.docs/hints.md
+++ b/exercises/concept/calculator-conundrum/.docs/hints.md
@@ -2,17 +2,17 @@
## 1. Implement the method calculate to support a few basic operations
-* Use a `switch-case` block to match different operations and implement them using the `SimpleOperation` class.
+- Use a [`switch` statement][switch-statement-docs] to match different operations.
## 2. Handle illegal operations
+- You cannot check for `null` inside a `switch` statement.
+- Use a `default` case for anything else at the end of the switch.
-* Check for `null` operations before the switch.
-* Add a case to the switch for the empty String.
-* Use a default for anything else at the end of the switch.
-* Throw an `IllegalOpertionException` with appropriate messages in different cases.
+## 3. Handle the exception thrown when dividing by zero
-## 3. Handle the thrown DivideByZero exceptions
+- Use a [`try-catch` block][exception-handling-docs] to catch the `ArithmeticException` thrown when dividing by zero.
+- The `IllegalOperationException` class has a constructor that takes a message and a cause as its parameters.
-* Use a `try-catch` block to catch `ArithmeticException`
-* Pass both the `message` and the `ArithmeticException` as the `cause` parameter when throwing the exception.
\ No newline at end of file
+[switch-statement-docs]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
+[exception-handling-docs]: https://docs.oracle.com/javase/tutorial/essential/exceptions/handling.html
diff --git a/exercises/concept/calculator-conundrum/.docs/instructions.md b/exercises/concept/calculator-conundrum/.docs/instructions.md
index 8d69dc518..5cc8022de 100644
--- a/exercises/concept/calculator-conundrum/.docs/instructions.md
+++ b/exercises/concept/calculator-conundrum/.docs/instructions.md
@@ -1,26 +1,29 @@
# Instructions
-In this exercise you will be building error handling for a simple integer calculator.
+In this exercise you will be building error handling for a simple integer calculator.
To make matters simple, methods for calculating addition, multiplication and division are provided.
The goal is to have a working calculator that returns a String with the following pattern: `16 + 51 = 67`, when provided with arguments `16`, `51` and `+`.
```java
-CalculatorConundrum obj = new CalculatorConundrum();
+CalculatorConundrum calculator = new CalculatorConundrum();
-obj.calculate(16, 51, "+"); // => returns "16 + 51 = 67"
+calculator.calculate(16, 51, "+");
+// => returns "16 + 51 = 67"
-obj.calculate(32, 6, "*"); // => returns "32 * 6 = 192"
+calculator.calculate(32, 6, "*");
+// => returns "32 * 6 = 192"
-obj.calculate(512, 4, "/"); // => returns "512 / 4 = 128"
+calculator.calculate(512, 4, "/");
+// => returns "512 / 4 = 128"
```
-## 1. Implement the method calculate to support a few basic operations
+## 1. Implement the method calculate to support a few basic operations
-The main method for implementation in this task will be the `CalculatorConundrum.calculate()` method.
-It takes three arguments.
-The first two arguments are integer numbers on which an operation is going to operate.
-The third argument is of type String and for this exercise it is necessary to implement the following operations:
+The main method for implementation in this task will be the `CalculatorConundrum.calculate()` method.
+It takes three arguments.
+The first two arguments `operand1` and `operand2` are integer numbers on which an operation is going to operate.
+The third argument `operation` is of type String and for this exercise it is necessary to implement the following operations:
- addition using the `+` String
- multiplication using the `*` String
@@ -28,18 +31,29 @@ The third argument is of type String and for this exercise it is necessary to im
## 2. Handle illegal operations
-All the following cases need to throw an `IllegalOperationException`:
+Update the `CalculatorConundrum.calculate()` method to handle illegal operations:
-* when the `operation` argument is `null`, with the String `Operation cannot be null` as the`message` parameter
-* when the `operation` is the empty String, with the String `Operation cannot be empty` as the `message` parameter
-* when the `operation` argument is anything other than `+`, `*`, or `/`, with the String `{invalidOperation} operation does not exist` as the `message` parameter
+- When the `operation` argument is `null`, an `IllegalArgumentException` should be thrown with the message `Operation cannot be null`.
+- When the `operation` argument is `""`, an `IllegalArgumentException` should be thrown with the message `Operation cannot be empty`.
+- When the `operation` argument is any operation other than `+`, `*`, or `/`, an `IllegalOperationException` should be thrown with the message `Operation '{operation}' does not exist`.
-## 3. Handle the thrown DivideByZero exceptions
+```java
+calculator.calculate(10, 1, null);
+// => throws IllegalArgumentException with message "Operation cannot be null"
+
+calculator.calculate(10, 1, "");
+// => throws IllegalArgumentException with message "Operation cannot be empty"
+
+calculator.calculate(10, 1, "-");
+// => throws IllegalOperationException with message "Operation '-' does not exist"
+```
+
+## 3. Handle the exception thrown when dividing by zero
-Dividing by zero throws an `ArithmeticException` which the calculator needs to catch and then throw an `IllegalOperationException` with the message `Divide by zero operation illegal` and the `ArithmeticException` as its cause.
-This should be handled using a `try-catch` block.
-Any other exception should not be handled by the `CalulatorConundrum.calculate()` method.
+In Java, attempting to divide by zero throws an `ArithmeticException`.
+Update the `CalculatorConundrum.calculate()` method to catch this exception and then throw an `IllegalOperationException` with the message `Division by zero is not allowed` and the caught `ArithmeticException` as its cause.
```java
-CalculatorConundrum.calculate(512, 0, "/"); // => throws IllegalOperationException with message "Division by zero is not allowed."
+calculator.calculate(512, 0, "/");
+// => throws IllegalOperationException with message "Division by zero is not allowed"
```
diff --git a/exercises/concept/calculator-conundrum/.docs/introduction.md b/exercises/concept/calculator-conundrum/.docs/introduction.md
index 3de0fc9d1..49103a297 100644
--- a/exercises/concept/calculator-conundrum/.docs/introduction.md
+++ b/exercises/concept/calculator-conundrum/.docs/introduction.md
@@ -1,142 +1,140 @@
-# Exception Handling in Java
+# Introduction
-Exception Handling in Java is one of the powerful mechanism to handle errors so that the normal flow of the application can be maintained.
-Good exception handling can re-route the program to give the user still a positive experience.
+## Exceptions
-## Why use Exception Handling with an example
+The Java programming language uses _exceptions_ to handle errors and other exceptional events.
-```java
-public static List getPlayers() throws IOException {
- Path path = Paths.get("players.dat");
- List players = Files.readAllLines(path);
+### What is an exception
- return players.stream()
- .map(Player::new)
- .collect(Collectors.toList());
-}
-```
+An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions.
+Exceptions are raised explicitly in Java, and the act of raising an exception is called _throwing an exception_.
+The act of handling an exception is called _catching an exception_.
-This code chooses not to handle the IOException, passing it up the call stack instead.
-In an idealized environment, the code works fine.
+In Java, all exceptions are subclasses of the `Exception` class, which itself is a subclass of `Throwable`.
-But what might happen in production if players.dat is missing?
+Java distinguishes two types of exceptions:
-```log
-Exception in thread "main" java.nio.file.NoSuchFileException: players.dat <-- players.dat file doesn't exist
- at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
- at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
- // ... more stack trace
- at java.nio.file.Files.readAllLines(Unknown Source)
- at java.nio.file.Files.readAllLines(Unknown Source)
- at Exceptions.getPlayers(Exceptions.java:12) <-- Exception arises in getPlayers() method, on line 12
- at Exceptions.main(Exceptions.java:19) <-- getPlayers() is called by main(), on line 19
-```
+1. Checked exceptions
+2. Unchecked exceptions
+
+#### Checked exceptions
-We must handle these conditions because they affect the flow of the application negatively and form exceptions.
+_Checked exceptions_ are exceptional conditions that an application should anticipate and recover from.
+An example of a checked exception is the `FileNotFoundException` which occurs when a method is trying to read a file that does not exist.
-## Types of Java Exceptions
+This type of exception is checked at compile-time: methods that throw checked exceptions should specify this in their method signature, and code calling a method that might throw a checked exception is required to handle it or the code will not compile.
-There are mainly two types of exceptions: checked and unchecked. An error is considered as the unchecked exception. However, according to Oracle, there are three types of exceptions namely:
+All checked exceptions are subclasses of `Exception` that do not extend `RuntimeException`.
-1. Checked Exception
-2. Unchecked Exception
-3. Error
+#### Unchecked exceptions
-### 1. Checked Exception
-The classes that directly inherit the Throwable class except RuntimeException and Error are known as checked exceptions.
-For example, IOException, SQLException, etc. Checked exceptions are checked at compile-time.
+_Unchecked exceptions_ are exceptional conditions that an application usually cannot anticipate or recover from.
+An example of an unchecked exception is the `NullPointerException` which occurs when a method that is expecting a non-null value but receives `null`.
-### 2. Unchecked Exception
-The classes that inherit the RuntimeException are known as unchecked exceptions.
-For example, ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException, etc.
-Unchecked exceptions are not checked at compile-time, but they are checked at runtime.
+This type of exception is not checked at compile-time: methods that throw unchecked exceptions are not required to specify this in their method signature, and code calling a method that might throw an unchecked exception is not required to handle it.
-### 3. Error
-Error is irrecoverable.
-Some example of errors are OutOfMemoryError, VirtualMachineError, AssertionError etc.
+All unchecked exceptions inherit from `RuntimeException`, which itself is an extension of `Exception`.
-## Handling Exceptions
+### Throwing exceptions
-### Try-Catch block
+A method in Java can throw an exception by using the `throw` statement.
-The `try` statement allows you to define a block of code to be tested for errors while it is being executed.
+#### Throwing a checked exception
-The `catch` statement allows you to define a block of code to be executed, if an error occurs in the try block.
+When throwing a checked exception from a method, it is required to specify this in the method signature by using the `throws` keyword, as shown in the example below.
+This forces calling code to anticipate that an exception might be thrown and handle it accordingly.
-For example:
```java
-public int getPlayerScore(String playerFile) {
- try {
- Scanner contents = new Scanner(new File(playerFile));
- return Integer.parseInt(contents.nextLine());
- } catch (FileNotFoundException noFile) {
- throw new IllegalArgumentException("File not found", noFile);
+public class InsufficientBalanceException extends Exception {
+
+}
+
+public class BankAccount {
+ public void withdraw(double amount) throws InsufficientBalanceException {
+ if (balance < amount) {
+ throw new InsufficientBalanceException();
}
+
+ // rest of the method implementation
+ }
}
```
-### The Throw/Throws keyword
+#### Throwing an unchecked exception
-If a method does not handle a checked exception, the method must declare it using the `throws` keyword.
-The `throws` keyword appears at the end of a method's signature.
+When throwing an unchecked exception from a method, it is not required to specify this in the method signature - although it is supported.
-You can throw an exception, either a newly instantiated one or an exception that you just caught, by using the `throw` keyword.
-
-#### Example
```java
-import java.io.*;
-public class className {
-
- public void deposit(double amount) throws RemoteException {
- // Method implementation
- throw new RemoteException();
- }
- // Remainder of class definition
+public class BankAccount {
+ public void withdraw(double amount) {
+ if (amount < 0) {
+ throw new IllegalArgumentException("Cannot withdraw a negative amount");
+ }
+
+ // rest of the method implementation
+ }
}
```
-### Finally
+### Handling exceptions
+
+Handling exceptions in Java is done with the `try`, `catch` and `finally` keywords.
-The `finally` statement (optional) lets you execute code, after `try...catch`, **regardless of the result**.
+- Code statements that might throw an exception should be wrapped in a `try` block.
+- The `try` block is followed by one or more `catch` blocks that catch the exceptions thrown in the `try` block.
+- The `catch` blocks are optionally followed by a `finally` block that always executes after the `try` block, regardless of whether an exception was thrown or not.
-#### Example
+The following example shows how these keywords work:
```java
-public class Main {
- public static void main(String[] args) {
+public class ATM {
+ public void withdraw(BankAccount bankAccount, double amount) {
try {
- int[] myNumbers = {1, 2, 3};
- System.out.println(myNumbers[10]);
- } catch (Exception e) {
- System.out.println("Something went wrong.");
+ System.out.println("Withdrawing " + amount);
+ bankAccount.withdraw(amount);
+ System.out.println("Withdrawal succeeded");
+ } catch (InsufficientBalanceException) {
+ System.out.println("Withdrawal failed: insufficient balance");
+ } catch (RuntimeException e) {
+ System.out.println("Withdrawal failed: " + e.getMessage());
} finally {
- System.out.println("The 'try catch' is finished.");
+ System.out.println("Current balance: " + bankAccount.getBalance());
}
}
}
```
-The output will be:
+In this example, when no exception is thrown, the following is printed:
-```
-Something went wrong.
-The 'try catch' is finished.
+```text
+Withdrawing 10.0
+Withdrawal succeeded
+Current balance: 5.0
```
-## User-Defined Exceptions
+However, should the `bankAccount.withdraw(amount)` statement throw an `InsufficientBalanceException`, the following is printed:
-You can create your own exceptions in Java.
-Keep the following points in mind when writing your own exception classes −
+```text
+Withdrawing 10.0
+Withdrawal failed: insufficient balance
+Current balance: 5.0
+```
-* All exceptions must be a child of Throwable.
+Or, in case an unchecked exception is thrown by the `bankAccount.withdraw(amount)`, the following is printed:
-* If you want to write a checked exception that is automatically enforced by the Handle or Declare Rule, you need to extend the Exception class.
+```text
+Withdrawing -10.0
+Withdrawal failed: Cannot withdraw a negative amount
+Current balance: 5.0
+```
-* If you want to write a runtime exception, you need to extend the RuntimeException class.
+### Errors
-We can define our own Exception class as below:
+Java also has a separate category called _Errors_ which are serious problems that are external to an application.
+An example of an error is the `OutOfMemoryError` which occurs when an application is trying to use more memory than is available on the system.
-```java
-class MyException extends Exception {
-}
-```
\ No newline at end of file
+Like unchecked exceptions, errors are not checked at compile-time.
+The difference is that they represent system level problems and are generally thrown by the Java Virtual machine or environment instead of the application.
+Applications should generally not attempt to catch or handle them.
+
+All errors in Java inherit from the `Error` class.
diff --git a/exercises/concept/calculator-conundrum/.docs/introduction.md.tpl b/exercises/concept/calculator-conundrum/.docs/introduction.md.tpl
new file mode 100644
index 000000000..52d25c5cb
--- /dev/null
+++ b/exercises/concept/calculator-conundrum/.docs/introduction.md.tpl
@@ -0,0 +1,5 @@
+# Introduction
+
+## Exceptions
+
+%{concept:exceptions}
diff --git a/exercises/concept/calculator-conundrum/.meta/config.json b/exercises/concept/calculator-conundrum/.meta/config.json
index fd4a19013..1c6c65d24 100644
--- a/exercises/concept/calculator-conundrum/.meta/config.json
+++ b/exercises/concept/calculator-conundrum/.meta/config.json
@@ -1,9 +1,12 @@
{
- "blurb": "Learn about exception-handling by making a simple calculator.",
"authors": [
"rv02",
"jmrunkle"
],
+ "contributors": [
+ "jagdish-15",
+ "sanderploegsma"
+ ],
"files": {
"solution": [
"src/main/java/CalculatorConundrum.java"
@@ -15,11 +18,14 @@
".meta/src/reference/java/CalculatorConundrum.java"
],
"editor": [
- "src/main/java/IllegalOperationException.java",
- "src/main/java/SimpleOperation.java"
+ "src/main/java/IllegalOperationException.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"csharp/calculator-conundrum"
- ]
+ ],
+ "blurb": "Learn about exception-handling by making a simple calculator."
}
diff --git a/exercises/concept/calculator-conundrum/.meta/design.md b/exercises/concept/calculator-conundrum/.meta/design.md
index b5a18e766..5134eb08a 100644
--- a/exercises/concept/calculator-conundrum/.meta/design.md
+++ b/exercises/concept/calculator-conundrum/.meta/design.md
@@ -2,24 +2,31 @@
## Learning Objectives
-* Know the concept of exception handling in java
-* Know how to implement and handle exception in different ways
+- Know the concept of exception handling in Java.
+- Know how and when to throw an exception.
+- Know how to catch an exception.
-## Out of scope
+## Out of scope
-* finally
+- The `finally` keyword.
## Concepts
-* exception-handling
-* try-catch
-* throw and throws keyword
-* user-defined exceptions
+- `exceptions`: know what exceptions are, how and when to throw an exception, know how to catch an exception.
## Prerequisites
-* `basics`: know how to define methods.
-* `conditionals`: know how to do conditional logic.
-* `switch-case`: know how to work with a switch-case conditionals.
+- `conditionals-if`: know how to do conditional logic.
+- `switch-statement`: know how to work with a `switch` statement.
+## Analyzer
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `essential`: Verify that the solution is using the try catch statement for the division.
+- `actionable`: If the solution is wrapping all the code in a try catch statement, instruct the student to only use the `try catch` for the division statement
+- `actionable`: If the solution uses only `if` statement, instruct the student that he/she can use the `switch case` statement to perform the operations.
+- `informative`: If the solution does not throw the exception for `Operation cannot be null` and `Operation cannot be empty` at the beginning, suggest the fail-fast approach to the student.
+ Inform this way of implementation can be less error-prone and more readable as suggested by Martin Fowler:
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/calculator-conundrum/.meta/src/reference/java/CalculatorConundrum.java b/exercises/concept/calculator-conundrum/.meta/src/reference/java/CalculatorConundrum.java
index ee8379192..fb409cc71 100644
--- a/exercises/concept/calculator-conundrum/.meta/src/reference/java/CalculatorConundrum.java
+++ b/exercises/concept/calculator-conundrum/.meta/src/reference/java/CalculatorConundrum.java
@@ -1,30 +1,27 @@
public class CalculatorConundrum {
-
public String calculate(int operand1, int operand2, String operation) {
- int result;
if (operation == null) {
- throw new IllegalOperationException("Operation cannot be null");
+ throw new IllegalArgumentException("Operation cannot be null");
+ }
+
+ if (operation.isEmpty()) {
+ throw new IllegalArgumentException("Operation cannot be empty");
}
+
+ int result;
switch (operation) {
- case "+":
- result = SimpleOperation.addition(operand1, operand2);
- break;
- case "*":
- result = SimpleOperation.multiplication(operand1, operand2);
- break;
- case "/":
+ case "+" -> result = operand1 + operand2;
+ case "*" -> result = operand1 * operand2;
+ case "/" -> {
try {
- result = SimpleOperation.division(operand1, operand2);
+ result = operand1 / operand2;
} catch (ArithmeticException e) {
- throw new IllegalOperationException("Divide by zero operation illegal", e);
+ throw new IllegalOperationException("Division by zero is not allowed", e);
}
- break;
- case "":
- throw new IllegalOperationException("Operation cannot be empty");
- default:
- throw new IllegalOperationException(String.format("%s operation does not exist", operation));
+ }
+ default -> throw new IllegalOperationException("Operation '" + operation + "' does not exist");
}
- return String.format("%d %s %d = %s", operand1, operation, operand2, result);
- }
+ return String.valueOf(operand1) + " " + String.valueOf(operation) + " " + operand2 + " = " + result;
+ }
}
diff --git a/exercises/concept/calculator-conundrum/.meta/src/reference/java/IllegalOperationException.java b/exercises/concept/calculator-conundrum/.meta/src/reference/java/IllegalOperationException.java
new file mode 100644
index 000000000..78d0a8e79
--- /dev/null
+++ b/exercises/concept/calculator-conundrum/.meta/src/reference/java/IllegalOperationException.java
@@ -0,0 +1,9 @@
+public class IllegalOperationException extends RuntimeException {
+ public IllegalOperationException(String errorMessage) {
+ super(errorMessage);
+ }
+
+ public IllegalOperationException(String errorMessage, Throwable cause) {
+ super(errorMessage, cause);
+ }
+}
diff --git a/exercises/concept/calculator-conundrum/build.gradle b/exercises/concept/calculator-conundrum/build.gradle
index aef19592c..d28f35dee 100644
--- a/exercises/concept/calculator-conundrum/build.gradle
+++ b/exercises/concept/calculator-conundrum/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/calculator-conundrum/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/calculator-conundrum/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/calculator-conundrum/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/calculator-conundrum/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/calculator-conundrum/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/calculator-conundrum/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/calculator-conundrum/gradlew b/exercises/concept/calculator-conundrum/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/calculator-conundrum/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/calculator-conundrum/gradlew.bat b/exercises/concept/calculator-conundrum/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/calculator-conundrum/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/calculator-conundrum/src/main/java/CalculatorConundrum.java b/exercises/concept/calculator-conundrum/src/main/java/CalculatorConundrum.java
index c155ba734..c6d9b809f 100644
--- a/exercises/concept/calculator-conundrum/src/main/java/CalculatorConundrum.java
+++ b/exercises/concept/calculator-conundrum/src/main/java/CalculatorConundrum.java
@@ -1,5 +1,4 @@
class CalculatorConundrum {
-
public String calculate(int operand1, int operand2, String operation) {
throw new UnsupportedOperationException("Please implement the CalculatorConundrum.calculate() method");
}
diff --git a/exercises/concept/calculator-conundrum/src/main/java/IllegalOperationException.java b/exercises/concept/calculator-conundrum/src/main/java/IllegalOperationException.java
index a57027de9..78d0a8e79 100644
--- a/exercises/concept/calculator-conundrum/src/main/java/IllegalOperationException.java
+++ b/exercises/concept/calculator-conundrum/src/main/java/IllegalOperationException.java
@@ -1,5 +1,4 @@
public class IllegalOperationException extends RuntimeException {
-
public IllegalOperationException(String errorMessage) {
super(errorMessage);
}
@@ -8,4 +7,3 @@ public IllegalOperationException(String errorMessage, Throwable cause) {
super(errorMessage, cause);
}
}
-
diff --git a/exercises/concept/calculator-conundrum/src/main/java/SimpleOperation.java b/exercises/concept/calculator-conundrum/src/main/java/SimpleOperation.java
deleted file mode 100644
index 86d881908..000000000
--- a/exercises/concept/calculator-conundrum/src/main/java/SimpleOperation.java
+++ /dev/null
@@ -1,17 +0,0 @@
-class SimpleOperation {
- public static int division(int operand1, int operand2)
- {
- return operand1 / operand2;
- }
-
- public static int multiplication(int operand1, int operand2)
- {
- return operand1 * operand2;
- }
-
- public static int addition(int operand1, int operand2)
- {
- return operand1 + operand2;
- }
-}
-
diff --git a/exercises/concept/calculator-conundrum/src/test/java/CalculatorConundrumTest.java b/exercises/concept/calculator-conundrum/src/test/java/CalculatorConundrumTest.java
index 6cb354e2c..e75144042 100644
--- a/exercises/concept/calculator-conundrum/src/test/java/CalculatorConundrumTest.java
+++ b/exercises/concept/calculator-conundrum/src/test/java/CalculatorConundrumTest.java
@@ -1,75 +1,89 @@
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertThrows;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
-import org.junit.Test;
+import static org.assertj.core.api.Assertions.*;
public class CalculatorConundrumTest {
@Test
+ @Tag("task:1")
+ @DisplayName("The calculate method returns the correct result when adding small operands")
public void additionWithSmallOperands() {
assertThat(new CalculatorConundrum().calculate(22, 25, "+")).isEqualTo("22 + 25 = 47");
}
@Test
+ @Tag("task:1")
+ @DisplayName("The calculate method returns the correct result when adding large operands")
public void additionWithLargeOperands() {
assertThat(new CalculatorConundrum().calculate(378_961, 399_635, "+")).isEqualTo("378961 + 399635 = 778596");
}
@Test
+ @Tag("task:1")
+ @DisplayName("The calculate method returns the correct result when multiplying small operands")
public void multiplicationWithSmallOperands() {
assertThat(new CalculatorConundrum().calculate(3, 21, "*")).isEqualTo("3 * 21 = 63");
}
@Test
+ @Tag("task:1")
+ @DisplayName("The calculate method returns the correct result when multiplying large operands")
public void multiplicationWithLargeOperands() {
assertThat(new CalculatorConundrum().calculate(72_441, 2_048, "*")).isEqualTo("72441 * 2048 = 148359168");
}
@Test
+ @Tag("task:1")
+ @DisplayName("The calculate method returns the correct result when dividing small operands")
public void divisionWithSmallOperands() {
assertThat(new CalculatorConundrum().calculate(72, 9, "/")).isEqualTo("72 / 9 = 8");
}
@Test
+ @Tag("task:1")
+ @DisplayName("The calculate method returns the correct result when dividing large operands")
public void divisionWithLargeOperands() {
assertThat(new CalculatorConundrum().calculate(1_338_800, 83_675, "/")).isEqualTo("1338800 / 83675 = 16");
}
@Test
- public void throwExceptionForDivisionByZero() {
- IllegalOperationException thrown = assertThrows(
- IllegalOperationException.class,
- () -> new CalculatorConundrum().calculate(33, 0, "/")
- );
- assertThat(thrown.getCause()).isInstanceOf(ArithmeticException.class);
- assertThat(thrown).hasMessage("Divide by zero operation illegal");
- }
-
- @Test
- public void throwExceptionForNonValidArguments() {
+ @Tag("task:2")
+ @DisplayName("The calculate method throws IllegalOperationException when passing invalid operation")
+ public void throwExceptionForUnknownOperation() {
String invalidOperation = "**";
- IllegalOperationException thrown = assertThrows(
- IllegalOperationException.class,
- () -> new CalculatorConundrum().calculate(3, 78, invalidOperation)
- );
- assertThat(thrown).hasMessage(String.format("%s operation does not exist", invalidOperation));
+ String expectedMessage = "Operation '" + invalidOperation + "' does not exist";
+ assertThatExceptionOfType(IllegalOperationException.class)
+ .isThrownBy(() -> new CalculatorConundrum().calculate(3, 78, invalidOperation))
+ .withMessage(expectedMessage);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The calculate method throws IllegalArgumentException when passing null operation")
public void throwExceptionForNullAsOperation() {
- IllegalOperationException thrown = assertThrows(
- IllegalOperationException.class,
- () -> new CalculatorConundrum().calculate(66, 65, null)
- );
- assertThat(thrown).hasMessage("Operation cannot be null");
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> new CalculatorConundrum().calculate(66, 65, null))
+ .withMessage("Operation cannot be null");
}
@Test
+ @Tag("task:2")
+ @DisplayName("The calculate method throws IllegalArgumentException when passing empty operation")
public void throwExceptionForAnEmptyStringOperation() {
- IllegalOperationException thrown = assertThrows(
- IllegalOperationException.class,
- () -> new CalculatorConundrum().calculate(34, 324, "")
- );
- assertThat(thrown).hasMessage("Operation cannot be empty");
+ assertThatExceptionOfType(IllegalArgumentException.class)
+ .isThrownBy(() -> new CalculatorConundrum().calculate(34, 324, ""))
+ .withMessage("Operation cannot be empty");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("The calculate method throws IllegalOperationException when dividing by zero")
+ public void throwExceptionForDivisionByZero() {
+ assertThatExceptionOfType(IllegalOperationException.class)
+ .isThrownBy(() -> new CalculatorConundrum().calculate(33, 0, "/"))
+ .withMessage("Division by zero is not allowed")
+ .withCauseInstanceOf(ArithmeticException.class);
}
}
diff --git a/exercises/concept/captains-log/.docs/hints.md b/exercises/concept/captains-log/.docs/hints.md
new file mode 100644
index 000000000..168c3937f
--- /dev/null
+++ b/exercises/concept/captains-log/.docs/hints.md
@@ -0,0 +1,14 @@
+# Hints
+
+## 1. Generate a random planet
+
+- Java does not provide a function to choose an element from a collection at random.
+- Remember that you can retrieve an element from an array by its index, which is an integer.
+
+## 2. Generate a random starship registry number
+
+- The `java.util.Random` class provides a method to generate a random `int` between 0 and a given maximum.
+
+## 3. Generate a random stardate
+
+- The `java.util.Random` class method to generate a random `double` always returns a number between `0.0` and `1.0`.
diff --git a/exercises/concept/captains-log/.docs/instructions.md b/exercises/concept/captains-log/.docs/instructions.md
new file mode 100644
index 000000000..61321bbe3
--- /dev/null
+++ b/exercises/concept/captains-log/.docs/instructions.md
@@ -0,0 +1,59 @@
+# Instructions
+
+Mary is a big fan of the TV series _Star Trek: The Next Generation_.
+She often plays pen-and-paper role playing games, where she and her friends pretend to be the crew of the _Starship Enterprise_.
+Mary's character is Captain Picard, which means she has to keep the captain's log.
+She loves the creative part of the game, but doesn't like to generate random data on the spot.
+
+Help Mary by creating random generators for data commonly appearing in the captain's log.
+
+~~~~exercism/note
+The starter implementation of this exercise takes a `java.util.Random` instance as constructor argument.
+This allows the exercise's tests to pass an instance with a predefined seed, which makes the test results predictable.
+
+Therefore, you are expected to use the provided `java.util.Random` instance in your implementation.
+~~~~
+
+## 1. Generate a random planet
+
+The _Starship Enterprise_ encounters many planets in its travels.
+Planets in the Star Trek universe are split into categories based on their properties.
+For example, Earth is a class `M` planet.
+All possible planetary classes are: `D`, `H`, `J`, `K`, `L`, `M`, `N`, `R`, `T`, and `Y`.
+
+Implement the `randomPlanetClass()` method.
+It should return one of the planetary classes at random.
+
+```java
+captainsLog.randomPlanetClass();
+// => "K"
+```
+
+## 2. Generate a random starship registry number
+
+Enterprise (registry number `NCC-1701`) is not the only starship flying around!
+When it rendezvous with another starship, Mary needs to log the registry number of that starship.
+
+Registry numbers start with the prefix "NCC-" and then use a number from `1000` to `9999` (inclusive).
+
+Implement the `randomShipRegistryNumber()` method that returns a random starship registry number.
+
+```java
+captainsLog.randomShipRegistryNumber();
+// => "NCC-1947"
+```
+
+## 3. Generate a random stardate
+
+What's the use of a log if it doesn't include dates?
+
+A stardate is a floating point number.
+The adventures of the _Starship Enterprise_ from the first season of _The Next Generation_ take place between the stardates `41000.0` and `42000.0`.
+The "4" stands for the 24th century, the "1" for the first season.
+
+Implement the `randomStardate()` method that returns a floating point number between `41000.0` (inclusive) and `42000.0` (exclusive).
+
+```java
+captainsLog.randomStardate();
+// => 41458.15721310934
+```
diff --git a/exercises/concept/captains-log/.docs/introduction.md b/exercises/concept/captains-log/.docs/introduction.md
new file mode 100644
index 000000000..62dc7d234
--- /dev/null
+++ b/exercises/concept/captains-log/.docs/introduction.md
@@ -0,0 +1,59 @@
+# Introduction
+
+## Randomness
+
+An instance of the `java.util.Random` class can be used to generate random numbers in Java.
+
+### Random integers
+
+A random integer can be generated using the `nextInt()` method.
+This will generate a value in the range from `Integer.MIN_VALUE` to `Integer.MAX_VALUE`.
+
+```java
+Random random = new Random();
+
+random.nextInt();
+// => -1169335537
+```
+
+To limit the range of generated values, use `nextInt(int)`.
+This will generate a value in the range from `0` (inclusive) to the given upper bound (exclusive).
+
+For example, this will generate a random number from `0` through `9`.
+
+```java
+Random random = new Random();
+
+random.nextInt(10);
+// => 6
+```
+
+And this will generate a random number from `10` through `19`.
+
+```java
+Random random = new Random();
+
+10 + random.nextInt(10);
+// => 11
+```
+
+### Random doubles
+
+A random double can be generated using the `nextDouble()` method.
+This will generate a value in the range from `0.0` to `1.0`.
+
+```java
+Random random = new Random();
+
+random.nextDouble();
+// => 0.19250004204021398
+```
+
+And this will generate a random number from `100.0` to `200.0`.
+
+```java
+Random random = new Random();
+
+100.0 + 100.0 * random.nextDouble();
+// => 111.31849856260328
+```
diff --git a/exercises/concept/captains-log/.docs/introduction.md.tpl b/exercises/concept/captains-log/.docs/introduction.md.tpl
new file mode 100644
index 000000000..2f743727f
--- /dev/null
+++ b/exercises/concept/captains-log/.docs/introduction.md.tpl
@@ -0,0 +1,5 @@
+# Introduction
+
+## Randomness
+
+%{concept:randomness}
diff --git a/exercises/concept/captains-log/.meta/config.json b/exercises/concept/captains-log/.meta/config.json
new file mode 100644
index 000000000..14ea12325
--- /dev/null
+++ b/exercises/concept/captains-log/.meta/config.json
@@ -0,0 +1,26 @@
+{
+ "authors": [
+ "sanderploegsma"
+ ],
+ "contributors": [
+ "santialb"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/CaptainsLog.java"
+ ],
+ "test": [
+ "src/test/java/CaptainsLogTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/CaptainsLog.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "forked_from": [
+ "elixir/captains-log"
+ ],
+ "blurb": "Learn about randomness by helping Mary generate stardates and starship registry numbers for her Star Trek themed pen-and-paper role playing sessions."
+}
diff --git a/exercises/concept/captains-log/.meta/design.md b/exercises/concept/captains-log/.meta/design.md
new file mode 100644
index 000000000..dfd04501b
--- /dev/null
+++ b/exercises/concept/captains-log/.meta/design.md
@@ -0,0 +1,39 @@
+# Design
+
+## Learning objectives
+
+- Know how to generate a random `Integer`.
+- Know how to generate a random `Double`.
+- Know how to select a random element from a collection.
+
+## Out of scope
+
+- `java.util.SecureRandom`.
+
+## Concepts
+
+- `randomness`
+
+## Prerequisites
+
+- `strings`: know how to use string formatting.
+- `numbers`: know how to apply basic mathematical operators.
+- `arrays`: know how to retrieve array elements by their index.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the solution hardcodes the number of planet classes in `randomPlanetClass`, instruct the student to use `length` method instead.
+- `informative`: If the solution uses Java version >= 17 and [`RandomGenerator.nextDouble(double)`][nextDouble(double)], inform the student that they could use [`RandomGenerator.nextDouble(double, double)`][nextDouble(double, double)] to achieve a clearer solution.
+- `informative`: If the solution uses Java version >= 17 and [`RandomGenerator.nextInt(int)`][nextInt(int)], inform the student that they could use [`RandomGenerator.nextInt(int, int)`][nextInt(int, int)] to achieve a clearer solution.
+- `informative`: If the solution uses `String.format` in the `randomShipRegistryNumber` method, inform the student that this cause a small performance penalty compared to string concatenation.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
+[nextDouble(double)]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/RandomGenerator.html#nextDouble(double)
+[nextDouble(double, double)]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/RandomGenerator.html#nextDouble(double,double)
+[nextInt(int)]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/RandomGenerator.html#nextInt(int)
+[nextInt(int, int)]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/RandomGenerator.html#nextInt(int,int)
diff --git a/exercises/concept/captains-log/.meta/src/reference/java/CaptainsLog.java b/exercises/concept/captains-log/.meta/src/reference/java/CaptainsLog.java
new file mode 100644
index 000000000..3e4d727fc
--- /dev/null
+++ b/exercises/concept/captains-log/.meta/src/reference/java/CaptainsLog.java
@@ -0,0 +1,31 @@
+import java.util.Random;
+
+class CaptainsLog {
+
+ private static final char[] PLANET_CLASSES = new char[]{'D', 'H', 'J', 'K', 'L', 'M', 'N', 'R', 'T', 'Y'};
+
+ private final Random random;
+
+ CaptainsLog(Random random) {
+ this.random = random;
+ }
+
+ char randomPlanetClass() {
+ var index = random.nextInt(PLANET_CLASSES.length);
+ return PLANET_CLASSES[index];
+ }
+
+ String randomShipRegistryNumber() {
+ var start = 1000;
+ var end = 10000;
+
+
+ return "NCC-" + String.valueOf(start + random.nextInt(end - start));
+ }
+
+ double randomStardate() {
+ var start = 41000.0;
+ var end = 42000.0;
+ return start + random.nextDouble() * (end - start);
+ }
+}
diff --git a/exercises/concept/captains-log/build.gradle b/exercises/concept/captains-log/build.gradle
new file mode 100644
index 000000000..dd3862eb9
--- /dev/null
+++ b/exercises/concept/captains-log/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/concept/captains-log/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/captains-log/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/captains-log/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/captains-log/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/captains-log/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/captains-log/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/captains-log/gradlew b/exercises/concept/captains-log/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/captains-log/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/captains-log/gradlew.bat b/exercises/concept/captains-log/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/captains-log/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/captains-log/src/main/java/CaptainsLog.java b/exercises/concept/captains-log/src/main/java/CaptainsLog.java
new file mode 100644
index 000000000..686f7f0fb
--- /dev/null
+++ b/exercises/concept/captains-log/src/main/java/CaptainsLog.java
@@ -0,0 +1,24 @@
+import java.util.Random;
+
+class CaptainsLog {
+
+ private static final char[] PLANET_CLASSES = new char[]{'D', 'H', 'J', 'K', 'L', 'M', 'N', 'R', 'T', 'Y'};
+
+ private Random random;
+
+ CaptainsLog(Random random) {
+ this.random = random;
+ }
+
+ char randomPlanetClass() {
+ throw new UnsupportedOperationException("Please implement the CaptainsLog.randomPlanetClass() method");
+ }
+
+ String randomShipRegistryNumber() {
+ throw new UnsupportedOperationException("Please implement the CaptainsLog.randomShipRegistryNumber() method");
+ }
+
+ double randomStardate() {
+ throw new UnsupportedOperationException("Please implement the CaptainsLog.randomStardate() method");
+ }
+}
diff --git a/exercises/concept/captains-log/src/test/java/CaptainsLogTest.java b/exercises/concept/captains-log/src/test/java/CaptainsLogTest.java
new file mode 100644
index 000000000..56e1e15e5
--- /dev/null
+++ b/exercises/concept/captains-log/src/test/java/CaptainsLogTest.java
@@ -0,0 +1,86 @@
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.util.Random;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CaptainsLogTest {
+
+ private Random random1;
+ private Random random2;
+ private Random random3;
+
+ @BeforeEach
+ public void setup() {
+ random1 = new Random(47);
+ random2 = new Random(474747);
+ random3 = new Random(474747474747L);
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Generating a random planet class")
+ public void testRandomPlanetClass() {
+ assertThat(new CaptainsLog(random1).randomPlanetClass()).isEqualTo('T');
+ assertThat(new CaptainsLog(random2).randomPlanetClass()).isEqualTo('K');
+ assertThat(new CaptainsLog(random3).randomPlanetClass()).isEqualTo('J');
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Generated planet classes are valid")
+ public void testRandomPlanetClassIsValid() {
+ var captainsLog = new CaptainsLog(new Random());
+
+ for (int i = 0; i < 100; i++) {
+ assertThat(captainsLog.randomPlanetClass())
+ .isIn('D', 'H', 'J', 'K', 'L', 'M', 'N', 'R', 'T', 'Y');
+ }
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Generating a random ship registry number")
+ public void testRandomShipRegistryNumber() {
+ assertThat(new CaptainsLog(random1).randomShipRegistryNumber()).isEqualTo("NCC-6258");
+ assertThat(new CaptainsLog(random2).randomShipRegistryNumber()).isEqualTo("NCC-1683");
+ assertThat(new CaptainsLog(random3).randomShipRegistryNumber()).isEqualTo("NCC-4922");
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Generated ship registry numbers are valid")
+ public void testRandomShipRegistryNumberIsValid() {
+ var captainsLog = new CaptainsLog(new Random());
+
+ for (int i = 0; i < 100; i++) {
+ var shipRegistryNumber = captainsLog.randomShipRegistryNumber();
+ var number = Integer.parseInt(shipRegistryNumber.substring(4));
+
+ assertThat(number).isBetween(1000, 9999);
+ }
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Generating a random stardate")
+ public void testRandomStardate() {
+ assertThat(new CaptainsLog(random1).randomStardate()).isEqualTo(41727.115786073);
+ assertThat(new CaptainsLog(random2).randomStardate()).isEqualTo(41531.31240225019);
+ assertThat(new CaptainsLog(random3).randomStardate()).isEqualTo(41283.50713600276);
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Generated stardates are valid")
+ public void testRandomStardateIsValid() {
+ var captainsLog = new CaptainsLog(new Random());
+
+ for (int i = 0; i < 100; i++) {
+ assertThat(captainsLog.randomStardate()).isBetween(41000.0, 42000.0);
+ }
+ }
+}
diff --git a/exercises/concept/cars-assemble/.docs/instructions.md b/exercises/concept/cars-assemble/.docs/instructions.md
index a01a68726..5ad3ea97f 100644
--- a/exercises/concept/cars-assemble/.docs/instructions.md
+++ b/exercises/concept/cars-assemble/.docs/instructions.md
@@ -13,7 +13,7 @@ You have two tasks.
## 1. Calculate the production rate per hour
-Implement the `CarsAssemble.productionRatePerHour()` method to calculate the assembly line's production rate per hour, taking into account its current assembly line's speed :
+Implement the `CarsAssemble.productionRatePerHour()` method to calculate the assembly line's production rate per hour, taking into account its current assembly line's speed and success rate:
```Java
CarsAssemble.productionRatePerHour(6)
diff --git a/exercises/concept/cars-assemble/.docs/introduction.md b/exercises/concept/cars-assemble/.docs/introduction.md
index 89111fb24..8569aa973 100644
--- a/exercises/concept/cars-assemble/.docs/introduction.md
+++ b/exercises/concept/cars-assemble/.docs/introduction.md
@@ -1,33 +1,85 @@
# Introduction
+## Numbers
+
There are two different types of numbers in Java:
-- Integers: numbers with no digits behind the decimal separator (whole numbers). Examples are `-6`, `0`, `1`, `25`, `976` and `-500000`.
-- Floating-point numbers: numbers with zero or more digits behind the decimal separator. Examples are `-20.4`, `0.1`, `2.72`, `16.984025` and `1024.0`.
+- Integers: numbers with no digits behind the decimal separator (whole numbers).
+ Examples are `-6`, `0`, `1`, `25`, `976` and `-500000`.
+- Floating-point numbers: numbers with zero or more digits behind the decimal separator.
+ Examples are `-20.4`, `0.1`, `2.72`, `16.984025` and `1024.0`.
-The two most common numeric types in Java are `int` and `double`. An `int` is a 32-bit integer and a `double` is a 64-bit floating-point number.
+The two most common numeric types in Java are `int` and `double`.
+An `int` is a 32-bit integer and a `double` is a 64-bit floating-point number.
-Arithmetic is done using the standard arithmetic operators. Numbers can be compared using the standard numeric comparison operators and the equality (`==`) and inequality (`!=`) operators.
+Arithmetic is done using the standard arithmetic operators.
+Numbers can be compared using the standard numeric comparison operators (eg. `5 > 4` and `4 <= 5`) and the equality (`==`) and inequality (`!=`) operators.
Java has two types of numeric conversions:
1. Implicit conversions: no data will be lost and no additional syntax is required.
2. Explicit conversions: data could be lost and additional syntax in the form of a _cast_ is required.
-As an `int` has less precision than a `double`, converting from an `int` to a `double` is safe and is thus an implicit conversion. However, converting from a `double` to an `int` could mean losing data, so that requires an explicit conversion.
+As an `int` has less precision than a `double`, converting from an `int` to a `double` is safe and is thus an implicit conversion.
+However, converting from a `double` to an `int` could mean losing data, so that requires an explicit conversion.
+
+## If-Else Statements
+
+### The _if-then_ statement
+
+The most basic control flow statement in Java is the _if-then_ statement.
+This statement is used to only execute a section of code if a particular condition is `true`.
+An _if-then_ statement is defined using the `if` clause:
+
+```java
+class Car {
+ void drive() {
+ // the "if" clause: the car needs to have fuel left to drive
+ if (fuel > 0) {
+ // the "then" clause: the car drives, consuming fuel
+ fuel--;
+ }
+ }
+}
+```
+
+In the above example, if the car is out of fuel, calling the `Car.drive` method will do nothing.
+
+### The _if-then-else_ statement
+
+The _if-then-else_ statement provides an alternative path of execution for when the condition in the `if` clause evaluates to `false`.
+This alternative path of execution follows an `if` clause and is defined using the `else` clause:
+
+```java
+class Car {
+ void drive() {
+ if (fuel > 0) {
+ fuel--;
+ } else {
+ stop();
+ }
+ }
+}
+```
+
+In the above example, if the car is out of fuel, calling the `Car.drive` method will call another method to stop the car.
-In this exercise you must conditionally execute logic. The most common way to do this in Java is by using an `if/else` statement:
+The _if-then-else_ statement also supports multiple conditions by using the `else if` clause:
```java
-int x = 6;
-
-if (x == 5) {
- // Execute logic if x equals 5
-} else if (x > 7) {
- // Execute logic if x greater than 7
-} else {
- // Execute logic in all other cases
+class Car {
+ void drive() {
+ if (fuel > 5) {
+ fuel--;
+ } else if (fuel > 0) {
+ turnOnFuelLight();
+ fuel--;
+ } else {
+ stop();
+ }
+ }
}
```
-The condition of an `if` statement must be of type `boolean`. Java has no concept of _truthy_ values.
+In the above example, driving the car when the fuel is less then or equal to `5` will drive the car, but it will turn on the fuel light.
+When the fuel reaches `0`, the car will stop driving.
diff --git a/exercises/concept/cars-assemble/.docs/introduction.md.tpl b/exercises/concept/cars-assemble/.docs/introduction.md.tpl
new file mode 100644
index 000000000..0c29a1a96
--- /dev/null
+++ b/exercises/concept/cars-assemble/.docs/introduction.md.tpl
@@ -0,0 +1,5 @@
+# Introduction
+
+%{concept:numbers}
+
+%{concept:if-else-statements}
diff --git a/exercises/concept/cars-assemble/.meta/config.json b/exercises/concept/cars-assemble/.meta/config.json
index 0e65f521b..5a323d498 100644
--- a/exercises/concept/cars-assemble/.meta/config.json
+++ b/exercises/concept/cars-assemble/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Learn about numbers by analyzing the production of an assembly line.",
"authors": [
"TalesDias"
],
@@ -12,9 +11,13 @@
],
"exemplar": [
".meta/src/reference/java/CarsAssemble.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"csharp/cars-assemble"
- ]
+ ],
+ "blurb": "Learn about numbers by analyzing the production of an assembly line."
}
diff --git a/exercises/concept/cars-assemble/.meta/design.md b/exercises/concept/cars-assemble/.meta/design.md
index 5c1c20972..4c57f8d39 100644
--- a/exercises/concept/cars-assemble/.meta/design.md
+++ b/exercises/concept/cars-assemble/.meta/design.md
@@ -17,10 +17,25 @@
## Concepts
- `numbers`: know of the existence of the two most commonly used number types, `int` and `double`; understand that the former represents whole numbers, and the latter floating-point numbers; know of basic operators such as multiplication, comparison and equality; know how to convert from one numeric type to another; know what implicit and explicit conversions are.
-- `conditionals`: know how to conditionally execute code using an `if` statement.
+- `if-else-statements`: know how to conditionally execute code using an `if` statement.
## Prerequisites
This exercise's prerequisites Concepts are:
- `basics`: know how to define methods.
+- `booleans`: know how to use boolean operators.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the student did not reuse the implementation of the `productionRatePerHour` method in the `workingItemsPerMinute` method, instruct them to do so.
+- `informative`: If the solution is repeatedly hard-coding the value `221`, inform the student that they could store this value in a field to make the code easier to maintain.
+- `informative`: If the solution has `if/else-if` statements in the `productionRatePerHour` method, inform the student that creating a helper method to calculate the succes rate might make their code easier to understand.
+- `informative`: If the solution is using `if/else-if` logic that contains return statements, inform the students that the `else` keywords are redundant and that their code can become more clear by omitting them.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/cars-assemble/.meta/src/reference/java/CarsAssemble.java b/exercises/concept/cars-assemble/.meta/src/reference/java/CarsAssemble.java
index f3ee506f8..2650b9e48 100644
--- a/exercises/concept/cars-assemble/.meta/src/reference/java/CarsAssemble.java
+++ b/exercises/concept/cars-assemble/.meta/src/reference/java/CarsAssemble.java
@@ -4,17 +4,13 @@ public class CarsAssemble {
private final int defaultProductionRate = 221;
public double productionRatePerHour(int speed) {
- return productionRatePerHourForSpeed(speed) * successRate(speed);
+ return defaultProductionRate * speed * successRate(speed);
}
public int workingItemsPerMinute(int speed) {
return (int) (productionRatePerHour(speed) / 60);
}
- private int productionRatePerHourForSpeed(int speed) {
- return defaultProductionRate * speed;
- }
-
private double successRate(int speed) {
if (speed == 10) {
return 0.77;
diff --git a/exercises/concept/cars-assemble/build.gradle b/exercises/concept/cars-assemble/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/cars-assemble/build.gradle
+++ b/exercises/concept/cars-assemble/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/cars-assemble/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/cars-assemble/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/cars-assemble/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/cars-assemble/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/cars-assemble/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/cars-assemble/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/cars-assemble/gradlew b/exercises/concept/cars-assemble/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/cars-assemble/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/cars-assemble/gradlew.bat b/exercises/concept/cars-assemble/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/cars-assemble/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/cars-assemble/src/main/java/CarsAssemble.java b/exercises/concept/cars-assemble/src/main/java/CarsAssemble.java
index df7d63951..a3b0059a3 100644
--- a/exercises/concept/cars-assemble/src/main/java/CarsAssemble.java
+++ b/exercises/concept/cars-assemble/src/main/java/CarsAssemble.java
@@ -1,7 +1,7 @@
public class CarsAssemble {
public double productionRatePerHour(int speed) {
- throw new UnsupportedOperationException("Please implement the CarsAssemble.productionRateperHour() method");
+ throw new UnsupportedOperationException("Please implement the CarsAssemble.productionRatePerHour() method");
}
public int workingItemsPerMinute(int speed) {
diff --git a/exercises/concept/cars-assemble/src/test/java/CarsAssembleTest.java b/exercises/concept/cars-assemble/src/test/java/CarsAssembleTest.java
index 344b7ada0..4a032d60b 100644
--- a/exercises/concept/cars-assemble/src/test/java/CarsAssembleTest.java
+++ b/exercises/concept/cars-assemble/src/test/java/CarsAssembleTest.java
@@ -1,7 +1,10 @@
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
+import static org.assertj.core.api.Assertions.within;
public class CarsAssembleTest {
@@ -9,67 +12,91 @@ public class CarsAssembleTest {
private CarsAssemble carsAssemble;
private double epsilon = 0.0000001d;
- @Before
+ @BeforeEach
public void setUp() {
carsAssemble = new CarsAssemble();
}
@Test
+ @Tag("task:1")
+ @DisplayName("The productionRatePerHour method returns the correct result when line's speed is 0")
public void productionRatePerHourForSpeedZero() {
- assertThat(Math.abs(carsAssemble.productionRatePerHour(0) - 0.0) < epsilon).isTrue();
+ assertThat(carsAssemble.productionRatePerHour(0)).isCloseTo(0.0, within(epsilon));
}
@Test
+ @Tag("task:1")
+ @DisplayName("The productionRatePerHour method returns the correct result when line's speed is 1")
public void productionRatePerHourForSpeedOne() {
- assertThat(Math.abs(carsAssemble.productionRatePerHour(1) - 221.0) < epsilon).isTrue();
+ assertThat(carsAssemble.productionRatePerHour(1)).isCloseTo(221.0, within(epsilon));
}
@Test
+ @Tag("task:1")
+ @DisplayName("The productionRatePerHour method returns the correct result when line's speed is 4")
public void productionRatePerHourForSpeedFour() {
- assertThat(Math.abs(carsAssemble.productionRatePerHour(4) - 884.0) < epsilon).isTrue();
+ assertThat(carsAssemble.productionRatePerHour(4)).isCloseTo(884.0, within(epsilon));
}
@Test
+ @Tag("task:1")
+ @DisplayName("The productionRatePerHour method returns the correct result when line's speed is 7")
public void productionRatePerHourForSpeedSeven() {
- assertThat(Math.abs(carsAssemble.productionRatePerHour(7) - 1392.3) < epsilon).isTrue();
+ assertThat(carsAssemble.productionRatePerHour(7)).isCloseTo(1392.3, within(epsilon));
}
@Test
+ @Tag("task:1")
+ @DisplayName("The productionRatePerHour method returns the correct result when line's speed is 9")
public void productionRatePerHourForSpeedNine() {
- assertThat(Math.abs(carsAssemble.productionRatePerHour(9) - 1591.2) < epsilon).isTrue();
+ assertThat(carsAssemble.productionRatePerHour(9)).isCloseTo(1591.2, within(epsilon));
}
@Test
+ @Tag("task:1")
+ @DisplayName("The productionRatePerHour method returns the correct result when line's speed is 10")
public void productionRatePerHourForSpeedTen() {
- assertThat(Math.abs(carsAssemble.productionRatePerHour(10) - 1701.7) < epsilon).isTrue();
+ assertThat(carsAssemble.productionRatePerHour(10)).isCloseTo(1701.7, within(epsilon));
}
@Test
+ @Tag("task:2")
+ @DisplayName("The workingItemsPerMinute should be 0 when line's speed is 0")
public void workingItemsPerMinuteForSpeedZero() {
assertThat(carsAssemble.workingItemsPerMinute(0)).isEqualTo(0);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The workingItemsPerMinute should be 3 when line's speed is 1")
public void workingItemsPerMinuteForSpeedOne() {
assertThat(carsAssemble.workingItemsPerMinute(1)).isEqualTo(3);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The workingItemsPerMinute should be 16 when line's speed is 5")
public void workingItemsPerMinuteForSpeedFive() {
assertThat(carsAssemble.workingItemsPerMinute(5)).isEqualTo(16);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The workingItemsPerMinute should be 26 when line's speed is 8")
public void workingItemsPerMinuteForSpeedEight() {
assertThat(carsAssemble.workingItemsPerMinute(8)).isEqualTo(26);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The workingItemsPerMinute should be 26 when line's speed is 9")
public void workingItemsPerMinuteForSpeedNine() {
assertThat(carsAssemble.workingItemsPerMinute(9)).isEqualTo(26);
}
@Test
+ @Tag("task:2")
+ @DisplayName("The workingItemsPerMinute should be 28 when line's speed is 10")
public void workingItemsPerMinuteForSpeedTen() {
assertThat(carsAssemble.workingItemsPerMinute(10)).isEqualTo(28);
}
diff --git a/exercises/concept/elons-toy-car/.docs/hints.md b/exercises/concept/elons-toy-car/.docs/hints.md
deleted file mode 100644
index 64bebc6a3..000000000
--- a/exercises/concept/elons-toy-car/.docs/hints.md
+++ /dev/null
@@ -1,34 +0,0 @@
-# Hints
-
-## General
-
-## 1. Buy a brand-new remote controlled car
-
-- [This page shows how to create a new instance of a class][creating-objects].
-
-## 2. Display the distance driven
-
-- Keep track of the distance driven in a [field][fields].
-- Consider what visibility to use for the field (does it need to be used outside the class?).
-
-## 3. Display the battery percentage
-
-- Keep track of the distance driven in a [field][fields].
-- Initialize the field to a specific value to correspond to the initial battery charge.
-- Consider what visibility to use for the field (does it need to be used outside the class?).
-
-## 4. Update the number of meters driven when driving
-
-- Update the field representing the distance driven.
-
-## 5. Update the battery percentage when driving
-
-- Update the field representing the battery percentage driven.
-
-## 6. Prevent driving when the battery is drained
-
-- Add a conditional to only update the distance and battery if the battery is not already drained.
-- Add a conditional to display the empty battery message if the battery is drained.
-
-[creating-objects]: https://docs.oracle.com/javase/tutorial/java/javaOO/objectcreation.html
-[fields]: https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html
diff --git a/exercises/concept/elons-toy-car/.docs/instructions.md b/exercises/concept/elons-toy-car/.docs/instructions.md
deleted file mode 100644
index a24720618..000000000
--- a/exercises/concept/elons-toy-car/.docs/instructions.md
+++ /dev/null
@@ -1,83 +0,0 @@
-# Instructions
-
-In this exercise you'll be playing around with a remote controlled car, which you've finally saved enough money for to buy.
-
-Cars start with full (100%) batteries. Each time you drive the car using the remote control, it covers 20 meters and drains one percent of the battery.
-
-The remote controlled car has a fancy LED display that shows two bits of information:
-
-- The total distance it has driven, displayed as: `"Driven meters"`.
-- The remaining battery charge, displayed as: `"Battery at %"`.
-
-If the battery is at 0%, you can't drive the car anymore and the battery display will show `"Battery empty"`.
-
-You have six tasks, each of which will work with remote controlled car instances.
-
-## 1. Buy a brand-new remote controlled car
-
-Implement the (_static_) `ElonsToyCar.buy()` method to return a brand-new remote controlled car instance:
-
-```java
-ElonsToyCar car = ElonsToyCar.buy();
-```
-
-## 2. Display the distance driven
-
-Implement the `ElonsToyCar.distanceDisplay()` method to return the distance as displayed on the LED display:
-
-```java
-ElonsToyCar car = ElonsToyCar.buy();
-car.distanceDisplay();
-// => "Driven 0 meters"
-```
-
-## 3. Display the battery percentage
-
-Implement the `ElonsToyCar.batteryDisplay()` method to return the battery percentage as displayed on the LED display:
-
-```java
-ElonsToyCar car = ElonsToyCar.buy();
-car.batteryDisplay();
-// => "Battery at 100%"
-```
-
-## 4. Update the number of meters driven when driving
-
-Implement the `ElonsToyCar.drive()` method that updates the number of meters driven:
-
-```java
-ElonsToyCar car = ElonsToyCar.buy();
-car.drive();
-car.drive();
-car.distanceDisplay();
-// => "Driven 40 meters"
-```
-
-## 5. Update the battery percentage when driving
-
-Update the `ElonsToyCar.drive()` method to update the battery percentage:
-
-```java
-ElonsToyCar car = ElonsToyCar.buy();
-car.drive();
-car.drive();
-car.batteryDisplay();
-// => "Battery at 98%"
-```
-
-## 6. Prevent driving when the battery is drained
-
-Update the `ElonsToyCar.drive()` method to not increase the distance driven nor decrease the battery percentage when the battery is drained (at 0%):
-
-```java
-ElonsToyCar car = ElonsToyCar.buy();
-
-// Drain the battery
-// ...
-
-car.distanceDisplay();
-// => "Driven 2000 meters"
-
-car.batteryDisplay();
-// => "Battery empty"
-```
diff --git a/exercises/concept/elons-toy-car/.docs/introduction.md b/exercises/concept/elons-toy-car/.docs/introduction.md
deleted file mode 100644
index 260701451..000000000
--- a/exercises/concept/elons-toy-car/.docs/introduction.md
+++ /dev/null
@@ -1,64 +0,0 @@
-# Introduction
-
-The primary object-oriented construct in Java is the _class_, which is a combination of data (_fields_) and behavior (_methods_). The fields and methods of a class are known as its _members_.
-
-Access to members can be controlled through access modifiers, the two most common ones being:
-
-- `public`: the member can be accessed by any code (no restrictions).
-- `private`: the member can only be accessed by code in the same class.
-
-You can think of a class as a template for creating instances of that class. To create an instance of a class (also known as an _object_), the `new` keyword is used:
-
-```java
-class Car {
-}
-
-// Create two car instances
-Car myCar = new Car();
-Car yourCar = new Car();
-```
-
-Fields have a type and a name (defined in camelCase) and can be defined anywhere in a class (by convention cased in PascalCase):
-
-```java
-class Car {
- // Accessible by anyone
- public int weight;
-
- // Only accessible by code in this class
- private String color;
-}
-```
-
-One can optionally assign an initial value to a field. If a field does _not_ specify an initial value, it wll be set to its type's default value. An instance's field values can be accessed and updated using dot-notation.
-
-```java
-class Car {
- // Will be set to specified value
- public int weight = 2500;
-
- // Will be set to default value (0)
- public int year;
-}
-
-Car newCar = new Car();
-newCar.weight; // => 2500
-newCar.year; // => 0
-
-// Update value of the field
-newCar.year = 2018;
-```
-
-Private fields are usually updated as a side effect of calling a method. Such methods usually don't return any value, in which case the return type should be `void`:
-
-```java
-class CarImporter {
- private int carsImported;
-
- public void ImportCars(int numberOfCars)
- {
- // Update private field from public method
- carsImported = carsImported + numberOfCars;
- }
-}
-```
diff --git a/exercises/concept/elons-toy-car/.meta/config.json b/exercises/concept/elons-toy-car/.meta/config.json
deleted file mode 100644
index 03ace55cb..000000000
--- a/exercises/concept/elons-toy-car/.meta/config.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "blurb": "Learn about classes by working on a remote controlled car.",
- "icon": "elons-toys",
- "contributors": [
- "mirkoperillo"
- ],
- "files": {
- "solution": [
- "src/main/java/ElonsToyCar.java"
- ],
- "test": [
- "src/test/java/ElonsToyCarTest.java"
- ],
- "exemplar": [
- ".meta/src/reference/java/ElonsToyCar.java"
- ]
- },
- "authors": [
- "mikedamay"
- ]
-}
diff --git a/exercises/concept/elons-toy-car/.meta/design.md b/exercises/concept/elons-toy-car/.meta/design.md
deleted file mode 100644
index f99cef1f7..000000000
--- a/exercises/concept/elons-toy-car/.meta/design.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Design
-
-## Learning objectives
-
-- Know what classes are.
-- Know what encapsulation is.
-- Know what fields are.
-- Know how to create an object.
-- Know how to update state through methods.
-- Know about the `void` type.
-
-## Out of scope
-
-- Reference equality.
-- Constructors.
-- Interfaces.
-- Inheritance.
-- Finalizers.
-- Method overloading.
-
-## Concepts
-
-- `classes`: know what classes are; know what encapsulation is; know what fields are; know how to create an object; know how to update state through methods; know about the `void` type.
-
-## Prerequisites
-
-- `basics`: know how to define a basic class with basic methods.
-- `strings`: know how to do basic string interpolation.
-- `numbers`: know how to compare numbers.
-- `conditionals`: know how to do conditional logic.
diff --git a/exercises/concept/elons-toy-car/.meta/src/reference/java/ElonsToyCar.java b/exercises/concept/elons-toy-car/.meta/src/reference/java/ElonsToyCar.java
deleted file mode 100644
index 683e4bb32..000000000
--- a/exercises/concept/elons-toy-car/.meta/src/reference/java/ElonsToyCar.java
+++ /dev/null
@@ -1,27 +0,0 @@
-class ElonsToyCar {
- private int batteryPercentage = 100;
- private int distanceDrivenInMeters = 0;
-
- public void drive() {
- if (batteryPercentage > 0) {
- batteryPercentage--;
- distanceDrivenInMeters += 20;
- }
- }
-
- public String distanceDisplay() {
- return "Driven " + distanceDrivenInMeters + " meters";
- }
-
- public String batteryDisplay() {
- if (batteryPercentage == 0) {
- return "Battery empty";
- }
-
- return "Battery at " + batteryPercentage + "%";
- }
-
- public static ElonsToyCar buy() {
- return new ElonsToyCar();
- }
-}
diff --git a/exercises/concept/elons-toy-car/build.gradle b/exercises/concept/elons-toy-car/build.gradle
deleted file mode 100644
index 8bd005d42..000000000
--- a/exercises/concept/elons-toy-car/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
-
-repositories {
- mavenCentral()
-}
-
-dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
-}
-
-test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
-}
diff --git a/exercises/concept/elons-toy-car/src/main/java/ElonsToyCar.java b/exercises/concept/elons-toy-car/src/main/java/ElonsToyCar.java
deleted file mode 100644
index a38dff1cb..000000000
--- a/exercises/concept/elons-toy-car/src/main/java/ElonsToyCar.java
+++ /dev/null
@@ -1,17 +0,0 @@
-public class ElonsToyCar {
- public static ElonsToyCar buy() {
- throw new UnsupportedOperationException("Please implement the (static) RemoteControlCar.buy() method");
- }
-
- public String distanceDisplay() {
- throw new UnsupportedOperationException("Please implement the (static) RemoteControlCar.distanceDisplay() method");
- }
-
- public String batteryDisplay() {
- throw new UnsupportedOperationException("Please implement the (static) RemoteControlCar.batteryDisplay() method");
- }
-
- public void drive() {
- throw new UnsupportedOperationException("Please implement the (static) RemoteControlCar.drive() method");
- }
-}
diff --git a/exercises/concept/elons-toy-car/src/test/java/ElonsToyCarTest.java b/exercises/concept/elons-toy-car/src/test/java/ElonsToyCarTest.java
deleted file mode 100644
index 349a23f61..000000000
--- a/exercises/concept/elons-toy-car/src/test/java/ElonsToyCarTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.*;
-
-public class ElonsToyCarTest {
- @Test
- public void buy_new_car_returns_instance() {
- ElonsToyCar car = ElonsToyCar.buy();
- assertThat(car).isNotNull();
- }
-
- @Test
- public void buy_new_car_returns_new_car_each_time() {
- ElonsToyCar car1 = ElonsToyCar.buy();
- ElonsToyCar car2 = ElonsToyCar.buy();
- assertThat(car1).isNotEqualTo(car2);
- }
-
- @Test
- public void new_car_distance_display() {
- ElonsToyCar car = new ElonsToyCar();
- assertThat(car.distanceDisplay()).isEqualTo("Driven 0 meters");
- }
-
- @Test
- public void new_car_battery_display() {
- ElonsToyCar car = new ElonsToyCar();
- assertThat(car.batteryDisplay()).isEqualTo("Battery at 100%");
- }
-
- @Test
- public void distance_display_after_driving_once() {
- ElonsToyCar car = new ElonsToyCar();
- car.drive();
- assertThat(car.distanceDisplay()).isEqualTo("Driven 20 meters");
- }
-
- @Test
- public void distance_display_after_driving_multiple_times() {
- ElonsToyCar car = new ElonsToyCar();
-
- for (int i = 0; i < 17; i++) {
- car.drive();
- }
-
- assertThat(car.distanceDisplay()).isEqualTo("Driven 340 meters");
- }
-
- @Test
- public void battery_display_after_driving_once() {
- ElonsToyCar car = new ElonsToyCar();
- car.drive();
-
- assertThat(car.batteryDisplay()).isEqualTo("Battery at 99%");
- }
-
- @Test
- public void battery_display_after_driving_multiple_times() {
- ElonsToyCar car = new ElonsToyCar();
-
- for (int i = 0; i < 23; i++) {
- car.drive();
- }
-
- assertThat(car.batteryDisplay()).isEqualTo("Battery at 77%");
- }
-
- @Test
- public void battery_display_when_battery_empty() {
- ElonsToyCar car = new ElonsToyCar();
-
- // Drain the battery
- for (int i = 0; i < 100; i++) {
- car.drive();
- }
-
- // Attempt to drive one more time (should not work)
- car.drive();
-
- assertThat(car.batteryDisplay()).isEqualTo("Battery empty");
- }
-
- @Test
- public void distance_display_when_battery_empty() {
- ElonsToyCar car = new ElonsToyCar();
-
- // Drain the battery
- for (int i = 0; i < 100; i++) {
- car.drive();
- }
-
- // Attempt to drive one more time (should not work)
- car.drive();
-
- assertThat(car.distanceDisplay()).isEqualTo("Driven 2000 meters");
- }
-}
diff --git a/exercises/concept/football-match-reports/.docs/instructions.md b/exercises/concept/football-match-reports/.docs/instructions.md
index 264dd94a8..2c668b663 100644
--- a/exercises/concept/football-match-reports/.docs/instructions.md
+++ b/exercises/concept/football-match-reports/.docs/instructions.md
@@ -1,6 +1,7 @@
# Instructions
-You are developing a system to help the staff of a football/soccer club's web site report on matches. Data is received from a variety of sources and piped into a single stream after being cleaned up.
+You are developing a system to help the staff of a football/soccer club's website report on matches.
+Data is received from various sources and piped into a single stream after being cleaned up.
## 1. Output descriptions of the players based on their shirt number
@@ -8,7 +9,7 @@ The team only ever plays a 4-3-3 formation and has never agreed with the 1965 ch
The player descriptions are as follows:
-```
+```text
1 -> "goalie"
2 -> "left back"
3 & 4 "center back"
@@ -26,11 +27,11 @@ FootballMatchReports.onField(10);
// => "striker"
```
-## 2. Raise an alert if an unknown shirt number is encountered
+## 2. Output "invalid" if the shirt number is not part of the official list
-Modify the `FootballMatchReports.onField()` method to throw an `IllegalArgumentException` when a shirt number outside the range 1-11 is processed.
+Modify the `FootballMatchReports.onField()` method to return 'invalid' when a shirt number outside the range 1-11 is processed.
```java
FootballMatchReports.onField(13);
-// => Throw IllegalArgumentException
+// => "invalid"
```
diff --git a/exercises/concept/football-match-reports/.docs/introduction.md b/exercises/concept/football-match-reports/.docs/introduction.md
index 8998611f4..ff20beb45 100644
--- a/exercises/concept/football-match-reports/.docs/introduction.md
+++ b/exercises/concept/football-match-reports/.docs/introduction.md
@@ -1,15 +1,21 @@
# Introduction
-Like an _if/else_ statement, a `switch` statement allow you to change the flow of the program by conditionally executing code. The difference is that a `switch` statement can only compare the value of a primitive or string expression against pre-defined constant values.
+## Switch Statements
-Some keywords are usefull when using a switch statement.
+Like an _if/else_ statement, a `switch` statement allows you to change the flow of the program by conditionally executing code.
+The difference is that a `switch` statement can only compare the value of a primitive or string expression against pre-defined constant values.
-- `switch` : this keyword allow you to declare the structure of the switch. It his follow by the expression or the variable that will make the result change.
-- `case` : you will use this one to declare the differents possibilties for the result.
-- `break` : the `break` keyword is very usefull in order to stop the execution of the switch at the end of the wanted flow. If you forget it, the program will continue and may lead to unexpected results.
-- `default` : as it's name says use it as a default result when no other case matchs your expression's result.
+Some keywords are useful when using a switch statement.
-At their simplest they test a primitive or string expression and make a decision based on its value. For example:
+- `switch`: this keyword allows you to declare the structure of the switch.
+ It is followed by the expression or the variable that will change the result.
+- `case`: you will use this keyword to declare the different possibilities for the result.
+- `break`: the `break` keyword is very useful in order to stop the execution of the switch at the end of the wanted flow.
+ If you forget it, the program will continue and may lead to unexpected results.
+- `default`: as its name says, use it as a default result when no other case matches your expression's result.
+
+At their simplest they test a primitive or string expression and make a decision based on its value.
+For example:
```java
String direction = getDirection();
@@ -21,7 +27,7 @@ switch (direction) {
goRight();
break;
default:
- //otherwise
+ // otherwise
markTime();
break;
}
diff --git a/exercises/concept/football-match-reports/.docs/introduction.md.tpl b/exercises/concept/football-match-reports/.docs/introduction.md.tpl
new file mode 100644
index 000000000..2af34c429
--- /dev/null
+++ b/exercises/concept/football-match-reports/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:switch-statement}
diff --git a/exercises/concept/football-match-reports/.meta/config.json b/exercises/concept/football-match-reports/.meta/config.json
index 3282ecc17..74d3894b7 100644
--- a/exercises/concept/football-match-reports/.meta/config.json
+++ b/exercises/concept/football-match-reports/.meta/config.json
@@ -1,7 +1,9 @@
{
- "blurb": "Learn about switches by developing a system to report on soccer matches.",
"authors": [
- "Azumix"
+ "Azumix"
+ ],
+ "contributors": [
+ "AlvesJorge"
],
"files": {
"solution": [
@@ -12,9 +14,13 @@
],
"exemplar": [
".meta/src/reference/java/FootballMatchReports.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"csharp/football-match-reports"
- ]
+ ],
+ "blurb": "Learn about switches by developing a system to report on soccer matches."
}
diff --git a/exercises/concept/football-match-reports/.meta/design.md b/exercises/concept/football-match-reports/.meta/design.md
index f2c12c63c..a4e3c279c 100644
--- a/exercises/concept/football-match-reports/.meta/design.md
+++ b/exercises/concept/football-match-reports/.meta/design.md
@@ -1,2 +1,50 @@
# Design
+## Learning objectives
+
+- Know the existence of the `Switch` statement.
+- Know how to use the switch statement.
+- Recognize the keywords `switch`, `case`, `break` and `default`.
+
+## Out of scope
+
+- Nested switch statements
+- Advanced switch statements features like using object as case values.
+
+## Concepts
+
+- `switch`: know the existence of the `Switch` statement, how to use it and how to apply the basic keywords.
+
+## Prerequisites
+
+This exercise's prerequisites Concepts are:
+
+- `classes`: know how to work with classes.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `essential`: If the student resolved the exercise without using `switch`, instruct them to do so.
+- `actionable`: If the solution returns the same value in different cases, instruct them that this could be simplified.
+
+ ```java
+ switch(shirtNum) {
+ case 6, 7, 8:
+ return "midfielder";
+ }
+ ```
+
+- `actionable`: If the student does not directly return the answer from the case in the switch statement, instruct them to do so.
+- `informative`: If the solution is returning the answer inside the cases, inform the student that it can be simplified by using a switch expression:
+
+ ```java
+ return switch(shirtNum) {
+ case 6, 7, 8 -> "midfielder";
+ }
+ ```
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/football-match-reports/.meta/src/reference/java/FootballMatchReports.java b/exercises/concept/football-match-reports/.meta/src/reference/java/FootballMatchReports.java
index d18f55ac6..c99abc7b0 100644
--- a/exercises/concept/football-match-reports/.meta/src/reference/java/FootballMatchReports.java
+++ b/exercises/concept/football-match-reports/.meta/src/reference/java/FootballMatchReports.java
@@ -30,7 +30,7 @@ public static String onField(int shirtNum) {
playerDescription = "striker";
break;
default:
- throw new IllegalArgumentException();
+ playerDescription = "invalid";
}
return playerDescription;
}
diff --git a/exercises/concept/football-match-reports/build.gradle b/exercises/concept/football-match-reports/build.gradle
index 0fc6fc19d..d28f35dee 100644
--- a/exercises/concept/football-match-reports/build.gradle
+++ b/exercises/concept/football-match-reports/build.gradle
@@ -1,22 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/football-match-reports/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/football-match-reports/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/football-match-reports/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/football-match-reports/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/football-match-reports/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/football-match-reports/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/football-match-reports/gradlew b/exercises/concept/football-match-reports/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/football-match-reports/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/football-match-reports/gradlew.bat b/exercises/concept/football-match-reports/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/football-match-reports/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/football-match-reports/src/test/java/FootballMatchReportsTest.java b/exercises/concept/football-match-reports/src/test/java/FootballMatchReportsTest.java
index 54f0c2ea7..82024e6f5 100644
--- a/exercises/concept/football-match-reports/src/test/java/FootballMatchReportsTest.java
+++ b/exercises/concept/football-match-reports/src/test/java/FootballMatchReportsTest.java
@@ -1,62 +1,81 @@
-import org.junit.Test;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
public class FootballMatchReportsTest {
@Test
- public void test_goal() {
+ @Tag("task:1")
+ @DisplayName("The onField method returns the correct description of player with shirt number 1")
+ public void testGoal() {
assertThat(FootballMatchReports.onField(1)).isEqualTo("goalie");
}
@Test
- public void test_left_back() {
+ @Tag("task:1")
+ @DisplayName("The onField method returns the correct description of player with shirt number 2")
+ public void testLeftBack() {
assertThat(FootballMatchReports.onField(2)).isEqualTo("left back");
}
@Test
- public void test_right_back() {
+ @Tag("task:1")
+ @DisplayName("The onField method returns the correct description of player with shirt number 5")
+ public void testRightBack() {
assertThat(FootballMatchReports.onField(5)).isEqualTo("right back");
}
@Test
- public void test_center_back() {
+ @Tag("task:1")
+ @DisplayName("The onField method returns the correct description of players with shirt numbers 3 and 4")
+ public void testCenterBack() {
assertThat(FootballMatchReports.onField(3)).isEqualTo("center back");
assertThat(FootballMatchReports.onField(4)).isEqualTo("center back");
}
@Test
- public void test_midfielder() {
+ @Tag("task:1")
+ @DisplayName("The onField method returns the correct description of players with shirt numbers 6, 7 and 8")
+ public void testMidfielder() {
assertThat(FootballMatchReports.onField(6)).isEqualTo("midfielder");
assertThat(FootballMatchReports.onField(7)).isEqualTo("midfielder");
assertThat(FootballMatchReports.onField(8)).isEqualTo("midfielder");
}
@Test
- public void test_left_wing() {
+ @Tag("task:1")
+ @DisplayName("The onField method returns the correct description of player with shirt number 9")
+ public void testLeftWing() {
assertThat(FootballMatchReports.onField(9)).isEqualTo("left wing");
}
@Test
- public void test_striker() {
+ @Tag("task:1")
+ @DisplayName("The onField method returns the correct description of player with shirt number 10")
+ public void testStriker() {
assertThat(FootballMatchReports.onField(10)).isEqualTo("striker");
}
@Test
- public void test_right_wing() {
+ @Tag("task:1")
+ @DisplayName("The onField method returns the correct description of player with shirt number 11")
+ public void testRightWing() {
assertThat(FootballMatchReports.onField(11)).isEqualTo("right wing");
}
@Test
- public void test_exception() {
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> FootballMatchReports.onField(13));
+ @Tag("task:2")
+ @DisplayName("The onField method returns 'invalid' for invalid shirt number")
+ public void testException() {
+ assertThat(FootballMatchReports.onField(13)).isEqualTo("invalid");
}
@Test
- public void test_exception_negative_number() {
- assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> FootballMatchReports.onField(-1));
+ @Tag("task:2")
+ @DisplayName("The onField method returns 'invalid' for negative shirt number")
+ public void testExceptionNegativeNumber() {
+ assertThat(FootballMatchReports.onField(-1)).isEqualTo("invalid");
}
}
diff --git a/exercises/concept/gotta-snatch-em-all/.docs/hints.md b/exercises/concept/gotta-snatch-em-all/.docs/hints.md
new file mode 100644
index 000000000..48627ff3b
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/.docs/hints.md
@@ -0,0 +1,32 @@
+# Hints
+
+## General
+
+- The [`Set` API documentation][set-docs] contains a list of methods available on the `Set` interface.
+
+## 1. Start a collection
+
+- The [`HashSet` class][hashset-docs] has a constructor that takes a `Collection`.
+
+## 2. Grow the collection
+
+- Check out the signature of the [`add` method][set-add-docs] defined on the `Set` interface.
+
+## 3. Start trading
+
+- You can create a copy of a `Set` by wrapping it in a `new HashSet<>()`.
+- The [`Set` interface][set-docs] has a method that removes all items contained in another `Collection`.
+
+## 4. Identify common cards
+
+- You can create a copy of a `Set` by wrapping it in a `new HashSet<>()`.
+- The [`Set` interface][set-docs] has a method that retains all items contained in another `Collection`.
+
+## 5. All of the cards
+
+- You can create a copy of a `Set` by wrapping it in a `new HashSet<>()`.
+- The [`Set` interface][set-docs] has a method that adds all items contained in another `Collection`.
+
+[hashset-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html
+[set-add-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#add(E)
+[set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html
diff --git a/exercises/concept/gotta-snatch-em-all/.docs/instructions.md b/exercises/concept/gotta-snatch-em-all/.docs/instructions.md
new file mode 100644
index 000000000..f6e87ead7
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/.docs/instructions.md
@@ -0,0 +1,80 @@
+# Instructions
+
+Your nostalgia for Blorkemon™️ cards is showing no sign of slowing down, you even started collecting them again, and you
+are getting your friends to join you.
+
+In this exercise, you will use the `Set` interface to help you manage your collection, since duplicate cards are not
+important when your goal is to get all existing cards.
+
+## 1. Start a collection
+
+You just found your old stash of Blorkemon™️ cards!
+The stash contains a bunch of duplicate cards, so it's time to start a new collection by removing the duplicates.
+
+You really want your friends to join your Blorkemon™️ madness, and the best way is to kickstart their collection by
+giving them one card.
+
+Implement the `newCollection` method, which transforms a list of cards into a `Set` representing your new collection.
+
+```java
+GottaSnatchEmAll.newCollection(List.of("Newthree", "Newthree", "Newthree"));
+// => {"Newthree"}
+```
+
+## 2. Grow the collection
+
+Once you have a collection, it takes a life of its own and must grow.
+
+Implement the `addCard` method, which takes a new card and your current set of collected cards.
+The method should add the new card to the collection if it isn't already present, and should return a `boolean`
+indicating whether the collection was updated.
+
+```java
+Set collection = GottaSnatchEmAll.newCollection("Newthree");
+GottaSnatchEmAll.addCard("Scientuna",collection);
+// => true
+
+collection.contains("Scientuna");
+// => true
+```
+
+## 3. Start trading
+
+You really want your friends to join your Blorkemon™️ madness, so it's time to start trading!
+
+When trading with friends not every trade is worth doing, or can be done at all.
+You should only trade if both you and your friend have a card the other does not have.
+
+Implement the `canTrade` method, that takes your current collection and the collection of one of your friends.
+It should return a `boolean` indicating whether a trade is possible, following the rules above.
+
+```java
+Set myCollection = Set.of("Newthree");
+Set theirCollection = Set.of("Scientuna");
+GottaSnatchEmAll.canTrade(myCollection, theirCollection);
+// => true
+```
+
+## 4. Identify common cards
+
+You and your Blorkemon™️ enthusiast friends gather and wonder which cards are the most common.
+
+Implement the `commonCards` method, which takes a list of collections and returns a collection of cards that all collections
+have.
+
+```java
+GottaSnatchEmAll.commonCards(List.of(Set.of("Scientuna"), Set.of("Newthree","Scientuna")));
+// => {"Scientuna"}
+```
+
+## 5. All of the cards
+
+Do you and your friends collectively own all of the Blorkemon™️ cards?
+
+Implement the `allCards` method, which takes a list of collections and returns a collection of all different cards in
+all the collections combined.
+
+```java
+GottaSnatchEmAll.allCards(List.of(Set.of("Scientuna"), Set.of("Newthree","Scientuna")));
+// => {"Newthree", "Scientuna"}
+```
diff --git a/exercises/concept/gotta-snatch-em-all/.docs/introduction.md b/exercises/concept/gotta-snatch-em-all/.docs/introduction.md
new file mode 100644
index 000000000..921174753
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/.docs/introduction.md
@@ -0,0 +1,54 @@
+# Introduction
+
+## Sets
+
+A [`Set`][set-docs] is an unordered collection that (unlike `List`) is guaranteed to not contain any duplicate values.
+
+The generic type parameter of the `Set` interface denotes the type of the elements contained in the `Set`:
+
+```java
+Set ints = Set.of(1, 2, 3);
+Set strings = Set.of("alpha", "beta", "gamma");
+Set mixed = Set.of(1, false, "foo");
+```
+
+Note that the `Set.of()` method creates an [_unmodifiable_][unmodifiable-set-docs] `Set` instance.
+Trying to call methods like `add` and `remove` on this instance will result in an exception at run-time.
+
+To create a modifiable `Set`, you need to instantiate a class that implements the `Set` interface.
+The most-used built-in class that implements this interface is the [`HashSet`][hashset-docs] class.
+
+```java
+Set ints = new HashSet<>();
+```
+
+The `Set` interface extends from the [`Collection`][collection-docs] and [`Iterable`][iterable-docs] interfaces, and therefore shares a lot of methods with other types of collections.
+A notable difference to the `Collection` interface, however, is that methods like `add` and `remove` return a `boolean` (instead of `void`) which indicates whether the item was contained in the set when that method was called:
+
+```java
+Set set = new HashSet<>();
+set.add(1);
+// => true
+set.add(2);
+// => true
+set.add(1);
+// => false
+set.size();
+// => 2
+set.contains(1);
+// => true
+set.contains(3);
+// => false
+set.remove(3);
+// => false
+set.remove(2);
+// => true
+set.size();
+// => 1
+```
+
+[collection-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Collection.html
+[hashset-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashSet.html
+[iterable-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Iterable.html
+[set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html
+[unmodifiable-set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#unmodifiable
diff --git a/exercises/concept/gotta-snatch-em-all/.docs/introduction.md.tpl b/exercises/concept/gotta-snatch-em-all/.docs/introduction.md.tpl
new file mode 100644
index 000000000..b92fafb7a
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:sets}
diff --git a/exercises/concept/gotta-snatch-em-all/.meta/config.json b/exercises/concept/gotta-snatch-em-all/.meta/config.json
new file mode 100644
index 000000000..e5ff03150
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/.meta/config.json
@@ -0,0 +1,27 @@
+{
+ "authors": [
+ "sanderploegsma"
+ ],
+ "contributors": [
+ "kahgoh"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/GottaSnatchEmAll.java"
+ ],
+ "test": [
+ "src/test/java/GottaSnatchEmAllTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/GottaSnatchEmAll.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "forked_from": [
+ "elm/gotta-snatch-em-all"
+ ],
+ "icon": "character-study",
+ "blurb": "Learn to use sets by playing a vintage trading card game"
+}
diff --git a/exercises/concept/gotta-snatch-em-all/.meta/design.md b/exercises/concept/gotta-snatch-em-all/.meta/design.md
new file mode 100644
index 000000000..7fb0c0312
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/.meta/design.md
@@ -0,0 +1,74 @@
+# Design
+
+## Learning objectives
+
+- Get to know the `Set` interface.
+- Know about the `HashSet` class.
+- Know how the `add` method works.
+- Know how to perform set operations like asymmetric difference, intersection and union.
+
+## Out of scope
+
+- More specific subtypes of `Set`, such as `OrderedSet` and `SequencedSet`.
+- More advanced classes implementing the `Set` interface, such as `TreeSet` and `LinkedHashSet`.
+
+## Concepts
+
+- `sets`
+
+## Prerequisites
+
+This exercise's prerequisites Concepts are:
+
+- `lists`
+- `generic-types`
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer].
+If the solution does not receive any of the listed feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+### Task 1
+
+The idea behind this task is that the student knows how to convert from a `List` to a `Set`.
+Ideally we want the student to use the `HashSet(Collection)` constructor.
+If the solution instead creates a new empty set and manually `add` or `addAll` the items in the list, leave an `actionable` comment pointing to the `HashSet` constructor instead.
+
+### Task 2
+
+The solution should invoke and return the result of the `add` method.
+If the solution manually checks for the existence of the item using `contains` and/or an `if` statement, leave an `essential` comment pointing to the signature of the `add` method.
+
+### Task 3
+
+The goal of this task is to calculate the [relative complement][set-relative-complement] or difference of two sets using the `removeAll` method.
+If the solution manually loops through the contents of either set, leave an `essential` comment pointing to the `removeAll` method.
+
+Since the sets passed to the `canTrade` method are mutable on purpose, the solution may call `removeAll` on passed sets directly.
+If this is the case, leave an `informative` comment explaining that it's better not to mutate the sets directly.
+Instead, they should create a copy by wrapping each set in a `new HashSet()`.
+
+The solution may use Java Streams to find the difference between the sets.
+This is also an acceptable solution and requires no feedback.
+
+### Task 4
+
+The goal of this task is to find the [intersection][set-intersection] of a list of sets using the `retainAll` method.
+If the solution manually loops through the contents of any set, leave an `essential` comment pointing to the `retainAll` method.
+
+The solution may use Java Streams to find the intersection of the sets.
+This is also an acceptable solution and requires no feedback.
+
+### Task 5
+
+The goal of this task is to find the [union][set-union] of a list of sets using the `addAll` method.
+If the solution manually loops through the contents of any set, leave an `essential` comment pointing to the `addAll` method.
+
+The solution may use Java Streams to find the union of the sets.
+This is also an acceptable solution and requires no feedback.
+
+[analyzer]: https://github.com/exercism/java-analyzer
+[set-relative-complement]: https://www.baeldung.com/java-set-operations#4-the-relative-complement-of-sets
+[set-intersection]: https://www.baeldung.com/java-set-operations#2-the-intersection-of-sets
+[set-union]: https://www.baeldung.com/java-set-operations#3-the-union-of-sets
diff --git a/exercises/concept/gotta-snatch-em-all/.meta/src/reference/java/GottaSnatchEmAll.java b/exercises/concept/gotta-snatch-em-all/.meta/src/reference/java/GottaSnatchEmAll.java
new file mode 100644
index 000000000..cf6a3b428
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/.meta/src/reference/java/GottaSnatchEmAll.java
@@ -0,0 +1,42 @@
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class GottaSnatchEmAll {
+
+ static Set newCollection(List cards) {
+ return new HashSet<>(cards);
+ }
+
+ static boolean addCard(String card, Set collection) {
+ return collection.add(card);
+ }
+
+ static boolean canTrade(Set myCollection, Set theirCollection) {
+ Set myUniqueCards = new HashSet<>(myCollection);
+ Set theirUniqueCards = new HashSet<>(theirCollection);
+ myUniqueCards.removeAll(theirCollection);
+ theirUniqueCards.removeAll(myCollection);
+ return !myUniqueCards.isEmpty() && !theirUniqueCards.isEmpty();
+ }
+
+ static Set commonCards(List> collections) {
+ if (collections.isEmpty()) {
+ return Set.of();
+ }
+
+ Set commonCards = new HashSet<>(collections.get(0));
+ for (int i = 1; i < collections.size(); i++) {
+ commonCards.retainAll(collections.get(i));
+ }
+ return commonCards;
+ }
+
+ static Set allCards(List> collections) {
+ Set allCards = new HashSet<>();
+ for (Set collection : collections) {
+ allCards.addAll(collection);
+ }
+ return allCards;
+ }
+}
diff --git a/exercises/concept/gotta-snatch-em-all/build.gradle b/exercises/concept/gotta-snatch-em-all/build.gradle
new file mode 100644
index 000000000..d28f35dee
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/concept/gotta-snatch-em-all/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/gotta-snatch-em-all/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/gotta-snatch-em-all/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/gotta-snatch-em-all/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/gotta-snatch-em-all/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/gotta-snatch-em-all/gradlew b/exercises/concept/gotta-snatch-em-all/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/gotta-snatch-em-all/gradlew.bat b/exercises/concept/gotta-snatch-em-all/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/gotta-snatch-em-all/src/main/java/GottaSnatchEmAll.java b/exercises/concept/gotta-snatch-em-all/src/main/java/GottaSnatchEmAll.java
new file mode 100644
index 000000000..56d2aa63f
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/src/main/java/GottaSnatchEmAll.java
@@ -0,0 +1,25 @@
+import java.util.List;
+import java.util.Set;
+
+class GottaSnatchEmAll {
+
+ static Set newCollection(List cards) {
+ throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.newCollection() method");
+ }
+
+ static boolean addCard(String card, Set collection) {
+ throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.addCard() method");
+ }
+
+ static boolean canTrade(Set myCollection, Set theirCollection) {
+ throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.canTrade() method");
+ }
+
+ static Set commonCards(List> collections) {
+ throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.commonCards() method");
+ }
+
+ static Set allCards(List> collections) {
+ throw new UnsupportedOperationException("Please implement the (static) GottaSnatchEmAll.allCards() method");
+ }
+}
diff --git a/exercises/concept/gotta-snatch-em-all/src/test/java/GottaSnatchEmAllTest.java b/exercises/concept/gotta-snatch-em-all/src/test/java/GottaSnatchEmAllTest.java
new file mode 100644
index 000000000..d2466e8a4
--- /dev/null
+++ b/exercises/concept/gotta-snatch-em-all/src/test/java/GottaSnatchEmAllTest.java
@@ -0,0 +1,215 @@
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class GottaSnatchEmAllTest {
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("newCollection returns an empty set when given an empty list")
+ void testNewCollectionEmptyList() {
+ assertThat(GottaSnatchEmAll.newCollection(List.of())).isEmpty();
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("newCollection returns a set with one card when given a list with one card")
+ void testNewCollectionSingletonList() {
+ List cards = List.of("Bleakachu");
+ Set expected = Set.of("Bleakachu");
+ assertThat(GottaSnatchEmAll.newCollection(cards)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("newCollection returns a set with one card when given a list with one repeated card")
+ void testNewCollectionListWithDuplicates() {
+ List cards = List.of("Bleakachu", "Bleakachu");
+ Set expected = Set.of("Bleakachu");
+ assertThat(GottaSnatchEmAll.newCollection(cards)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("newCollection returns a set with two cards when given a list with two unique cards")
+ void testNewCollectionListWithoutDuplicates() {
+ List cards = List.of("Bleakachu", "Newthree");
+ Set expected = Set.of("Bleakachu", "Newthree");
+ assertThat(GottaSnatchEmAll.newCollection(cards)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("addCard returns true when the collection does not yet contain the new card")
+ void testAddCardReturnsTrueWhenCardNotInCollection() {
+ Set collection = new HashSet<>();
+ assertThat(GottaSnatchEmAll.addCard("Veevee", collection)).isTrue();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("addCard returns false when the collection already contains the new card")
+ void testAddCardReturnsFalseWhenCardAlreadyInCollection() {
+ Set collection = new HashSet<>(Set.of("Veevee"));
+ assertThat(GottaSnatchEmAll.addCard("Veevee", collection)).isFalse();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("addCard adds the card to the collection when it is a new card")
+ void testAddCardShouldAddNewCardToCollection() {
+ Set collection = new HashSet<>();
+ Set expected = Set.of("Veevee");
+ GottaSnatchEmAll.addCard("Veevee", collection);
+ assertThat(collection).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("addCard doesn't add the card to the collection when it already contains the new card")
+ void testAddCardShouldNotAddExistingCardToCollection() {
+ Set collection = new HashSet<>(Set.of("Veevee"));
+ Set expected = Set.of("Veevee");
+ GottaSnatchEmAll.addCard("Veevee", collection);
+ assertThat(collection).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("canTrade returns false when both collections are empty")
+ void testCanTradeBothCollectionsEmpty() {
+ Set myCollection = new HashSet<>();
+ Set theirCollection = new HashSet<>();
+ assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("canTrade returns false when my collections is empty")
+ void testCanTradeMyCollectionsEmpty() {
+ Set myCollection = new HashSet<>();
+ Set theirCollection = new HashSet<>(Set.of("Bleakachu"));
+ assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("canTrade returns false when their collections is empty")
+ void testCanTradeTheirCollectionsEmpty() {
+ Set myCollection = new HashSet<>(Set.of("Bleakachu"));
+ Set theirCollection = new HashSet<>();
+ assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("canTrade returns false when both collections have the same cards")
+ void testCanTradeBothCollectionsHaveSameCards() {
+ Set myCollection = new HashSet<>(Set.of("Gyros", "Garilord"));
+ Set theirCollection = new HashSet<>(Set.of("Garilord", "Gyros"));
+ assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("canTrade returns true when both collections have unique cards")
+ void testCanTradeBothCollectionsHaveUniqueCards() {
+ Set myCollection = new HashSet<>(Set.of("Gyros"));
+ Set theirCollection = new HashSet<>(Set.of("Garilord"));
+ assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isTrue();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("canTrade returns true when both collections have at least one card the other doesn't have")
+ void testCanTradeBothCollectionsMixedCards() {
+ Set myCollection = new HashSet<>(Set.of("Gyros", "Garilord", "Bleakachu"));
+ Set theirCollection = new HashSet<>(Set.of("Garilord", "Veevee", "Gyros"));
+ assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isTrue();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("canTrade returns false when my collection is a non-empty subset of their collection")
+ void testCanTradeMyCollectionSubsetOfTheirCollection() {
+ Set myCollection = new HashSet<>(Set.of("Gyros", "Garilord"));
+ Set theirCollection = new HashSet<>(Set.of("Garilord", "Veevee", "Gyros"));
+ assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse();
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("canTrade returns false when their collection is a non-empty subset of my collection")
+ void testCanTradeTheirCollectionSubsetOfMyCollection() {
+ Set myCollection = new HashSet<>(Set.of("Garilord", "Veevee", "Gyros"));
+ Set theirCollection = new HashSet<>(Set.of("Gyros", "Garilord"));
+ assertThat(GottaSnatchEmAll.canTrade(myCollection, theirCollection)).isFalse();
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("commonCards returns an empty set when all cards are different")
+ void testCommonCardsAllCardsDifferent() {
+ List> collections = List.of(
+ Set.of("Veevee"),
+ Set.of("Bleakachu"),
+ Set.of("Wigglycream")
+ );
+ Set expected = Set.of();
+ assertThat(GottaSnatchEmAll.commonCards(collections)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("commonCards returns a set with all cards when given a single collection")
+ void testCommonCardsSingleCollection() {
+ List> collections = List.of(
+ Set.of("Veevee", "Wigglycream", "Mayofried")
+ );
+ Set expected = Set.of("Veevee", "Wigglycream", "Mayofried");
+ assertThat(GottaSnatchEmAll.commonCards(collections)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("commonCards returns a set with cards present in all given collections")
+ void testCommonCardsMultipleCollections() {
+ List> collections = List.of(
+ Set.of("Veevee", "Wigglycream", "Mayofried"),
+ Set.of("Gyros", "Wigglycream", "Shazam"),
+ Set.of("Cooltentbro", "Mayofried", "Wigglycream")
+ );
+ Set expected = Set.of("Wigglycream");
+ assertThat(GottaSnatchEmAll.commonCards(collections)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("allCards returns a set with all cards when given a single collection")
+ void testAllCardsSingleCollection() {
+ List> collections = List.of(
+ Set.of("Veevee", "Wigglycream", "Mayofried")
+ );
+ Set expected = Set.of("Veevee", "Wigglycream", "Mayofried");
+ assertThat(GottaSnatchEmAll.allCards(collections)).isEqualTo(expected);
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("allCards returns a set with all cards when given multiple collections")
+ void testAllCardsMultipleCollections() {
+ List> collections = List.of(
+ Set.of("Veevee", "Wigglycream", "Mayofried"),
+ Set.of("Gyros", "Wigglycream", "Shazam"),
+ Set.of("Cooltentbro", "Mayofried", "Wigglycream")
+ );
+ Set expected = Set.of("Cooltentbro", "Gyros", "Mayofried", "Shazam", "Veevee", "Wigglycream");
+ assertThat(GottaSnatchEmAll.allCards(collections)).isEqualTo(expected);
+ }
+}
diff --git a/exercises/concept/international-calling-connoisseur/.docs/hints.md b/exercises/concept/international-calling-connoisseur/.docs/hints.md
new file mode 100644
index 000000000..8bedc370b
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/.docs/hints.md
@@ -0,0 +1,39 @@
+# Hints
+
+## General
+
+- The [`Map` API documentation][map-docs] contains a list of methods available on the `Map` interface.
+
+## 1. Return the codes in a map
+
+- You will need to define a `Map` in [such a way][declaring-members] that you can use it in the class methods.
+
+## 2. Add entries to the dictionary
+
+- Maps have a [method][map-put-docs] to add and update the value for a key.
+
+## 3. Lookup a dialing code's country
+
+- Maps have a [method][map-get-docs] to get the key's value.
+
+## 4. Don't allow duplicates
+
+- There is a way to check if the map has a [key][map-contains-key-docs] or a [value][map-contains-value-docs].
+
+## 5. Find a country's dialing code
+
+- There is a [way][map-entry-set-docs] to get an iterable collection of entries in a map, which allows you to go through the key-value pairs.
+
+## 6. Update the country's dialing code
+
+- Do not forget about the country's previous dialing code will be in the map.
+- There is a [method][map-remove-docs] to remove an entry from the map.
+
+[declaring-members]: https://dev.java/learn/classes-objects/creating-classes/#declaring-members
+[map-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html
+[map-put-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#put(K,V)
+[map-get-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#get(java.lang.Object)
+[map-contains-key-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsKey(java.lang.Object)
+[map-contains-value-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsValue(java.lang.Object)
+[map-entry-set-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#entrySet()
+[map-remove-docs]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#remove(java.lang.Object)
diff --git a/exercises/concept/international-calling-connoisseur/.docs/instructions.md b/exercises/concept/international-calling-connoisseur/.docs/instructions.md
new file mode 100644
index 000000000..106844176
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/.docs/instructions.md
@@ -0,0 +1,95 @@
+# Instructions
+
+In this exercise you'll be writing code to manage a dictionary of international dialing codes using a `Map`.
+
+The dictionary allows looking up the name of a country (the map's value, as a `String`) by the international dialing code (the map's key, as an `Integer`),
+
+## 1. Return the codes in a map
+
+Implement the method `getCodes` that takes no parameters and returns a map of the dialing codes to country currently in the dictionary.
+
+```java
+DialingCodes dialingCodes = new DialingCodes();
+dialingCodes.getCodes();
+// => empty map
+```
+
+## 2. Add entries to the dictionary
+
+The dictionary needs to be populated.
+Implement the `setDialingCode` method that takes a dialing code and the corresponding country and adds the dialing code and country.
+If the dialing code is already in the map, update the map with the provided code and country.
+
+```java
+DialingCodes dialingCodes = new DialingCodes();
+dialingCodes.setDialingCode(679, "Unknown");
+// => { 679 => "Unknown" }
+
+dialingCodes.setDialingCode(679, "Fiji");
+// => { 679 => "Fiji" }
+```
+
+## 3. Lookup a dialing code's country
+
+Implement the `getCountry` method that takes a dialing code and returns the country name with the dialing code.
+
+```java
+DialingCodes dialingCodes = new DialingCodes();
+dialingCodes.setDialingCode(55, "Brazil");
+dialingCodes.getCountry(55);
+// => "Brazil"
+```
+
+## 4. Don't allow duplicates
+
+When adding a dialing code, care needs to be taken to prevent a code or country to be added twice.
+In situations where this happens, it can be assumed that the first entry is the right entry.
+Implement the `addNewDialingCode` method that adds an entry for the given dialing code and country.
+However, unlike `setDialingCode`, it does nothing if the dialing code or the country is already in the map.
+
+```java
+DialingCodes dialingCodes = new DialingCodes();
+dialingCodes.addNewDialingCode(32, "Belgium");
+dialingCodes.addNewDialingCode(379, "Vatican City");
+// => { 39 => "Italy", 379 => "Vatican City" }
+
+
+dialingCodes.addNewDialingCode(32, "Other");
+dialingCodes.addNewDialingCode(39, "Vatican City");
+// => { 32 => "Belgium", 379 => "Vatican City" }
+```
+
+## 5. Find a country's dialing code
+
+Its rare, but mistakes can be made.
+To correct the mistake, we will need to know what dialing code the country is currently mapped to.
+To find which dialing code needs to be corrected, implement the `findDialingCode` method that takes a country and returns the country's dialing code.
+Return `null` if the country is _not_ in the map.
+
+```java
+DialingCodes dialingCodes = new DialingCodes();
+dialingCodes.addDialingCode(44, "UK");
+dialingCodes.findDialingCode("UK");
+// => 44
+
+dialingCodes.findDialingCode("Unlisted");
+// => null
+```
+
+## 6. Update the country's dialing code
+
+Now that we know which dialing code needs to be corrected, we proceed to update the code.
+Implement the `updateCountryDialingCode` method that takes the country's new dialing code and the country's name and updates accordingly.
+Do nothing if the country is _not_ in the map (as this method is meant to only help correct mistakes).
+
+```java
+DialingCodes dialingCodes = new DialingCodes();
+dialingCodes.addDialingCode(88, "Japan");
+// => { 88 => "Japan" }
+
+dialingCodes.updateCountryDialingCode(81, "Japan");
+// => { 81 => "Japan" }
+
+dialingCodes.updateCountryDialingCode(32, "Mars");
+// => { 81 => "Japan"}
+```
diff --git a/exercises/concept/international-calling-connoisseur/.docs/introduction.md b/exercises/concept/international-calling-connoisseur/.docs/introduction.md
new file mode 100644
index 000000000..c407eea39
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/.docs/introduction.md
@@ -0,0 +1,74 @@
+# Introduction
+
+## Maps
+
+A **Map** is a data structure for storing key value pairs.
+It is similar to dictionaries in other programming languages.
+The [Map][map-javadoc] interface defines operations on a map.
+
+Java has a number of different Map implementations.
+[HashMap][hashmap-javadoc] is a commonly used one.
+
+```java
+// Make an instance
+Map fruitPrices = new HashMap<>();
+```
+
+Add entries to the map using [put][map-put-javadoc].
+
+```java
+fruitPrices.put("apple", 100);
+fruitPrices.put("pear", 80);
+// => { "apple" => 100, "pear" => 80 }
+```
+
+Only one value can be associated with each key.
+Calling `put` with the same key will update the key's value.
+
+```java
+fruitPrices.put("pear", 40);
+// => { "apple" => 100, "pear" => 40 }
+```
+
+Use [get][map-get-javadoc] to get the value for a key.
+
+```java
+fruitPrices.get("apple"); // => 100
+```
+
+Use [containsKey][map-containskey-javadoc] to see if the map contains a particular key.
+
+```java
+fruitPrices.containsKey("apple"); // => true
+fruitPrices.containsKey("orange"); // => false
+```
+
+Remove entries with [remove][map-remove-javadoc].
+
+```java
+fruitPrices.put("plum", 90); // Add plum to map
+fruitPrices.remove("plum"); // Removes plum from map
+```
+
+The [size][map-size-javadoc] method returns the number of entries.
+
+```java
+fruitPrices.size(); // Returns 2
+```
+
+You can use the [keySet][map-keyset-javadoc] or [values][map-values-javadoc] methods to obtain the keys or the values in a Map as a Set or collection respectively.
+
+```java
+fruitPrices.keySet(); // Returns "apple" and "pear" in a set
+fruitPrices.values(); // Returns 100 and 80, in a Collection
+```
+
+[map-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html
+[hashmap-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/HashMap.html
+[map-put-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#put(K,V)
+[map-get-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#get(java.lang.Object)
+[map-containskey-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#containsKey(java.lang.Object)
+[map-remove-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#remove(java.lang.Object)
+[map-size-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#size()
+[map-keyset-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#keySet()
+[map-values-javadoc]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Map.html#values()
diff --git a/exercises/concept/international-calling-connoisseur/.docs/introduction.md.tpl b/exercises/concept/international-calling-connoisseur/.docs/introduction.md.tpl
new file mode 100644
index 000000000..f1ea541d1
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:maps}
diff --git a/exercises/concept/international-calling-connoisseur/.meta/config.json b/exercises/concept/international-calling-connoisseur/.meta/config.json
new file mode 100644
index 000000000..e65e319f0
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/.meta/config.json
@@ -0,0 +1,20 @@
+{
+ "authors": [
+ "kahgoh"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/DialingCodes.java"
+ ],
+ "test": [
+ "src/test/java/DialingCodesTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/DialingCodes.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "blurb": "Learn about maps while managing international calling codes."
+}
diff --git a/exercises/concept/international-calling-connoisseur/.meta/design.md b/exercises/concept/international-calling-connoisseur/.meta/design.md
new file mode 100644
index 000000000..a173c4900
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/.meta/design.md
@@ -0,0 +1,38 @@
+# Design
+
+## Learning objectives
+
+- Know about the `Map` interface.
+- Know about `HashMap`.
+- Know how to put, remove and retrieve items from a `Map`.
+- Know how to find the keys of the map.
+
+## Out of scope
+
+- Map equality.
+- The importance of the key object's `hashCode` and `equals` implementation and how `HashMap` uses them.
+- More advanced or specific types of `Map`, such as `WeakHashMap` or `TreeMap`.
+- Handling concurrency.
+
+## Concepts
+
+- `map`
+
+## Prerequisites
+
+This exercise's prerequisites Concepts are:
+
+- `classes`
+- `foreach-loops`
+- `generic-types`
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the user directly returns the class field, recommend returning a copy to the student.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/international-calling-connoisseur/.meta/src/reference/java/DialingCodes.java b/exercises/concept/international-calling-connoisseur/.meta/src/reference/java/DialingCodes.java
new file mode 100644
index 000000000..d3ecb0a36
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/.meta/src/reference/java/DialingCodes.java
@@ -0,0 +1,43 @@
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class DialingCodes {
+
+ private final Map countryByDialingCode = new HashMap<>();
+
+ public Map getCodes() {
+ return Map.copyOf(countryByDialingCode);
+ }
+
+ public void setDialingCode(Integer code, String country) {
+ countryByDialingCode.put(code, country);
+ }
+
+ public String getCountry(Integer code) {
+ return countryByDialingCode.get(code);
+ }
+
+ public void addNewDialingCode(Integer code, String country) {
+ if (!countryByDialingCode.containsValue(country)) {
+ countryByDialingCode.putIfAbsent(code, country);
+ }
+ }
+
+ public Integer findDialingCode(String country) {
+ for (Map.Entry entry : countryByDialingCode.entrySet()) {
+ if (Objects.equals(entry.getValue(), country)) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+
+ public void updateCountryDialingCode(Integer code, String country) {
+ Integer existingCode = findDialingCode(country);
+ if (existingCode != null) {
+ countryByDialingCode.remove(existingCode);
+ countryByDialingCode.put(code, country);
+ }
+ }
+}
diff --git a/exercises/concept/international-calling-connoisseur/build.gradle b/exercises/concept/international-calling-connoisseur/build.gradle
new file mode 100644
index 000000000..d28f35dee
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/international-calling-connoisseur/gradlew b/exercises/concept/international-calling-connoisseur/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/international-calling-connoisseur/gradlew.bat b/exercises/concept/international-calling-connoisseur/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/international-calling-connoisseur/src/main/java/DialingCodes.java b/exercises/concept/international-calling-connoisseur/src/main/java/DialingCodes.java
new file mode 100644
index 000000000..0c40e1835
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/src/main/java/DialingCodes.java
@@ -0,0 +1,34 @@
+import java.util.Map;
+
+public class DialingCodes {
+
+ public Map getCodes() {
+ throw new UnsupportedOperationException(
+ "Delete this statement and write your own implementation.");
+ }
+
+ public void setDialingCode(Integer code, String country) {
+ throw new UnsupportedOperationException(
+ "Delete this statement and write your own implementation.");
+ }
+
+ public String getCountry(Integer code) {
+ throw new UnsupportedOperationException(
+ "Delete this statement and write your own implementation.");
+ }
+
+ public void addNewDialingCode(Integer code, String country) {
+ throw new UnsupportedOperationException(
+ "Delete this statement and write your own implementation.");
+ }
+
+ public Integer findDialingCode(String country) {
+ throw new UnsupportedOperationException(
+ "Delete this statement and write your own implementation.");
+ }
+
+ public void updateCountryDialingCode(Integer code, String country) {
+ throw new UnsupportedOperationException(
+ "Delete this statement and write your own implementation.");
+ }
+}
diff --git a/exercises/concept/international-calling-connoisseur/src/test/java/DialingCodesTest.java b/exercises/concept/international-calling-connoisseur/src/test/java/DialingCodesTest.java
new file mode 100644
index 000000000..e88e7379f
--- /dev/null
+++ b/exercises/concept/international-calling-connoisseur/src/test/java/DialingCodesTest.java
@@ -0,0 +1,141 @@
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+public class DialingCodesTest {
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("getCodes initially returns an empty map")
+ public void testGetCodesReturnsMap() {
+ DialingCodes dialingCodes = new DialingCodes();
+
+ assertThat(dialingCodes.getCodes()).isEmpty();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("setDialingCode adds new entry")
+ public void testSetDialingCodeAddsEntry() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.setDialingCode(679, "Fiji");
+
+ assertThat(dialingCodes.getCodes()).containsOnly(entry(679, "Fiji"));
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("setDialingCode updates existing entry")
+ public void testSetDialingCodeUpdatesEntry() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.setDialingCode(679, "Unknown");
+ dialingCodes.setDialingCode(679, "Fiji");
+
+ assertThat(dialingCodes.getCodes()).containsOnly(entry(679, "Fiji"));
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("setDialingCode with multiple entries")
+ public void testSetDialingCodeWithMultipleEntries() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.setDialingCode(60, "Malaysia");
+ dialingCodes.setDialingCode(233, "Retrieving...");
+ dialingCodes.setDialingCode(56, "Chile");
+ dialingCodes.setDialingCode(233, "Ghana");
+
+ assertThat(dialingCodes.getCodes()).containsOnly(entry(60, "Malaysia"), entry(233, "Ghana"),
+ entry(56, "Chile"));
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("getCountry returns a code's country")
+ public void testGetCountryForCode() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.setDialingCode(55, "Brazil");
+
+ assertThat(dialingCodes.getCountry(55)).isEqualTo("Brazil");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("getCountry returns updated country")
+ public void testGetCountryForUpdatedCode() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.setDialingCode(962, "Retrieving...");
+ dialingCodes.setDialingCode(962, "Jordan");
+
+ assertThat(dialingCodes.getCountry(962)).isEqualTo("Jordan");
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("addNewDialingCode adds new codes")
+ public void testAddNewDialingCodeAddsNewCodes() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.addNewDialingCode(32, "Belgium");
+ dialingCodes.addNewDialingCode(379, "Vatican City");
+
+ assertThat(dialingCodes.getCodes()).containsOnly(entry(32, "Belgium"),
+ entry(379, "Vatican City"));
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("addNewDialingCode leaves already added code")
+ public void testAddNewDialingCodeLeavesExistingCode() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.addNewDialingCode(32, "Belgium");
+ dialingCodes.addNewDialingCode(379, "Vatican City");
+ dialingCodes.addNewDialingCode(32, "Other");
+
+ assertThat(dialingCodes.getCodes()).containsOnly(entry(32, "Belgium"),
+ entry(379, "Vatican City"));
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("addNewDialingCode leaves already added country")
+ public void testAddNewDialingCodeLeavesExistingCountry() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.addNewDialingCode(61, "Australia");
+ dialingCodes.addNewDialingCode(1000, "Australia");
+
+ assertThat(dialingCodes.getCodes()).containsOnly(entry(61, "Australia"));
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("findDialingCode returns a country's dialing code")
+ public void testFindDialingCode() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.addNewDialingCode(44, "UK");
+
+ assertThat(dialingCodes.findDialingCode("UK")).isEqualTo(44);
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("findDialingCode returns null for country not yet added")
+ public void testFindDialingCodeWithUnlistedCountry() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.addNewDialingCode(44, "UK");
+
+ assertThat(dialingCodes.findDialingCode("Unlisted")).isNull();
+ }
+
+ @Test
+ @Tag("task:6")
+ @DisplayName("updateDialingCode updates the map")
+ public void testUpdateDialingCode() {
+ DialingCodes dialingCodes = new DialingCodes();
+ dialingCodes.addNewDialingCode(88, "Japan");
+ dialingCodes.updateCountryDialingCode(81, "Japan");
+
+ assertThat(dialingCodes.getCodes()).containsOnly(entry(81, "Japan"));
+ }
+}
diff --git a/exercises/concept/jedliks-toy-car/.docs/hints.md b/exercises/concept/jedliks-toy-car/.docs/hints.md
new file mode 100644
index 000000000..ad70588fd
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/.docs/hints.md
@@ -0,0 +1,34 @@
+# Hints
+
+## General
+
+## 1. Buy a brand-new remote controlled car
+
+- [This page shows how to create a new instance of a class][creating-objects].
+
+## 2. Display the distance driven
+
+- Keep track of the distance driven in a [field][fields].
+- Consider what visibility to use for the field (does it need to be used outside the class?).
+
+## 3. Display the battery percentage
+
+- Keep track of the distance driven in a [field][fields].
+- Initialize the field to a specific value corresponding to the initial battery charge.
+- Consider what visibility to use for the field (does it need to be used outside the class?).
+
+## 4. Update the number of meters driven when driving
+
+- Update the field representing the distance driven.
+
+## 5. Update the battery percentage when driving
+
+- Update the field representing the battery percentage driven.
+
+## 6. Prevent driving when the battery is drained
+
+- Add a conditional to only update the distance and battery if the battery is not already drained.
+- Add a conditional to display the empty battery message if the battery is drained.
+
+[creating-objects]: https://docs.oracle.com/javase/tutorial/java/javaOO/objectcreation.html
+[fields]: https://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html
diff --git a/exercises/concept/jedliks-toy-car/.docs/instructions.md b/exercises/concept/jedliks-toy-car/.docs/instructions.md
new file mode 100644
index 000000000..cb3f66561
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/.docs/instructions.md
@@ -0,0 +1,83 @@
+# Instructions
+
+In this exercise you'll be playing around with a remote controlled car, which you've finally saved enough money for to buy.
+
+Cars start with full (100%) batteries. Each time you drive the car using the remote control, it covers 20 meters and drains one percent of the battery.
+
+The remote controlled car has a fancy LED display that shows two bits of information:
+
+- The total distance it has driven, displayed as: `"Driven meters"`.
+- The remaining battery charge, displayed as: `"Battery at %"`.
+
+If the battery is at 0%, you can't drive the car anymore and the battery display will show `"Battery empty"`.
+
+You have six tasks, each of which will work with remote controlled car instances.
+
+## 1. Buy a brand-new remote controlled car
+
+Implement the (_static_) `JedliksToyCar.buy()` method to return a brand-new remote controlled car instance:
+
+```java
+JedliksToyCar car = JedliksToyCar.buy();
+```
+
+## 2. Display the distance driven
+
+Implement the `JedliksToyCar.distanceDisplay()` method to return the distance as displayed on the LED display:
+
+```java
+JedliksToyCar car = JedliksToyCar.buy();
+car.distanceDisplay();
+// => "Driven 0 meters"
+```
+
+## 3. Display the battery percentage
+
+Implement the `JedliksToyCar.batteryDisplay()` method to return the battery percentage as displayed on the LED display:
+
+```java
+JedliksToyCar car = JedliksToyCar.buy();
+car.batteryDisplay();
+// => "Battery at 100%"
+```
+
+## 4. Update the number of meters driven when driving
+
+Implement the `JedliksToyCar.drive()` method that updates the number of meters driven:
+
+```java
+JedliksToyCar car = JedliksToyCar.buy();
+car.drive();
+car.drive();
+car.distanceDisplay();
+// => "Driven 40 meters"
+```
+
+## 5. Update the battery percentage when driving
+
+Update the `JedliksToyCar.drive()` method to update the battery percentage:
+
+```java
+JedliksToyCar car = JedliksToyCar.buy();
+car.drive();
+car.drive();
+car.batteryDisplay();
+// => "Battery at 98%"
+```
+
+## 6. Prevent driving when the battery is drained
+
+Update the `JedliksToyCar.drive()` method to not increase the distance driven nor decrease the battery percentage when the battery is drained (at 0%):
+
+```java
+JedliksToyCar car = JedliksToyCar.buy();
+
+// Drain the battery
+// ...
+
+car.distanceDisplay();
+// => "Driven 2000 meters"
+
+car.batteryDisplay();
+// => "Battery empty"
+```
diff --git a/exercises/concept/jedliks-toy-car/.docs/introduction.md b/exercises/concept/jedliks-toy-car/.docs/introduction.md
new file mode 100644
index 000000000..3f9031c54
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/.docs/introduction.md
@@ -0,0 +1,71 @@
+# Introduction
+
+## Classes
+
+The primary object-oriented construct in Java is the _class_, which is a combination of data (_fields_) and behavior (_methods_).
+The fields and methods of a class are known as its _members_.
+
+Access to members can be controlled through access modifiers, the two most common ones being:
+
+- `public`: the member can be accessed by any code (no restrictions).
+- `private`: the member can only be accessed by code in the same class.
+
+You can think of a class as a template for creating instances of that class.
+To create an instance of a class (also known as an _object_), the `new` keyword is used:
+
+```java
+class Car {
+}
+
+// Create two car instances
+Car myCar = new Car();
+Car yourCar = new Car();
+```
+
+Fields have a type and a name (defined in camelCase) and can be defined anywhere in a class (by convention cased in PascalCase):
+
+```java
+class Car {
+ // Accessible by anyone
+ public int weight;
+
+ // Only accessible by code in this class
+ private String color;
+}
+```
+
+One can optionally assign an initial value to a field.
+If a field does _not_ specify an initial value, it will be set to its type's default value.
+An instance's field values can be accessed and updated using dot notation.
+
+```java
+class Car {
+ // Will be set to specified value
+ public int weight = 2500;
+
+ // Will be set to default value (0)
+ public int year;
+}
+
+Car newCar = new Car();
+newCar.weight; // => 2500
+newCar.year; // => 0
+
+// Update value of the field
+newCar.year = 2018;
+```
+
+Private fields are usually updated as a side effect of calling a method.
+Such methods usually don't return any value, in which case the return type should be `void`:
+
+```java
+class CarImporter {
+ private int carsImported;
+
+ public void importCars(int numberOfCars)
+ {
+ // Update private field from public method
+ carsImported = carsImported + numberOfCars;
+ }
+}
+```
diff --git a/exercises/concept/jedliks-toy-car/.docs/introduction.md.tpl b/exercises/concept/jedliks-toy-car/.docs/introduction.md.tpl
new file mode 100644
index 000000000..5fd766bb8
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:classes}
diff --git a/exercises/concept/jedliks-toy-car/.meta/config.json b/exercises/concept/jedliks-toy-car/.meta/config.json
new file mode 100644
index 000000000..300ca8c16
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/.meta/config.json
@@ -0,0 +1,24 @@
+{
+ "authors": [
+ "mikedamay"
+ ],
+ "contributors": [
+ "mirkoperillo"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/JedliksToyCar.java"
+ ],
+ "test": [
+ "src/test/java/JedliksToyCarTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/JedliksToyCar.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "icon": "jedliks-toys",
+ "blurb": "Learn about classes by working on a remote controlled car."
+}
diff --git a/exercises/concept/jedliks-toy-car/.meta/design.md b/exercises/concept/jedliks-toy-car/.meta/design.md
new file mode 100644
index 000000000..2a1b40eb1
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/.meta/design.md
@@ -0,0 +1,42 @@
+# Design
+
+## Learning objectives
+
+- Know what classes are.
+- Know what encapsulation is.
+- Know what fields are.
+- Know how to create an object.
+- Know how to update state through methods.
+- Know about the `void` type.
+
+## Out of scope
+
+- Reference equality.
+- Constructors.
+- Interfaces.
+- Inheritance.
+- Finalizers.
+- Method overloading.
+
+## Concepts
+
+- `classes`: know what classes are; know what encapsulation is; know what fields are; know how to create an object; know how to update state through methods; know about the `void` type.
+
+## Prerequisites
+
+- `basics`: know how to define a basic class with basic methods.
+- `strings`: know how to do basic string interpolation.
+- `numbers`: know how to compare numbers.
+- `conditionals`: know how to do conditional logic.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `essential`: Verify that the solution keeps the void type for the drive function.
+- `essential`: Verify that the solution has fields in the class
+- `actionable`: If the solution defines the fields as `public`, instruct the student to use `private` and explain the encapsulation principle.
+- `informative`: If the solution does not use a primitive as a type for the fields, inform the student to use it.
+ Explain that the values cannot be null and it is less error-prone
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/jedliks-toy-car/.meta/src/reference/java/JedliksToyCar.java b/exercises/concept/jedliks-toy-car/.meta/src/reference/java/JedliksToyCar.java
new file mode 100644
index 000000000..96ef6955c
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/.meta/src/reference/java/JedliksToyCar.java
@@ -0,0 +1,27 @@
+class JedliksToyCar {
+ private int batteryPercentage = 100;
+ private int distanceDrivenInMeters = 0;
+
+ public void drive() {
+ if (batteryPercentage > 0) {
+ batteryPercentage--;
+ distanceDrivenInMeters += 20;
+ }
+ }
+
+ public String distanceDisplay() {
+ return "Driven " + distanceDrivenInMeters + " meters";
+ }
+
+ public String batteryDisplay() {
+ if (batteryPercentage == 0) {
+ return "Battery empty";
+ }
+
+ return "Battery at " + batteryPercentage + "%";
+ }
+
+ public static JedliksToyCar buy() {
+ return new JedliksToyCar();
+ }
+}
diff --git a/exercises/concept/jedliks-toy-car/build.gradle b/exercises/concept/jedliks-toy-car/build.gradle
new file mode 100644
index 000000000..dd3862eb9
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/jedliks-toy-car/gradlew b/exercises/concept/jedliks-toy-car/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/jedliks-toy-car/gradlew.bat b/exercises/concept/jedliks-toy-car/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/jedliks-toy-car/src/main/java/JedliksToyCar.java b/exercises/concept/jedliks-toy-car/src/main/java/JedliksToyCar.java
new file mode 100644
index 000000000..d9906a7b8
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/src/main/java/JedliksToyCar.java
@@ -0,0 +1,17 @@
+public class JedliksToyCar {
+ public static JedliksToyCar buy() {
+ throw new UnsupportedOperationException("Please implement the (static) JedliksToyCar.buy() method");
+ }
+
+ public String distanceDisplay() {
+ throw new UnsupportedOperationException("Please implement the JedliksToyCar.distanceDisplay() method");
+ }
+
+ public String batteryDisplay() {
+ throw new UnsupportedOperationException("Please implement the JedliksToyCar.batteryDisplay() method");
+ }
+
+ public void drive() {
+ throw new UnsupportedOperationException("Please implement the JedliksToyCar.drive() method");
+ }
+}
diff --git a/exercises/concept/jedliks-toy-car/src/test/java/JedliksToyCarTest.java b/exercises/concept/jedliks-toy-car/src/test/java/JedliksToyCarTest.java
new file mode 100644
index 000000000..279cf07a0
--- /dev/null
+++ b/exercises/concept/jedliks-toy-car/src/test/java/JedliksToyCarTest.java
@@ -0,0 +1,119 @@
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class JedliksToyCarTest {
+ @Test
+ @Tag("task:1")
+ @DisplayName("The static buy method returns a new remote controlled car instance")
+ public void buyNewCarReturnsInstance() {
+ JedliksToyCar car = JedliksToyCar.buy();
+ assertThat(car).isNotNull();
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("The static buy method returns each time a new remote controlled car instance")
+ public void buyNewCarReturnsNewCarEachTime() {
+ JedliksToyCar car1 = JedliksToyCar.buy();
+ JedliksToyCar car2 = JedliksToyCar.buy();
+ assertThat(car1).isNotEqualTo(car2);
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("The distanceDisplay method shows 0 meters message on a new car")
+ public void newCarDistanceDisplay() {
+ JedliksToyCar car = new JedliksToyCar();
+ assertThat(car.distanceDisplay()).isEqualTo("Driven 0 meters");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("The batteryDisplay method shows full battery message on a new car")
+ public void newCarBatteryDisplay() {
+ JedliksToyCar car = new JedliksToyCar();
+ assertThat(car.batteryDisplay()).isEqualTo("Battery at 100%");
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("The distanceDisplay method shows the correct message after driving once")
+ public void distanceDisplayAfterDrivingOnce() {
+ JedliksToyCar car = new JedliksToyCar();
+ car.drive();
+ assertThat(car.distanceDisplay()).isEqualTo("Driven 20 meters");
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("The distanceDisplay method shows the correct message after driving multiple times")
+ public void distanceDisplayAfterDrivingMultipleTimes() {
+ JedliksToyCar car = new JedliksToyCar();
+
+ for (int i = 0; i < 17; i++) {
+ car.drive();
+ }
+
+ assertThat(car.distanceDisplay()).isEqualTo("Driven 340 meters");
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("The batteryDisplay method shows the correct message after driving once")
+ public void batteryDisplayAfterDrivingOnce() {
+ JedliksToyCar car = new JedliksToyCar();
+ car.drive();
+
+ assertThat(car.batteryDisplay()).isEqualTo("Battery at 99%");
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("The batteryDisplay method shows the correct battery percentage after driving multiple times")
+ public void batteryDisplayAfterDrivingMultipleTimes() {
+ JedliksToyCar car = new JedliksToyCar();
+
+ for (int i = 0; i < 23; i++) {
+ car.drive();
+ }
+
+ assertThat(car.batteryDisplay()).isEqualTo("Battery at 77%");
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("The batteryDisplay method shows battery empty after draining all battery")
+ public void batteryDisplayWhenBatteryEmpty() {
+ JedliksToyCar car = new JedliksToyCar();
+
+ // Drain the battery
+ for (int i = 0; i < 100; i++) {
+ car.drive();
+ }
+
+ // Attempt to drive one more time (should not work)
+ car.drive();
+
+ assertThat(car.batteryDisplay()).isEqualTo("Battery empty");
+ }
+
+ @Test
+ @Tag("task:6")
+ @DisplayName("The distanceDisplay method shows the correct message after driving and draining all battery")
+ public void distanceDisplayWhenBatteryEmpty() {
+ JedliksToyCar car = new JedliksToyCar();
+
+ // Drain the battery
+ for (int i = 0; i < 100; i++) {
+ car.drive();
+ }
+
+ // Attempt to drive one more time (should not work)
+ car.drive();
+
+ assertThat(car.distanceDisplay()).isEqualTo("Driven 2000 meters");
+ }
+}
diff --git a/exercises/concept/karls-languages/.docs/hints.md b/exercises/concept/karls-languages/.docs/hints.md
index ee41a8fba..112b935cb 100644
--- a/exercises/concept/karls-languages/.docs/hints.md
+++ b/exercises/concept/karls-languages/.docs/hints.md
@@ -1,30 +1,33 @@
+# Hints
+
## 1. Define a function to check if the language list is empty
-* Try using the [`isEmpty()`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html#isEmpty()) method.
+- One of the [methods][list] on the List type can be used to check if it is empty.
## 2. Define a function to add a language to the list
-* Try using the [`add(E element)`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html#add(E)) method.
-* Reminder: methods that return `void` do not need any `return` statements.
+- One of the [methods][list] on the List type can be used to add elements.
+- Reminder: methods that return `void` do not need any `return` statements.
## 3. Define a function to remove a language from the list
-* Try using the [`remove(Object o)`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html#remove(java.lang.Object)) method.
-* Reminder: methods that return `void` do not need any `return` statements.
+- One of the [methods][list] on the List type can be used to remove elements.
+- Reminder: methods that return `void` do not need any `return` statements.
## 4. Define a function to return the first item in the list
-* Try using the [`get(int index)`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html#get(int)) method.
+- One of the [methods][list] on the List type can be used to get elements on a certain index.
## 5. Define a function to return how many languages are in the list
-* Try using the [`size()`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html#size()) method.
+- One of the [methods][list] on the List type can be used to get the size of the list.
## 6. Define a function to determine if a language is in the list
-* Try using the [`contains(Object o)`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html#contains(java.lang.Object)) method.
+- One of the [methods][list] on the List type can be used to check if an element is contained in the list.
## 7. Define a function to determine if the list is exciting
-* Try using a [for-each loop](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html) through all of the elements, checking each one.
-* Alternatively, try using the `containsLanguage` method from the previous step.
+- Try using a method already defined in the class.
+
+[list]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html
diff --git a/exercises/concept/karls-languages/.docs/instructions.md b/exercises/concept/karls-languages/.docs/instructions.md
index 32ec63ebd..f3a048d00 100644
--- a/exercises/concept/karls-languages/.docs/instructions.md
+++ b/exercises/concept/karls-languages/.docs/instructions.md
@@ -6,7 +6,7 @@ It would be very exciting if Karl wants to learn Java or Kotlin!
## 1. Define a function to check if the language list is empty
-Karl needs to know if his list of languages ever becomes empty so he can go find more to learn!
+Karl needs to know if his list of languages ever becomes empty, so he can go find more to learn!
Define a method called `isEmpty` which returns `true` if there are no languages in the list.
```java
diff --git a/exercises/concept/karls-languages/.docs/introduction.md b/exercises/concept/karls-languages/.docs/introduction.md
index 3cfed9144..c34d4f3db 100644
--- a/exercises/concept/karls-languages/.docs/introduction.md
+++ b/exercises/concept/karls-languages/.docs/introduction.md
@@ -56,7 +56,7 @@ stringContainer.set(42);
## Lists
**Lists** are the ordered sequence collection in Java.
-Unlike arrays, a [`List`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html) can grow in size to accomodate any number of items.
+Unlike arrays, a [`List`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/List.html) can grow in size to accommodate any number of items.
One standard implementation is the `ArrayList` which is backed by a re-sizable array.
Another standard implementation is the `LinkedList` class which is backed by a doubly-linked list.
@@ -68,7 +68,7 @@ For example:
List emptyListOfStrings = List.of();
List singleInteger = List.of(1);
List threeBooleans = List.of(true, false, true);
-List listWithMulitipleTypes = List.of("hello", 1, true);
+List listWithMultipleTypes = List.of("hello", 1, true);
```
`List`s have various helpful methods to add, remove, get, and check for an element to be present:
diff --git a/exercises/concept/karls-languages/.docs/introduction.md.tpl b/exercises/concept/karls-languages/.docs/introduction.md.tpl
new file mode 100644
index 000000000..579662f74
--- /dev/null
+++ b/exercises/concept/karls-languages/.docs/introduction.md.tpl
@@ -0,0 +1,5 @@
+# Introduction
+
+%{concept:generic-types}
+
+%{concept:lists}
diff --git a/exercises/concept/karls-languages/.meta/config.json b/exercises/concept/karls-languages/.meta/config.json
index 853489aa0..2315fc3c5 100644
--- a/exercises/concept/karls-languages/.meta/config.json
+++ b/exercises/concept/karls-languages/.meta/config.json
@@ -1,19 +1,21 @@
{
- "blurb": "Learn about lists by helping Karl keep track of the languages he wants to learn on Exercism.",
- "authors": [
- "jmrunkle"
+ "authors": [
+ "jmrunkle"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/LanguageList.java"
],
- "files": {
- "solution": [
- "src/main/java/LanguageList.java"
- ],
- "test": [
- "src/test/java/LanguageListTest.java"
- ],
- "exemplar": [
- ".meta/src/reference/java/LanguageList.java"
- ]
- },
- "icon": "language-list"
- }
-
\ No newline at end of file
+ "test": [
+ "src/test/java/LanguageListTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/LanguageList.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "icon": "language-list",
+ "blurb": "Learn about lists by helping Karl keep track of the languages he wants to learn on Exercism."
+}
diff --git a/exercises/concept/karls-languages/.meta/design.md b/exercises/concept/karls-languages/.meta/design.md
index ec8856464..5f44a4a67 100644
--- a/exercises/concept/karls-languages/.meta/design.md
+++ b/exercises/concept/karls-languages/.meta/design.md
@@ -8,41 +8,55 @@ We should be using the list with more than one data type to show that is possibl
The list collection type was chosen as the first generic type for the following reasons:
-* It is the most commonly used generic type.
-* They are a common data type in many language.
+- It is the most commonly used generic type.
+- They are a common data type in many language.
## Learning objectives
-* Know what generic types are.
-* Know of the existence of the `List` type.
-* Know how a list is different from an array.
-* Know how to define a list.
-* Know how to add and remove elements from a list.
-* Know how to access elements in a list by index.
-* Know how to iterate over elements in a list.
-* Know some basic list functions (like finding the index of an element in a list or sorting a list).
+- Know what generic types are.
+- Know of the existence of the `List` type.
+- Know how a list is different from an array.
+- Know how to define a list.
+- Know how to add and remove elements from a list.
+- Know how to access elements in a list by index.
+- Know how to iterate over elements in a list.
+- Know some basic list functions (like finding the index of an element in a list or sorting a list).
## Out of scope
-* Generic functions.
-* Generic constraints.
-* Memory and performance characteristics.
-* Concurrency issues.
-* Co-/contravariance.
-* Equality.
-* List resizing due to it using an array as its data type.
+- Generic functions.
+- Generic constraints.
+- Memory and performance characteristics.
+- Concurrency issues.
+- Co-/contravariance.
+- Equality.
+- List resizing due to it using an array as its data type.
## Concepts
This Concepts Exercise's Concepts are:
-* `lists`: know of the existence of the `List` type; know how a list is different from an array; know how to define a list; know how to add and remove elements from a list; know how to access elements in a list by index; know how to iterate over elements in a list; know some basic list functions (like finding the index of an element in a list).
-* `generic-types`: know what generic types are.
+- `lists`: know of the existence of the `List` type; know how a list is different from an array; know how to define a list; know how to add and remove elements from a list; know how to access elements in a list by index; know how to iterate over elements in a list; know some basic list functions (like finding the index of an element in a list).
+- `generic-types`: know what generic types are.
## Prerequisites
This Concept Exercise's prerequisites Concepts are:
-* `for-loops`: know how to use a for-loop to iterate over a collection.
-* `arrays`: know of the array collection type and that it has a fixed length.
-* `strings`: data types used in this exercise
+- `arrays`: know of the array collection type and that it has a fixed length.
+- `strings`: data types used in this exercise
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the solution did not use `contains` in the method `containsLanguage`, instruct the student to do so.
+- `actionable`: If the solution did not use `isEmpty` in the method `isEmpty`, instruct the student to do so.
+- `informative`: If the student did not reuse the implementation of the `containsLanguage` method in the `isExciting` method, instruct them to do so.
+ Explain that reusing existing code instead of copy-pasting can help make code easier to maintain.
+- `informative`: If the solution uses an `if statement` in the `containsLanguage` method, instruct the student to return directly the `contains` method.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/karls-languages/.meta/src/reference/java/LanguageList.java b/exercises/concept/karls-languages/.meta/src/reference/java/LanguageList.java
index 963abbcdb..13df5f4a2 100644
--- a/exercises/concept/karls-languages/.meta/src/reference/java/LanguageList.java
+++ b/exercises/concept/karls-languages/.meta/src/reference/java/LanguageList.java
@@ -29,11 +29,6 @@ public boolean containsLanguage(String language) {
}
public boolean isExciting() {
- for (String language : languages) {
- if (language.equals("Java") || language.equals("Kotlin")) {
- return true;
- }
- }
- return false;
+ return containsLanguage("Java") || containsLanguage("Kotlin");
}
}
diff --git a/exercises/concept/karls-languages/build.gradle b/exercises/concept/karls-languages/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/karls-languages/build.gradle
+++ b/exercises/concept/karls-languages/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/karls-languages/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/karls-languages/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/karls-languages/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/karls-languages/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/karls-languages/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/karls-languages/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/karls-languages/gradlew b/exercises/concept/karls-languages/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/karls-languages/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/karls-languages/gradlew.bat b/exercises/concept/karls-languages/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/karls-languages/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/karls-languages/src/test/java/LanguageListTest.java b/exercises/concept/karls-languages/src/test/java/LanguageListTest.java
index c618e3305..551d6cfc9 100644
--- a/exercises/concept/karls-languages/src/test/java/LanguageListTest.java
+++ b/exercises/concept/karls-languages/src/test/java/LanguageListTest.java
@@ -1,17 +1,23 @@
-import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
-import org.junit.Test;
+import static org.assertj.core.api.Assertions.*;
public class LanguageListTest {
LanguageList languageList = new LanguageList();
@Test
+ @Tag("task:1")
+ @DisplayName("The isEmpty method returns true when the list contains no languages")
public void empty() {
assertThat(languageList.isEmpty()).isTrue();
}
@Test
+ @Tag("task:2")
+ @DisplayName("The isEmpty method returns false after adding a language to the list")
public void nonEmpty() {
languageList.addLanguage("Java");
@@ -19,39 +25,18 @@ public void nonEmpty() {
}
@Test
- public void addOneLanguage() {
- languageList.addLanguage("Java");
-
- assertThat(languageList.containsLanguage("Java")).isTrue();
- assertThat(languageList.containsLanguage("Python")).isFalse();
- }
-
- @Test
- public void addMultipleLanguages() {
- languageList.addLanguage("Java");
- languageList.addLanguage("Ruby");
- languageList.addLanguage("C++");
-
- assertThat(languageList.containsLanguage("Java")).isTrue();
- assertThat(languageList.containsLanguage("Ruby")).isTrue();
- assertThat(languageList.containsLanguage("C++")).isTrue();
- assertThat(languageList.containsLanguage("Python")).isFalse();
- }
-
- @Test
+ @Tag("task:3")
+ @DisplayName("The removeLanguage method correctly removes a language from the list")
public void removeLanguage() {
languageList.addLanguage("Java");
- languageList.addLanguage("Python");
- languageList.addLanguage("Ruby");
-
- languageList.removeLanguage("Python");
+ languageList.removeLanguage("Java");
- assertThat(languageList.containsLanguage("Java")).isTrue();
- assertThat(languageList.containsLanguage("Python")).isFalse();
- assertThat(languageList.containsLanguage("Ruby")).isTrue();
+ assertThat(languageList.isEmpty()).isTrue();
}
@Test
+ @Tag("task:4")
+ @DisplayName("The firstLanguage method returns the first language that was added to the list")
public void firstLanguage() {
languageList.addLanguage("Java");
languageList.addLanguage("Python");
@@ -61,6 +46,8 @@ public void firstLanguage() {
}
@Test
+ @Tag("task:5")
+ @DisplayName("The count method returns the number of languages in the list")
public void countThree() {
languageList.addLanguage("Java");
languageList.addLanguage("Python");
@@ -70,11 +57,33 @@ public void countThree() {
}
@Test
+ @Tag("task:5")
+ @DisplayName("The count method returns 0 when the list is empty")
public void countEmpty() {
assertThat(languageList.count()).isEqualTo(0);
}
@Test
+ @Tag("task:6")
+ @DisplayName("The containsLanguage method returns true when the language is in the list")
+ public void containsLanguage() {
+ languageList.addLanguage("Java");
+
+ assertThat(languageList.containsLanguage("Java")).isTrue();
+ }
+
+ @Test
+ @Tag("task:6")
+ @DisplayName("The containsLanguage method returns false when the language is not in the list")
+ public void doesNotContainLanguage() {
+ languageList.addLanguage("Kotlin");
+
+ assertThat(languageList.containsLanguage("Java")).isFalse();
+ }
+
+ @Test
+ @Tag("task:7")
+ @DisplayName("The isExciting method returns true when the list contains Java")
public void excitingLanguageListWithJava() {
languageList.addLanguage("Java");
@@ -82,6 +91,8 @@ public void excitingLanguageListWithJava() {
}
@Test
+ @Tag("task:7")
+ @DisplayName("The isExciting method returns true when the list contains Kotlin")
public void excitingLanguageListWithKotlin() {
languageList.addLanguage("Python");
languageList.addLanguage("Kotlin");
@@ -90,6 +101,8 @@ public void excitingLanguageListWithKotlin() {
}
@Test
+ @Tag("task:7")
+ @DisplayName("The isExciting method returns false when the list contains neither Java nor Kotlin")
public void boringLanguageList() {
languageList.addLanguage("Python");
languageList.addLanguage("Ruby");
diff --git a/exercises/concept/lasagna/.docs/introduction.md b/exercises/concept/lasagna/.docs/introduction.md
index 65fe8a965..71fb191d7 100644
--- a/exercises/concept/lasagna/.docs/introduction.md
+++ b/exercises/concept/lasagna/.docs/introduction.md
@@ -1,22 +1,30 @@
# Introduction
-Java is a statically-typed language, which means that everything has a type at compile-time. Assigning a value to a name is referred to as defining a variable. A variable is defined by explicitly specifying its type.
+## Basics
+
+Java is a statically-typed language, which means that the type of a variable is known at compile-time.
+Assigning a value to a name is referred to as defining a variable.
+A variable is defined by explicitly specifying its type.
```java
int explicitVar = 10;
```
-Updating a variable's value is done through the `=` operator. Once defined, a variable's type can never change.
+Updating a variable's value is done through the `=` operator.
+Here, `=` does not represent mathematical equality.
+It simply assigns a value to a variable.
+Once defined, a variable's type can never change.
```java
int count = 1; // Assign initial value
count = 2; // Update to new value
-// Compiler error when assigning different type
+// Compiler error when assigning a different type
// count = false;
```
-Java is an [object-oriented language][object-oriented-programming] and requires all functions to be defined in a _class_. The `class` keyword is used to define a class.
+Java is an [object-oriented language][object-oriented-programming] and requires all functions to be defined in a _class_.
+The `class` keyword is used to define a class.
```java
class Calculator {
@@ -24,7 +32,12 @@ class Calculator {
}
```
-A function within a class is referred to as a _method_. Each method can have zero or more parameters. All parameters must be explicitly typed, there is no type inference for parameters. Similarly, the return type must also be made explicit. Values are returned from functions using the `return` keyword. To allow a method to be called by other classes, the `public` access modifier must be added.
+A function within a class is referred to as a _method_.
+Each method can have zero or more parameters.
+All parameters must be explicitly typed, there is no type inference for parameters.
+Similarly, the return type must also be made explicit.
+Values are returned from methods using the `return` keyword.
+To allow a method to be called by other classes, the `public` access modifier must be added.
```java
class Calculator {
@@ -34,14 +47,15 @@ class Calculator {
}
```
-Invoking a method is done by specifying its class and method name and passing arguments for each of the method's parameters.
+Invoking/calling a method is done by specifying its class and method name and passing arguments for each of the method's parameters.
```java
-int sum = new Calculator().add(1, 2);
+int sum = new Calculator().add(1, 2); // here the "add" method has been called to perform the task of addition
```
Scope in Java is defined between the `{` and `}` characters.
-Java supports two types of comments. Single line comments are preceded by `//` and multiline comments are inserted between `/*` and `*/`.
+Java supports two types of comments.
+Single line comments are preceded by `//` and multiline comments are inserted between `/*` and `*/`.
[object-oriented-programming]: https://docs.oracle.com/javase/tutorial/java/javaOO/index.html
diff --git a/exercises/concept/lasagna/.docs/introduction.md.tpl b/exercises/concept/lasagna/.docs/introduction.md.tpl
new file mode 100644
index 000000000..cd2cef4ed
--- /dev/null
+++ b/exercises/concept/lasagna/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:basics}
diff --git a/exercises/concept/lasagna/.meta/config.json b/exercises/concept/lasagna/.meta/config.json
index 956e21c30..9cf76ed92 100644
--- a/exercises/concept/lasagna/.meta/config.json
+++ b/exercises/concept/lasagna/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Learn about the basics of Java by following a lasagna recipe.",
"authors": [
"mirkoperillo"
],
@@ -12,9 +11,13 @@
],
"exemplar": [
".meta/src/reference/java/Lasagna.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"csharp/lucians-luscious-lasagna"
- ]
+ ],
+ "blurb": "Learn about the basics of Java by following a lasagna recipe."
}
diff --git a/exercises/concept/lasagna/.meta/design.md b/exercises/concept/lasagna/.meta/design.md
index 3addebb23..47ead5001 100644
--- a/exercises/concept/lasagna/.meta/design.md
+++ b/exercises/concept/lasagna/.meta/design.md
@@ -47,7 +47,16 @@ This exercise does not require any specific representation logic to be added to
## Analyzer
-This exercise does not require any specific logic to be added to the [analyzer][analyzer]:
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the student did not reuse the implementation of the `expectedMinutesInOven` method in the `remainingMinutesInOven` method, instruct them to do so.
+ Explain that reusing existing code instead of copy-pasting can help make code easier to maintain.
+- `actionable`: If the student did not reuse the implementation of the `preparationTimeInMinutes` method in the `totalTimeInMinutes` method, instruct them to do so.
+ Explain that reusing existing code instead of copy-pasting can help make code easier to maintain.
+- `actionable`: If the student left any `// TODO: ...` comments in the code, instruct them to remove these.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
[analyzer]: https://github.com/exercism/java-analyzer
[representer]: https://github.com/exercism/java-representer
diff --git a/exercises/concept/lasagna/build.gradle b/exercises/concept/lasagna/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/concept/lasagna/build.gradle
+++ b/exercises/concept/lasagna/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/concept/lasagna/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/lasagna/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/lasagna/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/lasagna/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/lasagna/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/lasagna/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/lasagna/gradlew b/exercises/concept/lasagna/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/lasagna/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/lasagna/gradlew.bat b/exercises/concept/lasagna/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/lasagna/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/lasagna/src/test/java/LasagnaTest.java b/exercises/concept/lasagna/src/test/java/LasagnaTest.java
index ca897198a..6200d47c5 100644
--- a/exercises/concept/lasagna/src/test/java/LasagnaTest.java
+++ b/exercises/concept/lasagna/src/test/java/LasagnaTest.java
@@ -1,4 +1,6 @@
-import org.junit.Test;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import utils.Lasagna;
@@ -7,7 +9,9 @@
public class LasagnaTest {
@Test
- public void implemented_expected_minutes_in_oven() {
+ @Tag("task:1")
+ @DisplayName("Implemented the expectedMinutesInOven method")
+ public void implementedExpectedMinutesInOven() {
assertThat(new Lasagna().hasMethod("expectedMinutesInOven"))
.withFailMessage("Method expectedMinutesInOven must be created")
.isTrue();
@@ -20,12 +24,16 @@ public void implemented_expected_minutes_in_oven() {
}
@Test
- public void expected_minutes_in_oven() {
+ @Tag("task:1")
+ @DisplayName("The expectedMinutesInOven method returns the correct value")
+ public void expectedMinutesInOven() {
assertThat(new Lasagna().expectedMinutesInOven()).isEqualTo(40);
}
@Test
- public void implemented_remaining_minutes_in_oven() {
+ @Tag("task:2")
+ @DisplayName("Implemented the remainingMinutesInOven method")
+ public void implementedRemainingMinutesInOven() {
assertThat(new Lasagna().hasMethod("remainingMinutesInOven", int.class))
.withFailMessage("Method remainingMinutesInOven must be created")
.isTrue();
@@ -38,12 +46,16 @@ public void implemented_remaining_minutes_in_oven() {
}
@Test
- public void remaining_minutes_in_oven() {
+ @Tag("task:2")
+ @DisplayName("The remainingMinutesInOven method calculates and returns the correct value")
+ public void remainingMinutesInOven() {
assertThat(new Lasagna().remainingMinutesInOven(25)).isEqualTo(15);
}
@Test
- public void implemented_preparation_time_in_minutes() {
+ @Tag("task:3")
+ @DisplayName("Implemented the preparationTimeInMinutes method")
+ public void implementedPreparationTimeInMinutes() {
assertThat(new Lasagna().hasMethod("preparationTimeInMinutes", int.class))
.withFailMessage("Method preparationTimeInMinutes must be created")
.isTrue();
@@ -56,17 +68,23 @@ public void implemented_preparation_time_in_minutes() {
}
@Test
- public void preparation_time_in_minutes_for_one_layer() {
+ @Tag("task:3")
+ @DisplayName("The preparationTimeInMinutes method calculates the correct value for single layer")
+ public void preparationTimeInMinutesForOneLayer() {
assertThat(new Lasagna().preparationTimeInMinutes(1)).isEqualTo(2);
}
@Test
- public void preparation_time_in_minutes_for_multiple_layers() {
+ @Tag("task:3")
+ @DisplayName("The preparationTimeInMinutes method calculates the correct value for multiple layers")
+ public void preparationTimeInMinutesForMultipleLayers() {
assertThat(new Lasagna().preparationTimeInMinutes(4)).isEqualTo(8);
}
@Test
- public void implemented_total_time_in_minutes() {
+ @Tag("task:4")
+ @DisplayName("Implemented the totalTimeInMinutes method")
+ public void implementedTotalTimeInMinutes() {
assertThat(new Lasagna().hasMethod("totalTimeInMinutes", int.class, int.class))
.withFailMessage("Method totalTimeInMinutes must be created")
.isTrue();
@@ -79,12 +97,16 @@ public void implemented_total_time_in_minutes() {
}
@Test
- public void total_time_in_minutes_for_one_layer() {
+ @Tag("task:4")
+ @DisplayName("The totalTimeInMinutes method calculates the correct value for single layer")
+ public void totalTimeInMinutesForOneLayer() {
assertThat(new Lasagna().totalTimeInMinutes(1, 30)).isEqualTo(32);
}
@Test
- public void total_time_in_minutes_for_multiple_layers() {
+ @Tag("task:4")
+ @DisplayName("The totalTimeInMinutes method calculates the correct value for multiple layers")
+ public void totalTimeInMinutesForMultipleLayers() {
assertThat(new Lasagna().totalTimeInMinutes(4, 8)).isEqualTo(16);
}
}
diff --git a/exercises/concept/lasagna/src/test/java/utils/Lasagna.java b/exercises/concept/lasagna/src/test/java/utils/Lasagna.java
index 21e12ef63..ba2ac0aeb 100644
--- a/exercises/concept/lasagna/src/test/java/utils/Lasagna.java
+++ b/exercises/concept/lasagna/src/test/java/utils/Lasagna.java
@@ -8,34 +8,19 @@ public String getTargetClassName() {
}
public int expectedMinutesInOven() {
- try {
- return invokeMethod("expectedMinutesInOven", new Class[]{});
- } catch (Exception e) {
- throw new UnsupportedOperationException("Please implement the expectedMinutesInOven() method");
- }
+ return invokeMethod("expectedMinutesInOven", Integer.class, new Class[]{});
}
public int remainingMinutesInOven(int actualMinutes) {
- try {
- return invokeMethod("remainingMinutesInOven", new Class[]{int.class}, actualMinutes);
- } catch (Exception e) {
- throw new UnsupportedOperationException("Please implement the remainingMinutesInOven(int) method");
- }
+ return invokeMethod("remainingMinutesInOven", Integer.class, new Class[]{int.class}, actualMinutes);
}
public int preparationTimeInMinutes(int amountLayers) {
- try {
- return invokeMethod("preparationTimeInMinutes", new Class[]{int.class}, amountLayers);
- } catch (Exception e) {
- throw new UnsupportedOperationException("Please implement the preparationTimeInMinutes(int) method");
- }
+ return invokeMethod("preparationTimeInMinutes", Integer.class, new Class[]{int.class}, amountLayers);
}
public int totalTimeInMinutes(int amountLayers, int actualMinutes) {
- try {
- return invokeMethod("totalTimeInMinutes", new Class[]{int.class, int.class}, amountLayers, actualMinutes);
- } catch (Exception e) {
- throw new UnsupportedOperationException("Please implement the totalTimeInMinutes(int, int) method");
- }
+ return invokeMethod("totalTimeInMinutes", Integer.class, new Class[]{int.class, int.class},
+ amountLayers, actualMinutes);
}
}
diff --git a/exercises/concept/lasagna/src/test/java/utils/ReflectionProxy.java b/exercises/concept/lasagna/src/test/java/utils/ReflectionProxy.java
index 6405b525c..14810c9ef 100644
--- a/exercises/concept/lasagna/src/test/java/utils/ReflectionProxy.java
+++ b/exercises/concept/lasagna/src/test/java/utils/ReflectionProxy.java
@@ -101,12 +101,14 @@ public boolean isMethodReturnType(Class> returnType, String name, Class>...
/**
* Invokes a method from the target instance
* @param methodName The name of the method
+ * @param returnType The class representing the expected return type
* @param parameterTypes The list of parameter types
* @param parameterValues The list with values for the method parameters
* @param The result type we expect the method to be
* @return The value returned by the method
*/
- protected T invokeMethod(String methodName, Class>[] parameterTypes, Object... parameterValues) {
+ protected T invokeMethod(String methodName, Class returnType, Class>[] parameterTypes,
+ Object... parameterValues) {
if (target == null) {
return null;
}
@@ -114,13 +116,13 @@ protected T invokeMethod(String methodName, Class>[] parameterTypes, Obje
// getDeclaredMethod is used to get protected/private methods
Method method = target.getClass().getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
- return (T) method.invoke(target, parameterValues);
+ return returnType.cast(method.invoke(target, parameterValues));
} catch (NoSuchMethodException e) {
try {
// try getting it from parent class, but only public methods will work
Method method = target.getClass().getMethod(methodName, parameterTypes);
method.setAccessible(true);
- return (T) method.invoke(target, parameterValues);
+ return returnType.cast(method.invoke(target, parameterValues));
} catch (Exception ex) {
return null;
}
@@ -381,7 +383,7 @@ public int hashCode() {
* @return The result of 'toString' from the target instance
*/
public String toString() {
- return invokeMethod("toString", new Class[]{ });
+ return invokeMethod("toString", String.class, new Class[]{ });
}
/**
@@ -390,14 +392,14 @@ public String toString() {
* @param The type we are expecting it to be
* @return The value of the property (if it exists)
*/
- protected T getPropertyValue(String propertyName) {
+ protected T getPropertyValue(String propertyName, Class propertyType) {
if (target == null || !hasProperty(propertyName)) {
return null;
}
try {
Field field = target.getClass().getDeclaredField(propertyName);
field.setAccessible(true);
- return (T) field.get(target);
+ return propertyType.cast(field.get(target));
} catch (Exception e) {
return null;
}
diff --git a/exercises/concept/log-levels/.docs/hints.md b/exercises/concept/log-levels/.docs/hints.md
index 8453667c5..2e5bce849 100644
--- a/exercises/concept/log-levels/.docs/hints.md
+++ b/exercises/concept/log-levels/.docs/hints.md
@@ -7,12 +7,12 @@
## 1. Get message from a log line
- Different options to search for text in a string are explored in [this tutorial][tutorial-search-text-in-string].
-- How to split strings can be seen [here][tutorial-split-strings]
+- How to split strings can be seen in [this tutorial][tutorial-split-strings]
- Removing white space is [built-in][tutorial-trim-white-space].
## 2. Get log level from a log line
-- Changing a `String`'s casing is explored [here][tutorial-changing-case-upper] and [here][tutorial-changing-case-lower].
+- Changing a `String`'s casing is explored in [this changing to upper case][tutorial-changing-case-upper] and [this changing to lower case][tutorial-changing-case-lower] tutorial.
## 3. Reformat a log line
diff --git a/exercises/concept/log-levels/.docs/introduction.md b/exercises/concept/log-levels/.docs/introduction.md
index 127e0a0e0..6fa63ad07 100644
--- a/exercises/concept/log-levels/.docs/introduction.md
+++ b/exercises/concept/log-levels/.docs/introduction.md
@@ -1,10 +1,15 @@
# Introduction
-A `String` in Java is an object that represents immutable text as a sequence of Unicode characters (letters, digits, punctuation, etc.). Double quotes are used to define a `String` instance:
+## Strings
+
+A `String` in Java is an object that represents immutable text as a sequence of Unicode characters (letters, digits, punctuation, etc.).
+Double quotes are used to define a `String` instance:
```java
String fruit = "Apple";
```
-Strings are manipulated by calling the string's methods. Once a string has been constructed, its value can never change. Any methods that appear to modify a string will actually return a new string.
+Strings are manipulated by calling the string's methods.
+Once a string has been constructed, its value can never change.
+Any methods that appear to modify a string will actually return a new string.
The `String` class provides some _static_ methods to transform the strings.
diff --git a/exercises/concept/log-levels/.docs/introduction.md.tpl b/exercises/concept/log-levels/.docs/introduction.md.tpl
new file mode 100644
index 000000000..1dc0a9066
--- /dev/null
+++ b/exercises/concept/log-levels/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:strings}
diff --git a/exercises/concept/log-levels/.meta/config.json b/exercises/concept/log-levels/.meta/config.json
index 052b1de27..48f0f8033 100644
--- a/exercises/concept/log-levels/.meta/config.json
+++ b/exercises/concept/log-levels/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Learn about strings by processing logs.",
"authors": [
"mirkoperillo"
],
@@ -12,9 +11,13 @@
],
"exemplar": [
".meta/src/reference/java/LogLevels.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"csharp/log-levels"
- ]
+ ],
+ "blurb": "Learn about strings by processing logs."
}
diff --git a/exercises/concept/log-levels/.meta/design.md b/exercises/concept/log-levels/.meta/design.md
index 9e473714b..7e87f0143 100644
--- a/exercises/concept/log-levels/.meta/design.md
+++ b/exercises/concept/log-levels/.meta/design.md
@@ -22,3 +22,17 @@
This exercise's prerequisites Concepts are:
- `basics`: know how to define methods.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `essential`: Verify that the solution does not hard-code the log levels (`[ERROR]:`, `[WARNING]:`, `[INFO]:`)
+- `actionable`: If the student did not reuse the implementation of the `message` and `logLevel` methods in the `reformat` method, instruct them to do so.
+- `actionable`: If the solution did not use `split` or `substring` in the methods `message` and `logLevel`, instruct the student to do so.
+- `informative`: If the solution uses `String.format` in the `reformat` method, inform the student that this cause a small performance penalty compared to string concatenation.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/log-levels/.meta/src/reference/java/LogLevels.java b/exercises/concept/log-levels/.meta/src/reference/java/LogLevels.java
index 5291f17c4..a1e18de48 100644
--- a/exercises/concept/log-levels/.meta/src/reference/java/LogLevels.java
+++ b/exercises/concept/log-levels/.meta/src/reference/java/LogLevels.java
@@ -8,6 +8,6 @@ public static String logLevel(String logLine) {
}
public static String reformat(String logLine) {
- return String.format("%s (%s)", message(logLine), logLevel(logLine));
+ return message(logLine) + " (" + logLevel(logLine) + ")";
}
}
diff --git a/exercises/concept/log-levels/build.gradle b/exercises/concept/log-levels/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/log-levels/build.gradle
+++ b/exercises/concept/log-levels/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/log-levels/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/log-levels/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/log-levels/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/log-levels/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/log-levels/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/log-levels/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/log-levels/gradlew b/exercises/concept/log-levels/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/log-levels/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/log-levels/gradlew.bat b/exercises/concept/log-levels/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/log-levels/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/log-levels/src/main/java/LogLevels.java b/exercises/concept/log-levels/src/main/java/LogLevels.java
index c67e19e56..71102ac87 100644
--- a/exercises/concept/log-levels/src/main/java/LogLevels.java
+++ b/exercises/concept/log-levels/src/main/java/LogLevels.java
@@ -1,14 +1,14 @@
public class LogLevels {
public static String message(String logLine) {
- throw new UnsupportedOperationException("Please implement the (static) LogLine.message() method");
+ throw new UnsupportedOperationException("Please implement the (static) LogLevels.message() method");
}
public static String logLevel(String logLine) {
- throw new UnsupportedOperationException("Please implement the (static) LogLine.logLevel() method");
+ throw new UnsupportedOperationException("Please implement the (static) LogLevels.logLevel() method");
}
public static String reformat(String logLine) {
- throw new UnsupportedOperationException("Please implement the (static) LogLine.reformat() method");
+ throw new UnsupportedOperationException("Please implement the (static) LogLevels.reformat() method");
}
}
diff --git a/exercises/concept/log-levels/src/test/java/LogLevelsTest.java b/exercises/concept/log-levels/src/test/java/LogLevelsTest.java
index 5fec7170e..65dd46629 100644
--- a/exercises/concept/log-levels/src/test/java/LogLevelsTest.java
+++ b/exercises/concept/log-levels/src/test/java/LogLevelsTest.java
@@ -1,63 +1,87 @@
-import org.junit.Test;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
public class LogLevelsTest {
@Test
- public void error_message() {
+ @Tag("task:1")
+ @DisplayName("The message method returns the log line's message of an error log")
+ public void errorMessage() {
assertThat(LogLevels.message("[ERROR]: Stack overflow")).isEqualTo("Stack overflow");
}
@Test
- public void warning_message() {
+ @Tag("task:1")
+ @DisplayName("The message method returns the log line's message of a warning log")
+ public void warningMessage() {
assertThat(LogLevels.message("[WARNING]: Disk almost full")).isEqualTo("Disk almost full");
}
@Test
- public void info_message() {
+ @Tag("task:1")
+ @DisplayName("The message method returns the log line's message of an info log")
+ public void infoMessage() {
assertThat(LogLevels.message("[INFO]: File moved")).isEqualTo("File moved");
}
@Test
- public void message_with_leading_and_trailing_white_space() {
+ @Tag("task:1")
+ @DisplayName("The message method returns the log line's message after removing leading and trailing spaces")
+ public void messageWithLeadingAndTrailingWhiteSpace() {
assertThat(LogLevels.message("[WARNING]: \tTimezone not set \r\n")).isEqualTo("Timezone not set");
}
@Test
- public void error_log_level() {
+ @Tag("task:2")
+ @DisplayName("The logLevel method returns the log level of an error log line")
+ public void errorLogLevel() {
assertThat(LogLevels.logLevel("[ERROR]: Disk full")).isEqualTo("error");
}
@Test
- public void warning_log_level() {
+ @Tag("task:2")
+ @DisplayName("The logLevel method returns the log level of a warning log line")
+ public void warningLogLevel() {
assertThat(LogLevels.logLevel("[WARNING]: Unsafe password")).isEqualTo("warning");
}
@Test
- public void info_log_level() {
+ @Tag("task:2")
+ @DisplayName("The logLevel method returns the log level of an info log line")
+ public void infoLogLevel() {
assertThat(LogLevels.logLevel("[INFO]: Timezone changed")).isEqualTo("info");
}
@Test
- public void error_reformat() {
+ @Tag("task:3")
+ @DisplayName("The reformat method correctly reformats an error log line")
+ public void errorReformat() {
assertThat(LogLevels.reformat("[ERROR]: Segmentation fault"))
.isEqualTo("Segmentation fault (error)");
}
@Test
- public void warning_reformat() {
+ @Tag("task:3")
+ @DisplayName("The reformat method correctly reformats a warning log line")
+ public void warningReformat() {
assertThat(LogLevels.reformat("[WARNING]: Decreased performance"))
.isEqualTo("Decreased performance (warning)");
}
@Test
- public void info_reformat() {
+ @Tag("task:3")
+ @DisplayName("The reformat method correctly reformats an info log line")
+ public void infoReformat() {
assertThat(LogLevels.reformat("[INFO]: Disk defragmented"))
.isEqualTo("Disk defragmented (info)");
}
@Test
- public void reformat_with_leading_and_trailing_white_space() {
+ @Tag("task:3")
+ @DisplayName("The reformat method correctly reformats an error log line removing spaces")
+ public void reformatWithLeadingAndTrailingWhiteSpace() {
assertThat(LogLevels.reformat("[ERROR]: \t Corrupt disk\t \t \r\n"))
.isEqualTo("Corrupt disk (error)");
}
diff --git a/exercises/concept/logs-logs-logs/.docs/hints.md b/exercises/concept/logs-logs-logs/.docs/hints.md
new file mode 100644
index 000000000..a3463a1be
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/.docs/hints.md
@@ -0,0 +1,22 @@
+# Hints
+
+## General
+
+- [Tutorial on working with enums][java-tutorial-enum-types].
+
+## 1. Parse log level
+
+- There is a [method to get a part of a string][java-docs-substring].
+- You can use a [`switch` statement][java-tutorial-switch-statement] to elegantly handle the various log levels.
+
+## 2. Support unknown log level
+
+- There is a special switch case called `default` that can be used to catch unspecified cases.
+
+## 3. Convert log line to short format
+
+- You can add fields and methods to an enum type, see the [tutorial on working with enums][java-tutorial-enum-types].
+
+[java-tutorial-enum-types]: https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html
+[java-tutorial-switch-statement]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html
+[java-docs-substring]: https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#substring-int-int-
diff --git a/exercises/concept/logs-logs-logs/.docs/instructions.md b/exercises/concept/logs-logs-logs/.docs/instructions.md
new file mode 100644
index 000000000..c1dff6218
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/.docs/instructions.md
@@ -0,0 +1,69 @@
+# Instructions
+
+In this exercise you'll be processing log-lines.
+
+Each log line is a string formatted as follows: `"[]: "`.
+
+These are the different log levels:
+
+- `TRC` (trace)
+- `DBG` (debug)
+- `INF` (info)
+- `WRN` (warning)
+- `ERR` (error)
+- `FTL` (fatal)
+
+You have three tasks.
+
+## 1. Parse log level
+
+Define a `LogLevel` enum that has six elements corresponding to the above log levels.
+
+- `TRACE`
+- `DEBUG`
+- `INFO`
+- `WARNING`
+- `ERROR`
+- `FATAL`
+
+Next, implement the `LogLine.getLogLevel()` method that returns the parsed log level of a log line:
+
+```java
+var logLine = new LogLine("[INF]: File deleted");
+logLine.getLogLevel();
+// => LogLevel.INFO
+```
+
+## 2. Support unknown log level
+
+Unfortunately, occasionally some log lines have an unknown log level.
+To gracefully handle these log lines, add an `UNKNOWN` element to the `LogLevel` enum which should be returned when parsing an unknown log level:
+
+```java
+var logLine = new LogLine("[XYZ]: Overly specific, out of context message");
+logLine.getLogLevel();
+// => LogLevel.UNKNOWN
+```
+
+## 3. Convert log line to short format
+
+The log level of a log line is quite verbose.
+To reduce the disk space needed to store the log lines, a short format is developed: `"[]:"`.
+
+The encoded log level is a simple mapping of a log level to a number:
+
+- `UNKNOWN` - `0`
+- `TRACE` - `1`
+- `DEBUG` - `2`
+- `INFO` - `4`
+- `WARNING` - `5`
+- `ERROR` - `6`
+- `FATAL` - `42`
+
+Implement the `LogLine.getOutputForShortLog()` method that can output the shortened log line format:
+
+```java
+var logLine = new LogLine("[ERR]: Stack Overflow");
+logLine.getOutputForShortLog();
+// => "6:Stack Overflow"
+```
diff --git a/exercises/concept/logs-logs-logs/.docs/introduction.md b/exercises/concept/logs-logs-logs/.docs/introduction.md
new file mode 100644
index 000000000..8b53d6c25
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/.docs/introduction.md
@@ -0,0 +1,91 @@
+# Introduction
+
+## Enums
+
+An _enum type_ is a special data type that enables for a variable to be a set of predefined constants.
+The variable must be equal to one of the values that have been predefined for it.
+Common examples include compass directions (values of `NORTH`, `SOUTH`, `EAST`, and `WEST`) and the days of the week.
+
+Because they are constants, the names of an enum type's fields are in uppercase letters.
+
+### Defining an enum type
+
+In the Java programming language, you define an enum type by using the `enum` keyword.
+For example, you would specify a days-of-the-week enum type as:
+
+```java
+public enum DayOfWeek {
+ SUNDAY,
+ MONDAY,
+ TUESDAY,
+ WEDNESDAY,
+ THURSDAY,
+ FRIDAY,
+ SATURDAY
+}
+```
+
+You should use enum types any time you need to represent a fixed set of constants.
+That includes natural enum types such as the planets in our solar system and data sets where you know all possible values at compile time - for example, the choices on a menu, command line flags, and so on.
+
+### Using an enum type
+
+Here is some code that shows you how to use the `DayOfWeek` enum defined above:
+
+```java
+public class Shop {
+ public String getOpeningHours(DayOfWeek dayOfWeek) {
+ switch (dayOfWeek) {
+ case MONDAY:
+ case TUESDAY:
+ case WEDNESDAY:
+ case THURSDAY:
+ case FRIDAY:
+ return "9am - 5pm";
+ case SATURDAY:
+ return "10am - 4pm"
+ case SUNDAY:
+ return "Closed.";
+ }
+ }
+}
+```
+
+```java
+var shop = new Shop();
+shop.getOpeningHours(DayOfWeek.WEDNESDAY);
+// => "9am - 5pm"
+```
+
+### Adding methods and fields
+
+Java programming language enum types are much more powerful than their counterparts in other languages.
+The `enum` declaration defines a _class_ (called an _enum type_).
+The enum class body can include methods and other fields:
+
+```java
+public enum Rating {
+ GREAT(5),
+ GOOD(4),
+ OK(3),
+ BAD(2),
+ TERRIBLE(1);
+
+ private final int numberOfStars;
+
+ Rating(int numberOfStars) {
+ this.numberOfStars = numberOfStars;
+ }
+
+ public int getNumberOfStars() {
+ return this.numberOfStars;
+ }
+}
+```
+
+Calling the `getNumberOfStars` method on a member of the `Rating` enum type:
+
+```java
+Rating.GOOD.getNumberOfStars();
+// => 4
+```
diff --git a/exercises/concept/logs-logs-logs/.docs/introduction.md.tpl b/exercises/concept/logs-logs-logs/.docs/introduction.md.tpl
new file mode 100644
index 000000000..00708a387
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:enums}
diff --git a/exercises/concept/logs-logs-logs/.meta/config.json b/exercises/concept/logs-logs-logs/.meta/config.json
new file mode 100644
index 000000000..438eb882c
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/.meta/config.json
@@ -0,0 +1,25 @@
+{
+ "authors": [
+ "sanderploegsma"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/LogLevel.java",
+ "src/main/java/LogLine.java"
+ ],
+ "test": [
+ "src/test/java/LogsLogsLogsTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/LogLevel.java",
+ ".meta/src/reference/java/LogLine.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "forked_from": [
+ "csharp/logs-logs-logs"
+ ],
+ "blurb": "Learn about enums by parsing logs."
+}
diff --git a/exercises/concept/logs-logs-logs/.meta/design.md b/exercises/concept/logs-logs-logs/.meta/design.md
new file mode 100644
index 000000000..df78aa422
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/.meta/design.md
@@ -0,0 +1,22 @@
+# Design
+
+## Learning objectives
+
+After completing this exercise, the student should:
+
+- Know of the existence of the `enum` keyword.
+- Know how to define enum members.
+- Know how to add fields to enum types.
+- Know how to add methods to enum types.
+
+## Concepts
+
+- `enums`: know of the existence of the `enum` keyword; know how to define enum members; know how to assign values to enum members.
+
+## Prerequisites
+
+This exercise's prerequisites Concepts are:
+
+- `strings`
+- `switch-statement`
+- `constructors`
diff --git a/exercises/concept/logs-logs-logs/.meta/src/reference/java/LogLevel.java b/exercises/concept/logs-logs-logs/.meta/src/reference/java/LogLevel.java
new file mode 100644
index 000000000..0d61767e1
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/.meta/src/reference/java/LogLevel.java
@@ -0,0 +1,19 @@
+public enum LogLevel {
+ UNKNOWN(0),
+ TRACE(1),
+ DEBUG(2),
+ INFO(4),
+ WARNING(5),
+ ERROR(6),
+ FATAL(42);
+
+ private final int encodedLevel;
+
+ LogLevel(int encodedLevel) {
+ this.encodedLevel = encodedLevel;
+ }
+
+ public int getEncodedLevel() {
+ return this.encodedLevel;
+ }
+}
diff --git a/exercises/concept/logs-logs-logs/.meta/src/reference/java/LogLine.java b/exercises/concept/logs-logs-logs/.meta/src/reference/java/LogLine.java
new file mode 100644
index 000000000..c3ccfd4e6
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/.meta/src/reference/java/LogLine.java
@@ -0,0 +1,26 @@
+public class LogLine {
+ private final LogLevel level;
+ private final String message;
+
+ public LogLine(String logLine) {
+ this.level = switch (logLine.substring(1, 4)) {
+ case "TRC" -> LogLevel.TRACE;
+ case "DBG" -> LogLevel.DEBUG;
+ case "INF" -> LogLevel.INFO;
+ case "WRN" -> LogLevel.WARNING;
+ case "ERR" -> LogLevel.ERROR;
+ case "FTL" -> LogLevel.FATAL;
+ default -> LogLevel.UNKNOWN;
+ };
+
+ this.message = logLine.substring(7);
+ }
+
+ public LogLevel getLogLevel() {
+ return this.level;
+ }
+
+ public String getOutputForShortLog() {
+ return String.valueOf(this.level.getEncodedLevel()) + ":" + this.message;
+ }
+}
diff --git a/exercises/concept/logs-logs-logs/build.gradle b/exercises/concept/logs-logs-logs/build.gradle
new file mode 100644
index 000000000..dd3862eb9
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/concept/logs-logs-logs/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/logs-logs-logs/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/logs-logs-logs/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/logs-logs-logs/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/logs-logs-logs/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/logs-logs-logs/gradlew b/exercises/concept/logs-logs-logs/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/logs-logs-logs/gradlew.bat b/exercises/concept/logs-logs-logs/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/logs-logs-logs/src/main/java/LogLevel.java b/exercises/concept/logs-logs-logs/src/main/java/LogLevel.java
new file mode 100644
index 000000000..17fb02207
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/src/main/java/LogLevel.java
@@ -0,0 +1,3 @@
+public enum LogLevel {
+ // TODO: define members for each log level
+}
diff --git a/exercises/concept/logs-logs-logs/src/main/java/LogLine.java b/exercises/concept/logs-logs-logs/src/main/java/LogLine.java
new file mode 100644
index 000000000..49b5c5b21
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/src/main/java/LogLine.java
@@ -0,0 +1,13 @@
+public class LogLine {
+
+ public LogLine(String logLine) {
+ }
+
+ public LogLevel getLogLevel() {
+ throw new UnsupportedOperationException("Please implement the getLogLevel() method");
+ }
+
+ public String getOutputForShortLog() {
+ throw new UnsupportedOperationException("Please implement the getOutputForShortLog() method");
+ }
+}
diff --git a/exercises/concept/logs-logs-logs/src/test/java/LogsLogsLogsTest.java b/exercises/concept/logs-logs-logs/src/test/java/LogsLogsLogsTest.java
new file mode 100644
index 000000000..ce796efb8
--- /dev/null
+++ b/exercises/concept/logs-logs-logs/src/test/java/LogsLogsLogsTest.java
@@ -0,0 +1,127 @@
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LogsLogsLogsTest {
+ @Test
+ @Tag("task:1")
+ @DisplayName("Parsing log level TRC")
+ public void getLogLevelTrace() {
+ var logLine = new LogLine("[TRC]: Line 84 - System.out.println(\"Hello World\");");
+ assertThat(logLine.getLogLevel()).isEqualTo(LogLevel.valueOf("TRACE"));
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Parsing log level DBG")
+ public void parseLogLevelDbg() {
+ var logLine = new LogLine("[DBG]: ; expected");
+ assertThat(logLine.getLogLevel()).isEqualTo(LogLevel.valueOf("DEBUG"));
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Parsing log level INF")
+ public void parseLogLevelInf() {
+ var logLine = new LogLine("[INF]: Timezone changed");
+ assertThat(logLine.getLogLevel()).isEqualTo(LogLevel.valueOf("INFO"));
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Parsing log level WRN")
+ public void parseLogLevelWrn() {
+ var logLine = new LogLine("[WRN]: Timezone not set");
+ assertThat(logLine.getLogLevel()).isEqualTo(LogLevel.valueOf("WARNING"));
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Parsing log level ERR")
+ public void parseLogLevelErr() {
+ var logLine = new LogLine("[ERR]: Disk full");
+ assertThat(logLine.getLogLevel()).isEqualTo(LogLevel.valueOf("ERROR"));
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Parsing log level FTL")
+ public void parseLogLevelFtl() {
+ var logLine = new LogLine("[FTL]: Not enough memory");
+ assertThat(logLine.getLogLevel()).isEqualTo(LogLevel.valueOf("FATAL"));
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Parsing unknown log level XYZ")
+ public void parseLogLevelXyz() {
+ var logLine = new LogLine("[XYZ]: Gibberish message.. beep boop..");
+ assertThat(logLine.getLogLevel()).isEqualTo(LogLevel.valueOf("UNKNOWN"));
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Parsing unknown log level ABC")
+ public void parseLogLevelAbc() {
+ var logLine = new LogLine("[ABC]: Gibberish message.. beep boop..");
+ assertThat(logLine.getLogLevel()).isEqualTo(LogLevel.valueOf("UNKNOWN"));
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Get short log output for log level UNKNOWN")
+ public void getShortLogOutputUnknown() {
+ var logLine = new LogLine("[ABC]: We're no strangers to love");
+ assertThat(logLine.getOutputForShortLog()).isEqualTo("0:We're no strangers to love");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Get short log output for log level TRACE")
+ public void getShortLogOutputTrace() {
+ var logLine = new LogLine("[TRC]: You know the rules and so do I");
+ assertThat(logLine.getOutputForShortLog()).isEqualTo("1:You know the rules and so do I");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Get short log output for log level DEBUG")
+ public void getShortLogOutputDebug() {
+ var logLine = new LogLine("[DBG]: A full commitment's what I'm thinking of");
+ assertThat(logLine.getOutputForShortLog()).isEqualTo("2:A full commitment's what I'm thinking of");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Get short log output for log level INFO")
+ public void getShortLogOutputInfo() {
+ var logLine = new LogLine("[INF]: You wouldn't get this from any other guy");
+ assertThat(logLine.getOutputForShortLog()).isEqualTo("4:You wouldn't get this from any other guy");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Get short log output for log level WARNING")
+ public void getShortLogOutputWarning() {
+ var logLine = new LogLine("[WRN]: I just wanna tell you how I'm feeling");
+ assertThat(logLine.getOutputForShortLog()).isEqualTo("5:I just wanna tell you how I'm feeling");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Get short log output for log level ERROR")
+ public void getShortLogOutputError() {
+ var logLine = new LogLine("[ERR]: Gotta make you understand");
+ assertThat(logLine.getOutputForShortLog()).isEqualTo("6:Gotta make you understand");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Get short log output for log level FATAL")
+ public void getShortLogOutputFatal() {
+ var logLine = new LogLine("[FTL]: Never gonna give you up");
+ assertThat(logLine.getOutputForShortLog()).isEqualTo("42:Never gonna give you up");
+ }
+}
diff --git a/exercises/concept/need-for-speed/.docs/hints.md b/exercises/concept/need-for-speed/.docs/hints.md
index 7228502c4..ea0d20c3d 100644
--- a/exercises/concept/need-for-speed/.docs/hints.md
+++ b/exercises/concept/need-for-speed/.docs/hints.md
@@ -28,11 +28,9 @@
## 6. Check if a remote control car can finish a race
-- Solving this is probably best done by [repeatedly driving the car][while].
+- Try applying a formula that compares the distance and speed against the battery and battery drain.
- Remember that the car has a method to retrieve the distance it has driven.
-- Consider what to do when the battery has been drained before reaching the finish line.
[constructor-syntax]: https://docs.oracle.com/javase/tutorial/java/javaOO/constructors.html
[instance-constructors]: https://docs.oracle.com/javase/tutorial/java/javaOO/objectcreation.html
-[while]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/while.html
[fields]: https://docs.oracle.com/javase/tutorial/java/javaOO/variables.html
diff --git a/exercises/concept/need-for-speed/.docs/instructions.md b/exercises/concept/need-for-speed/.docs/instructions.md
index e784ab0ea..e5c478efa 100644
--- a/exercises/concept/need-for-speed/.docs/instructions.md
+++ b/exercises/concept/need-for-speed/.docs/instructions.md
@@ -1,6 +1,6 @@
# Instructions
-In this exercise you'll be organizing races between various types of remote controlled cars. Each car has its own speed and battery drain characteristics.
+In this exercise, you'll be organizing races between various types of remote controlled cars. Each car has its own speed and battery drain characteristics.
Cars start with full (100%) batteries. Each time you drive the car using the remote control, it covers the car's speed in meters and decreases the remaining battery percentage by its battery drain.
@@ -8,11 +8,11 @@ If a car's battery is below its battery drain percentage, you can't drive the ca
Each race track has its own distance. Cars are tested by checking if they can finish the track without running out of battery.
-You have six tasks, each of which will work with remote controller car instances.
+You have six tasks, each of which will work with remote controlled car instances.
## 1. Creating a remote controlled car
-Allow creating a remote controller car by defining a constructor for the `NeedForSpeed` class that takes the speed of the car in meters and the battery drain percentage as its two parameters (both of type `int`):
+Allow creating a remote controlled car by defining a constructor for the `NeedForSpeed` class that takes the speed of the car in meters and the battery drain percentage as its two parameters (both of type `int`):
```java
int speed = 5;
@@ -31,7 +31,7 @@ var raceTrack = new RaceTrack(distance);
## 3. Drive the car
-Implement the `NeedForSpeed.drive()` method that updates the number of meters driven based on the car's speed. Also implement the `NeedForSpeed.distanceDriven()` method to return the number of meters driven by the car:
+Implement the `NeedForSpeed.drive()` method that updates the number of meters driven based on the car's speed. Also, implement the `NeedForSpeed.distanceDriven()` method to return the number of meters driven by the car:
```java
int speed = 5;
@@ -45,7 +45,7 @@ car.distanceDriven();
## 4. Check for a drained battery
-Update the `NeedForSpeed.drive()` method to drain the battery based on the car's battery drain. Also implement the `NeedForSpeed.batteryDrained()` method that indicates if the battery is drained:
+Update the `NeedForSpeed.drive()` method to drain the battery based on the car's battery drain. Also, implement the `NeedForSpeed.batteryDrained()` method that indicates if the battery is drained:
```java
int speed = 5;
@@ -70,21 +70,22 @@ car.distanceDriven();
## 6. Check if a remote control car can finish a race
-To finish a race, a car has to be able to drive the race's distance. This means not draining its battery before having crossed the finish line. Implement the `RaceTrack.tryFinishTrack()` method that takes a `NeedForSpeed` instance as its parameter and returns `true` if the car can finish the race; otherwise, return `false`. To see if the car can finish the race, you should try to drive the car until either you reach the end of the track or the battery drains:
+To finish a race, a car has to be able to drive the race's distance. This means not draining its battery before having crossed the finish line. Implement the `RaceTrack.canFinishRace()` method that takes a `NeedForSpeed` instance as its parameter and returns `true` if the car can finish the race; otherwise, return `false`:
```java
int speed = 5;
int batteryDrain = 2;
var car = new NeedForSpeed(speed, batteryDrain);
-int distance = 100;
-var race = new RaceTrack(distance);
+int distance1 = 100;
+var race1 = new RaceTrack(distance1);
-car.distanceDriven()
-// => 0
+int distance2 = 300;
+var race2 = new RaceTrack(distance2);
-race.tryFinishTrack(car);
+race1.canFinishRace(car);
// => true
-car.distanceDriven()
-// => 100
+race2.canFinishRace(car);
+// => false
+```
diff --git a/exercises/concept/need-for-speed/.docs/introduction.md b/exercises/concept/need-for-speed/.docs/introduction.md
index fee719096..5a63e3196 100644
--- a/exercises/concept/need-for-speed/.docs/introduction.md
+++ b/exercises/concept/need-for-speed/.docs/introduction.md
@@ -1,6 +1,10 @@
# Introduction
-Creating an instance of a _class_ is done by calling its _constructor_ through the `new` operator. A constructor is a special type of method whose goal is to initialize a newly created instance. Constructors look like regular methods, but without a return type and with a name that matches the classes' name.
+## Constructors
+
+Creating an instance of a _class_ is done by calling its _constructor_ through the `new` operator.
+A constructor is a special type of method whose goal is to initialize a newly created instance.
+Constructors look like regular methods, but without a return type and with a name that matches the class's name.
```java
class Library {
@@ -16,7 +20,9 @@ class Library {
var library = new Library();
```
-Like regular methods, constructors can have parameters. Constructor parameters are usually stored as (private) fields to be accessed later, or else used in some one-off calculation. Arguments can be passed to constructors just like passing arguments to regular methods.
+Like regular methods, constructors can have parameters.
+Constructor parameters are usually stored as (private) fields to be accessed later, or else used in some one-off calculation.
+Arguments can be passed to constructors just like passing arguments to regular methods.
```java
class Building {
diff --git a/exercises/concept/need-for-speed/.docs/introduction.md.tpl b/exercises/concept/need-for-speed/.docs/introduction.md.tpl
new file mode 100644
index 000000000..dcf4a0486
--- /dev/null
+++ b/exercises/concept/need-for-speed/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:constructors}
diff --git a/exercises/concept/need-for-speed/.meta/config.json b/exercises/concept/need-for-speed/.meta/config.json
index a77ee42c5..6db5778f5 100644
--- a/exercises/concept/need-for-speed/.meta/config.json
+++ b/exercises/concept/need-for-speed/.meta/config.json
@@ -1,5 +1,7 @@
{
- "blurb": "Learn about classes by creating cars.",
+ "authors": [
+ "ystromm"
+ ],
"contributors": [
"mirkoperillo"
],
@@ -12,9 +14,10 @@
],
"exemplar": [
".meta/src/reference/java/NeedForSpeed.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
- "authors": [
- "ystromm"
- ]
+ "blurb": "Learn about classes by creating cars."
}
diff --git a/exercises/concept/need-for-speed/.meta/design.md b/exercises/concept/need-for-speed/.meta/design.md
index f99cef1f7..fdf7b2924 100644
--- a/exercises/concept/need-for-speed/.meta/design.md
+++ b/exercises/concept/need-for-speed/.meta/design.md
@@ -2,29 +2,34 @@
## Learning objectives
-- Know what classes are.
-- Know what encapsulation is.
-- Know what fields are.
-- Know how to create an object.
-- Know how to update state through methods.
-- Know about the `void` type.
+- Know what constructors are.
+- Know how to create a constructor.
+- Know how to initialize a new instance of a class.
+- Know how to differentiate them with normal methods.
## Out of scope
-- Reference equality.
-- Constructors.
-- Interfaces.
-- Inheritance.
-- Finalizers.
- Method overloading.
+- No arguments constructor.
## Concepts
-- `classes`: know what classes are; know what encapsulation is; know what fields are; know how to create an object; know how to update state through methods; know about the `void` type.
+- `constructors`: Know how to create `constructors` and what they are.
## Prerequisites
- `basics`: know how to define a basic class with basic methods.
-- `strings`: know how to do basic string interpolation.
-- `numbers`: know how to compare numbers.
- `conditionals`: know how to do conditional logic.
+- `classes`: know how classes work.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the student used a loop in the `canFinishRace()` method, encourage it to explore a different approach.
+- `actionable`: If the student used a conditional statement like `if/else` or ternary expressions in the `batteryDrained` method, encourage it to explore a different approach.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/need-for-speed/.meta/src/reference/java/NeedForSpeed.java b/exercises/concept/need-for-speed/.meta/src/reference/java/NeedForSpeed.java
index e3852f0dd..f499e9fe5 100644
--- a/exercises/concept/need-for-speed/.meta/src/reference/java/NeedForSpeed.java
+++ b/exercises/concept/need-for-speed/.meta/src/reference/java/NeedForSpeed.java
@@ -21,6 +21,18 @@ public int distanceDriven() {
return distance;
}
+ public int getSpeed() {
+ return speed;
+ }
+
+ public int getBatteryDrain() {
+ return batteryDrain;
+ }
+
+ public int getCurrentBattery() {
+ return battery;
+ }
+
public void drive() {
if (!batteryDrained()) {
battery -= batteryDrain;
@@ -36,15 +48,7 @@ class RaceTrack {
this.distance = distance;
}
- public boolean tryFinishTrack(NeedForSpeed car) {
- while (car.distanceDriven() < distance) {
- if (car.batteryDrained()) {
- return false;
- }
-
- car.drive();
- }
-
- return true;
+ public boolean canFinishRace(NeedForSpeed car) {
+ return ((double) distance / car.getSpeed()) <= (car.getCurrentBattery() / car.getBatteryDrain());
}
}
diff --git a/exercises/concept/need-for-speed/build.gradle b/exercises/concept/need-for-speed/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/need-for-speed/build.gradle
+++ b/exercises/concept/need-for-speed/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/need-for-speed/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/need-for-speed/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/need-for-speed/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/need-for-speed/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/need-for-speed/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/need-for-speed/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/need-for-speed/gradlew b/exercises/concept/need-for-speed/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/need-for-speed/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/need-for-speed/gradlew.bat b/exercises/concept/need-for-speed/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/need-for-speed/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/need-for-speed/src/main/java/NeedForSpeed.java b/exercises/concept/need-for-speed/src/main/java/NeedForSpeed.java
index a384f4b4d..b762a2edc 100644
--- a/exercises/concept/need-for-speed/src/main/java/NeedForSpeed.java
+++ b/exercises/concept/need-for-speed/src/main/java/NeedForSpeed.java
@@ -1,5 +1,7 @@
class NeedForSpeed {
- // TODO: define the constructor for the 'NeedForSpeed' class
+ NeedForSpeed(int speed, int batteryDrain) {
+ throw new UnsupportedOperationException("Please implement the NeedForSpeed constructor");
+ }
public boolean batteryDrained() {
throw new UnsupportedOperationException("Please implement the NeedForSpeed.batteryDrained() method");
@@ -19,9 +21,11 @@ public static NeedForSpeed nitro() {
}
class RaceTrack {
- // TODO: define the constructor for the 'RaceTrack' class
+ RaceTrack(int distance) {
+ throw new UnsupportedOperationException("Please implement the RaceTrack constructor");
+ }
- public boolean tryFinishTrack(NeedForSpeed car) {
- throw new UnsupportedOperationException("Please implement the RaceTrack.tryFinishTrack() method");
+ public boolean canFinishRace(NeedForSpeed car) {
+ throw new UnsupportedOperationException("Please implement the RaceTrack.canFinishRace() method");
}
}
diff --git a/exercises/concept/need-for-speed/src/test/java/NeedForSpeedTest.java b/exercises/concept/need-for-speed/src/test/java/NeedForSpeedTest.java
index ff6d177c5..2848f34fb 100644
--- a/exercises/concept/need-for-speed/src/test/java/NeedForSpeedTest.java
+++ b/exercises/concept/need-for-speed/src/test/java/NeedForSpeedTest.java
@@ -1,10 +1,16 @@
-import static org.assertj.core.api.Assertions.assertThat;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.*;
-import org.junit.Test;
public class NeedForSpeedTest {
+
@Test
- public void new_remote_control_car_has_not_driven_any_distance() {
+ @Tag("task:3")
+ @DisplayName("The distanceDriven method returns 0 on a new car")
+ public void newRemoteControlCarHasNotDrivenAnyDistance() {
int speed = 10;
int batteryDrain = 2;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -13,7 +19,9 @@ public void new_remote_control_car_has_not_driven_any_distance() {
}
@Test
- public void drive_increases_distance_driven_with_speed() {
+ @Tag("task:3")
+ @DisplayName("The distanceDriven method returns 5 after driving once")
+ public void driveIncreasesDistanceDrivenWithSpeed() {
int speed = 5;
int batteryDrain = 1;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -24,7 +32,9 @@ public void drive_increases_distance_driven_with_speed() {
}
@Test
- public void drive_does_not_increase_distance_driven_when_battery_drained() {
+ @Tag("task:3")
+ @DisplayName("The distanceDriven method returns the correct distance after driving multiple times")
+ public void driveDoesNotIncreaseDistanceDrivenWhenBatteryDrained() {
int speed = 9;
int batteryDrain = 50;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -40,7 +50,9 @@ public void drive_does_not_increase_distance_driven_when_battery_drained() {
}
@Test
- public void new_remote_control_car_battery_is_not_drained() {
+ @Tag("task:4")
+ @DisplayName("The batteryDrained method returns false when car never drove")
+ public void newRemoteControlCarBatteryIsNotDrained() {
int speed = 15;
int batteryDrain = 3;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -49,7 +61,20 @@ public void new_remote_control_car_battery_is_not_drained() {
}
@Test
- public void drive_to_almost_drain_battery() {
+ @Tag("task:4")
+ @DisplayName("The batteryDrained method returns false when there's not enough battery")
+ public void newRemoteControlCarThatCanOnlyDriveOnce() {
+ var car = new NeedForSpeed(1, 99);
+ car.drive();
+ assertThat(car.batteryDrained()).isTrue();
+ // Ensure that the car was driven once
+ assertThat(car.distanceDriven()).isEqualTo(1);
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("The batteryDrained method returns false when car battery did not completely drain after driving")
+ public void driveToAlmostDrainBattery() {
int speed = 2;
int batteryDrain = 1;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -63,7 +88,9 @@ public void drive_to_almost_drain_battery() {
}
@Test
- public void drive_until_battery_is_drained() {
+ @Tag("task:4")
+ @DisplayName("The batteryDrained method returns true when battery completely drained after driving")
+ public void driveUntilBatteryIsDrained() {
int speed = 2;
int batteryDrain = 1;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -77,26 +104,34 @@ public void drive_until_battery_is_drained() {
}
@Test
- public void nitro_car_has_not_driven_any_distance() {
+ @Tag("task:5")
+ @DisplayName("The distanceDriven method returns 0 on a new nitro car")
+ public void nitroCarHasNotDrivenAnyDistance() {
var car = NeedForSpeed.nitro();
assertThat(car.distanceDriven()).isEqualTo(0);
}
@Test
- public void nitro_car_has_battery_not_drained() {
+ @Tag("task:5")
+ @DisplayName("The batteryDrained method returns false when nitro car never drove")
+ public void nitroCarHasBatteryNotDrained() {
var car = NeedForSpeed.nitro();
assertThat(car.batteryDrained()).isFalse();
}
@Test
- public void nitro_car_has_correct_speed() {
+ @Tag("task:5")
+ @DisplayName("The distanceDriven method returns the correct distance after driving a nitro car")
+ public void nitroCarHasCorrectSpeed() {
var car = NeedForSpeed.nitro();
car.drive();
assertThat(car.distanceDriven()).isEqualTo(50);
}
@Test
- public void nitro_has_correct_battery_drain() {
+ @Tag("task:5")
+ @DisplayName("The batteryDrained method returns false when nitro battery did not completely drain after driving")
+ public void nitroHasCorrectBatteryDrain() {
var car = NeedForSpeed.nitro();
// The battery is almost drained
@@ -105,15 +140,26 @@ public void nitro_has_correct_battery_drain() {
}
assertThat(car.batteryDrained()).isFalse();
+ }
- // Drain the battery
- car.drive();
+ @Test
+ @Tag("task:5")
+ @DisplayName("The batteryDrained method returns true when nitro battery completely drained after driving")
+ public void nitroBatteryCompletelyDrains() {
+ var car = NeedForSpeed.nitro();
+
+ // The battery is drained
+ for (var i = 0; i < 25; i++) {
+ car.drive();
+ }
assertThat(car.batteryDrained()).isTrue();
}
@Test
- public void car_can_finish_with_car_that_can_easily_finish() {
+ @Tag("task:6")
+ @DisplayName("The canFinishRace method returns true when car can finish a race")
+ public void carCanFinishWithCarThatCanEasilyFinish() {
int speed = 10;
int batteryDrain = 2;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -121,11 +167,13 @@ public void car_can_finish_with_car_that_can_easily_finish() {
int distance = 100;
var race = new RaceTrack(distance);
- assertThat(race.tryFinishTrack(car)).isTrue();
+ assertThat(race.canFinishRace(car)).isTrue();
}
@Test
- public void car_can_finish_with_car_that_can_just_finish() {
+ @Tag("task:6")
+ @DisplayName("The canFinishRace method returns true when car can just finish a race")
+ public void carCanFinishWithCarThatCanJustFinish() {
int speed = 2;
int batteryDrain = 10;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -133,11 +181,13 @@ public void car_can_finish_with_car_that_can_just_finish() {
int distance = 20;
var race = new RaceTrack(distance);
- assertThat(race.tryFinishTrack(car)).isTrue();
+ assertThat(race.canFinishRace(car)).isTrue();
}
@Test
- public void car_can_finish_with_car_that_just_cannot_finish() {
+ @Tag("task:6")
+ @DisplayName("The canFinishRace method returns false when car just cannot finish a race")
+ public void carCanFinishWithCarThatJustCannotFinish() {
int speed = 3;
int batteryDrain = 20;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -145,11 +195,13 @@ public void car_can_finish_with_car_that_just_cannot_finish() {
int distance = 16;
var race = new RaceTrack(distance);
- assertThat(race.tryFinishTrack(car)).isFalse();
+ assertThat(race.canFinishRace(car)).isFalse();
}
@Test
- public void car_can_finish_with_car_that_cannot_finish() {
+ @Tag("task:6")
+ @DisplayName("The canFinishRace method returns false when car cannot finish a race")
+ public void carCanFinishWithCarThatCannotFinish() {
int speed = 1;
int batteryDrain = 20;
var car = new NeedForSpeed(speed, batteryDrain);
@@ -157,7 +209,7 @@ public void car_can_finish_with_car_that_cannot_finish() {
int distance = 678;
var race = new RaceTrack(distance);
- assertThat(race.tryFinishTrack(car)).isFalse();
+ assertThat(race.canFinishRace(car)).isFalse();
}
}
diff --git a/exercises/concept/remote-control-competition/.docs/hints.md b/exercises/concept/remote-control-competition/.docs/hints.md
index 1b48b5515..fbd4ebfa7 100644
--- a/exercises/concept/remote-control-competition/.docs/hints.md
+++ b/exercises/concept/remote-control-competition/.docs/hints.md
@@ -15,7 +15,7 @@
## 3. Allow the production cars to be ranked
- See [this discussion][sort] of sorting.
-- See [here][comparable] for default comparison of objects.
+- See [Comparable's Javadocs][comparable] for default comparison of objects.
[interfaces]: https://docs.oracle.com/javase/tutorial/java/concepts/interface.html
[sort]: https://docs.oracle.com/javase/7/docs/api/java/util/Collections.html
diff --git a/exercises/concept/remote-control-competition/.docs/instructions.md b/exercises/concept/remote-control-competition/.docs/instructions.md
index 65789f7cc..bbf46f7f8 100644
--- a/exercises/concept/remote-control-competition/.docs/instructions.md
+++ b/exercises/concept/remote-control-competition/.docs/instructions.md
@@ -2,46 +2,69 @@
In this exercise you will be doing some more work on remote control cars.
-An experimental car has been developed and the test track needs to be adapted to handle both production and experimental models. The two types of car have already been built and you need to find a way to deal with them both on the test track.
+An experimental car has been developed and the test track needs to be adapted to handle both production and experimental models.
+The two types of car have already been built and you need to find a way to deal with them both on the test track.
-In addition, production cars are beginning to have some success. The team boss is keen to maintain the competitive spirit by publishing a ranking of the production cars.
+In addition, production cars are beginning to have some success.
+The team boss is keen to maintain the competitive spirit by publishing a ranking of the production cars.
-## 1. Enable cars to be driven on the same test track
+## 1. Implement the Interface
-Please add a method to the `RemoteControlCar` interface to encapsulate the behavior of `drive()` for the two types of car.
+Please add two methods to the `RemoteControlCar` interface:
-```java
-TestTrack.race(new ProductionRemoteControlCar());
-TestTrack.race(new ExperimentalRemoteControlCar());
-// this should execute without an exception being thrown
-```
+- `drive()`, returning nothing, and
+- `getDistanceTravelled()`, returning an `int`.
-## 2. Enable the distance travelled by different models on the test track to be compared
+Then make `ProductionRemoteControlCar` and `ExperimentalRemoteControlCar` implement the `RemoteControlCar` interface.
+This includes implementing all methods required by the interface.
-Please add a method to the `RemoteControlCar` interface to encapsulate the behavior of the `getDistanceTravelled()` method for the two types of car.
+## 2. Drive
+
+Each call of `.drive()` should make the car travel a certain distance:
+
+- a `ProductionRemoteControlCar` drives 10 units,
+- an `ExperimentalRemoteControlCar` drives 20 units.
+
+The `.getDistanceTravelled()` method should return the number of units that the car has travelled in its lifetime.
```java
ProductionRemoteControlCar prod = new ProductionRemoteControlCar();
-TestTrack.race(prod);
-ExperimentalRemoteControlCar exp = new ExperimentalRemoteControlCar();
-TestTrack.race(exp);
+prod.drive();
prod.getDistanceTravelled();
// => 10
+
+ExperimentalRemoteControlCar exp = new ExperimentalRemoteControlCar();
+exp.drive();
exp.getDistanceTravelled();
// => 20
```
-## 3. Allow the production cars to be ranked
+## 3. Race
+
+Implement the `TestTrack.race(RemoteControlCar car)` method in which the `car`s get to `drive()`.
+
+```java
+TestTrack.race(new ProductionRemoteControlCar());
+TestTrack.race(new ExperimentalRemoteControlCar());
+// this should execute without an exception being thrown
+```
+
+## 4. Allow the production cars to be sorted
-Please implement the `Comparable` interface in the `ProductionRemoteControlCar` class. The default sort order for cars should be ascending order of victories.
+Please implement the `Comparable` interface in the `ProductionRemoteControlCar` class.
+The default sort order for cars should be descending order of victories.
-Implement the static `TestTrack.getRankedCars()` to return the cars passed in, sorted in ascending order of number of victories.
+Implement the static `TestTrack.getRankedCars()` to return the cars passed in, sorted in descending order of number of victories.
```java
ProductionRemoteControlCar prc1 = new ProductionRemoteControlCar();
ProductionRemoteControlCar prc2 = new ProductionRemoteControlCar();
-prc1.setNumberOfVictories(3);
-prc2.setNumberOfVictories(2);
-int rankings = TestTrack.getRankedCars(prc1, prc2);
-// => rankings[1] == prc1
+prc1.setNumberOfVictories(2);
+prc2.setNumberOfVictories(3);
+List unsortedCars = new ArrayList<>();
+unsortedCars.add(prc1);
+unsortedCars.add(prc2);
+List rankings = TestTrack.getRankedCars(unsortedCars);
+// => rankings.get(0) == prc2
+// => rankings.get(1) == prc1
```
diff --git a/exercises/concept/remote-control-competition/.docs/introduction.md b/exercises/concept/remote-control-competition/.docs/introduction.md
index 92e890693..d7cecddb4 100644
--- a/exercises/concept/remote-control-competition/.docs/introduction.md
+++ b/exercises/concept/remote-control-competition/.docs/introduction.md
@@ -1,6 +1,9 @@
# Introduction
-An interface is a type containing members defining a group of related functionality. It distances the uses of a class from the implementation allowing multiple different implementations or support for some generic behavior such as formatting, comparison or conversion.
+## Interfaces
+
+An interface is a type containing members defining a group of related functionality.
+It distances the uses of a class from the implementation allowing multiple different implementations or support for some generic behavior such as formatting, comparison or conversion.
The syntax of an interface is similar to that of a class except that methods appear as the signature only and no body is provided.
@@ -24,7 +27,7 @@ public class ItalianTraveller implements Language, Cloneable {
// from Cloneable interface
public Object clone() {
- ItalianTaveller it = new ItalianTaveller();
+ ItalianTraveller it = new ItalianTraveller();
return it;
}
}
@@ -34,4 +37,5 @@ All operations defined by the interface must be implemented by the implementing
Interfaces usually contain instance methods.
-An example of an interface found in the Java Class Library, apart from `Cloneable` illustrated above, is `Comparable`. The `Comparable` interface can be implemented where a default generic sort order in collections is required.
+An example of an interface found in the Java Class Library, apart from `Cloneable` illustrated above, is `Comparable`.
+The `Comparable` interface can be implemented where a default generic sort order in collections is required.
diff --git a/exercises/concept/remote-control-competition/.docs/introduction.md.tpl b/exercises/concept/remote-control-competition/.docs/introduction.md.tpl
new file mode 100644
index 000000000..641fb729b
--- /dev/null
+++ b/exercises/concept/remote-control-competition/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:interfaces}
diff --git a/exercises/concept/remote-control-competition/.meta/config.json b/exercises/concept/remote-control-competition/.meta/config.json
index fb13fe5a9..a7d0a7057 100644
--- a/exercises/concept/remote-control-competition/.meta/config.json
+++ b/exercises/concept/remote-control-competition/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Learn about interfaces by working on cars.",
"authors": [
"mikedamay"
],
@@ -18,6 +17,10 @@
".meta/src/reference/java/ProductionRemoteControlCar.java",
".meta/src/reference/java/RemoteControlCar.java",
".meta/src/reference/java/TestTrack.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
- }
+ },
+ "blurb": "Learn about interfaces by working on cars."
}
diff --git a/exercises/concept/remote-control-competition/.meta/src/reference/java/ProductionRemoteControlCar.java b/exercises/concept/remote-control-competition/.meta/src/reference/java/ProductionRemoteControlCar.java
index 68067dfe3..b84aff013 100644
--- a/exercises/concept/remote-control-competition/.meta/src/reference/java/ProductionRemoteControlCar.java
+++ b/exercises/concept/remote-control-competition/.meta/src/reference/java/ProductionRemoteControlCar.java
@@ -1,10 +1,10 @@
class ProductionRemoteControlCar implements RemoteControlCar, Comparable {
private int distanceTravelled;
- private int numberofFictories;
+ private int numberOfVictories;
public int compareTo(ProductionRemoteControlCar other) {
- return Integer.compare(this.getNumberOfVictories(), other.getNumberOfVictories());
+ return Integer.compare(other.getNumberOfVictories(), this.getNumberOfVictories());
}
public void drive() {
@@ -16,10 +16,10 @@ public int getDistanceTravelled() {
}
public int getNumberOfVictories() {
- return numberofFictories;
+ return numberOfVictories;
}
- public void setNumberOfVictories(int numberofFictories) {
- this.numberofFictories = numberofFictories;
+ public void setNumberOfVictories(int numberOfVictories) {
+ this.numberOfVictories = numberOfVictories;
}
}
diff --git a/exercises/concept/remote-control-competition/.meta/src/reference/java/TestTrack.java b/exercises/concept/remote-control-competition/.meta/src/reference/java/TestTrack.java
index 088132928..dfc58cf7b 100644
--- a/exercises/concept/remote-control-competition/.meta/src/reference/java/TestTrack.java
+++ b/exercises/concept/remote-control-competition/.meta/src/reference/java/TestTrack.java
@@ -8,13 +8,9 @@ public static void race(RemoteControlCar car) {
car.drive();
}
- public static List getRankedCars(ProductionRemoteControlCar prc1,
- ProductionRemoteControlCar prc2) {
- List rankings = new ArrayList<>();
- rankings.add(prc1);
- rankings.add(prc1);
- Collections.sort(rankings);
-
- return rankings;
+ public static List getRankedCars(List cars) {
+ List sorted = new ArrayList<>(cars);
+ Collections.sort(sorted);
+ return sorted;
}
}
diff --git a/exercises/concept/remote-control-competition/build.gradle b/exercises/concept/remote-control-competition/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/remote-control-competition/build.gradle
+++ b/exercises/concept/remote-control-competition/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/remote-control-competition/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/remote-control-competition/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/remote-control-competition/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/remote-control-competition/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/remote-control-competition/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/remote-control-competition/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/remote-control-competition/gradlew b/exercises/concept/remote-control-competition/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/remote-control-competition/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/remote-control-competition/gradlew.bat b/exercises/concept/remote-control-competition/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/remote-control-competition/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/remote-control-competition/src/main/java/ProductionRemoteControlCar.java b/exercises/concept/remote-control-competition/src/main/java/ProductionRemoteControlCar.java
index 460f5e160..dd3472433 100644
--- a/exercises/concept/remote-control-competition/src/main/java/ProductionRemoteControlCar.java
+++ b/exercises/concept/remote-control-competition/src/main/java/ProductionRemoteControlCar.java
@@ -12,7 +12,7 @@ public int getNumberOfVictories() {
throw new UnsupportedOperationException("Please implement the ProductionRemoteControlCar.getNumberOfVictories() method");
}
- public void setNumberOfVictories(int numberofVictories) {
+ public void setNumberOfVictories(int numberOfVictories) {
throw new UnsupportedOperationException("Please implement the ProductionRemoteControlCar.setNumberOfVictories() method");
}
}
diff --git a/exercises/concept/remote-control-competition/src/main/java/TestTrack.java b/exercises/concept/remote-control-competition/src/main/java/TestTrack.java
index 48686e783..95e59ddf3 100644
--- a/exercises/concept/remote-control-competition/src/main/java/TestTrack.java
+++ b/exercises/concept/remote-control-competition/src/main/java/TestTrack.java
@@ -6,8 +6,7 @@ public static void race(RemoteControlCar car) {
throw new UnsupportedOperationException("Please implement the (static) TestTrack.race() method");
}
- public static List getRankedCars(ProductionRemoteControlCar prc1,
- ProductionRemoteControlCar prc2) {
+ public static List getRankedCars(List cars) {
throw new UnsupportedOperationException("Please implement the (static) TestTrack.getRankedCars() method");
}
}
diff --git a/exercises/concept/remote-control-competition/src/test/java/RemoteControlCarTest.java b/exercises/concept/remote-control-competition/src/test/java/RemoteControlCarTest.java
index 1cbf5faf4..45461f77c 100644
--- a/exercises/concept/remote-control-competition/src/test/java/RemoteControlCarTest.java
+++ b/exercises/concept/remote-control-competition/src/test/java/RemoteControlCarTest.java
@@ -1,11 +1,52 @@
-import org.junit.Test;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class RemoteControlCarTest {
@Test
+ @Tag("task:1")
+ @DisplayName("The ProductionRemoteControlCar is an instance of the RemoteControlCar interface")
+ public void productionRccIsRemoteControlCar() {
+ ProductionRemoteControlCar productionCar = new ProductionRemoteControlCar();
+ assertThat(productionCar instanceof RemoteControlCar).isTrue();
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("The ExperimentalRemoteControlCar is an instance of the RemoteControlCar interface")
+ public void experimentalRccIsRemoteControlCar() {
+ ExperimentalRemoteControlCar experimentalCar = new ExperimentalRemoteControlCar();
+ assertThat(experimentalCar instanceof RemoteControlCar).isTrue();
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("The getDistanceTravelled method of the ProductionRemoteControlCar returns 10 after driving once")
+ public void productionCarTravels10UnitsPerDrive() {
+ ProductionRemoteControlCar car = new ProductionRemoteControlCar();
+ assertThat(car.getDistanceTravelled()).isEqualTo(0);
+ car.drive();
+ assertThat(car.getDistanceTravelled()).isEqualTo(10);
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("The getDistanceTravelled method of the ExperimentalRemoteControlCar returns 20 after driving once")
+ public void experimentalCarTravels20UnitsPerDrive() {
+ ExperimentalRemoteControlCar car = new ExperimentalRemoteControlCar();
+ assertThat(car.getDistanceTravelled()).isEqualTo(0);
+ car.drive();
+ assertThat(car.getDistanceTravelled()).isEqualTo(20);
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("The TestTrack.race method uses the drive method on the remote control car")
public void race() {
ProductionRemoteControlCar productionCar = new ProductionRemoteControlCar();
ExperimentalRemoteControlCar experimentalCar = new ExperimentalRemoteControlCar();
@@ -17,17 +58,53 @@ public void race() {
}
@Test
- public void rankCars() {
+ @Tag("task:4")
+ @DisplayName("The ProductionRemoteControlCar implements the Comparable interface")
+ public void ensureCarsAreComparable() {
+ assertThat(Comparable.class).isAssignableFrom(ProductionRemoteControlCar.class);
+ }
+
+ private static ProductionRemoteControlCar getCarWithVictories(int numberOfVictories) {
ProductionRemoteControlCar prc1 = new ProductionRemoteControlCar();
- ProductionRemoteControlCar prc2 = new ProductionRemoteControlCar();
- prc1.setNumberOfVictories(3);
- prc2.setNumberOfVictories(2);
- List rankings = TestTrack.getRankedCars(prc1, prc2);
- assertThat(rankings.get(1)).isEqualTo(prc1);
+ prc1.setNumberOfVictories(numberOfVictories);
+ return prc1;
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("The getRankedCars returns a list of two cars sorted by number of victories")
+ public void rankTwoCars() {
+ List unsortedCars = new ArrayList<>() {
+ {
+ add(getCarWithVictories(2));
+ add(getCarWithVictories(3));
+ }
+ };
+ List rankings = TestTrack.getRankedCars(unsortedCars);
+ assertThat(rankings.get(0).getNumberOfVictories()).isEqualTo(3);
+ assertThat(rankings.get(1).getNumberOfVictories()).isEqualTo(2);
}
@Test
- public void ensureCarsAreComparables() {
- assertThat(RemoteControlCar.class).isAssignableFrom(ProductionRemoteControlCar.class);
+ @Tag("task:4")
+ @DisplayName("The getRankedCars returns a list of multiple cars sorted by number of victories")
+ public void rankManyCars() {
+ List unsortedCars = new ArrayList<>() {
+ {
+ add(getCarWithVictories(0));
+ add(getCarWithVictories(3));
+ add(getCarWithVictories(5));
+ add(getCarWithVictories(7));
+ add(getCarWithVictories(2));
+ add(getCarWithVictories(1));
+ }
+ };
+ List rankings = TestTrack.getRankedCars(unsortedCars);
+ assertThat(rankings.get(0).getNumberOfVictories()).isEqualTo(7);
+ assertThat(rankings.get(1).getNumberOfVictories()).isEqualTo(5);
+ assertThat(rankings.get(2).getNumberOfVictories()).isEqualTo(3);
+ assertThat(rankings.get(3).getNumberOfVictories()).isEqualTo(2);
+ assertThat(rankings.get(4).getNumberOfVictories()).isEqualTo(1);
+ assertThat(rankings.get(5).getNumberOfVictories()).isEqualTo(0);
}
}
diff --git a/exercises/concept/salary-calculator/.docs/hints.md b/exercises/concept/salary-calculator/.docs/hints.md
index 300478550..68df6d809 100644
--- a/exercises/concept/salary-calculator/.docs/hints.md
+++ b/exercises/concept/salary-calculator/.docs/hints.md
@@ -2,15 +2,19 @@
## General
-- Check the [this][ternary-operator-first] and [this][ternary-operator-second] examples on how to use _ternary operators_.
+- Refer to examples in [this article][ternary-operator-first] and [this one][ternary-operator-second] for guidance on using _ternary operators_.
-## 1. Implement the multipliers
+## 1. Determine the salary multiplier
-- Both multiplier methods depend on a certain condition being met, you must check that before deciding what should be returned.
+- The salary multiplier depends on meeting a specific condition.
-## 2. Calculate the final salary
+## 2. Calculate the bonus for products sold
-- If a salary is greater then the maximum, you can ignore the final value and use the maximim value instead.
+- The bonus multiplier depends on meeting a specific condition.
+
+## 3. Calculate the final salary
+
+- If the salary exceeds the maximum, ignore the final value and use the maximum value instead.
[ternary-operator-first]: https://www.programiz.com/java-programming/ternary-operator
[ternary-operator-second]: https://www.baeldung.com/java-ternary-operator
diff --git a/exercises/concept/salary-calculator/.docs/instructions.md b/exercises/concept/salary-calculator/.docs/instructions.md
index 5910ce632..a33f651b0 100644
--- a/exercises/concept/salary-calculator/.docs/instructions.md
+++ b/exercises/concept/salary-calculator/.docs/instructions.md
@@ -1,44 +1,58 @@
# Instructions
-In this exercise, you'll be implementing rules for calculating the total salary of a employee in a month. The International Siderurgy Company (ISC) needs help to calculate the salary for the employees, given that different factors can alter the final wage value for each employee.
+In this exercise, you'll be implementing rules for calculating the total salary of an employee in a month.
+The International Siderurgy Company (ISC) requires assistance in calculating employee salaries, considering various factors that can impact the final wage.
-You have three tasks and you should use the ternary operator instead of if/else statements to implement them.
+You have three tasks, and you should use the ternary operator instead of if/else statements to implement them.
## 1. Determine the salary multiplier
-Implement the `multiplierPerDaysSkipped` method that returns the salary multiplier based on the number of days the employee skipped the job. A 15% penalty is applied if more than five days were skipped.
+Implement the `salaryMultiplier` method, which returns the salary multiplier based on the number of days an employee skipped work.
+Apply a 15% penalty if the employee skipped at least five days.
```java
int daysSkipped = 3;
-multiplierPerDaysSkipped(daysSkipped);
-// => 1
+salaryMultiplier(daysSkipped);
+// => 1.0
daysSkipped = 7;
-multiplierPerDaysSkipped(daysSkipped);
+salaryMultiplier(daysSkipped);
// => 0.85
```
## 2. Calculate the bonus for products sold
-Implement the `multiplierPerProductsSold` and `bonusForProductSold` methods. The ISC pays ten monetary units for each product sold, but if the employee sold more than twenty products, the multiplier is improved to thirteen. `multiplierPerProductsSold` should decide which multiplier is applied and `bonusForProductSold` should return the total bonus in monetary units.
+Implement the `bonusMultiplier` and `bonusForProductsSold` methods.
+The ISC pays ten monetary units for each product sold, and if an employee sells twenty products or more, the multiplier improves to thirteen.
+`bonusMultiplier` should determine which multiplier to apply, and `bonusForProductSold` should return the total bonus in monetary units.
```java
int productsSold = 21;
-multiplierPerProductsSold(productsSold);
+bonusMultiplier(productsSold);
// => 13
+bonusForProductsSold(productsSold);
+// => 273
productsSold = 5;
-bonusForProductSold(productsSold);
+bonusMultiplier(productsSold);
+// => 10
+bonusForProductsSold(productsSold);
// => 50
```
## 3. Calculate the final salary for the employee
-Implement the `finalSalary` method. It should be able to multiply the base salary of 1000.00 by the salary multiplier and sum the bonus and return the result, but keep in mind that salaries should be capped at 2000.00;
+Implement the `finalSalary` method.
+It should multiply the base salary of `1000.00` by the salary multiplier, add the bonus, and return the result.
+However, salaries should be capped at `2000.00`.
```java
int daysSkipped = 2;
int productsSold = 3;
finalSalary(daysSkipped, productsSold);
-// => 1030
+// => 1030.00
+
+productsSold = 90;
+finalSalary(daysSkipped, productsSold);
+// => 2000.00
```
diff --git a/exercises/concept/salary-calculator/.docs/introduction.md b/exercises/concept/salary-calculator/.docs/introduction.md
index 606435f9c..9087ad290 100644
--- a/exercises/concept/salary-calculator/.docs/introduction.md
+++ b/exercises/concept/salary-calculator/.docs/introduction.md
@@ -1,6 +1,9 @@
# Introduction
-The _ternary operator_ is a lightweight, compact alternative for simple _if/else_ statements. Usually used in (but not restricted to) return statements, it needs just one single line to make the decision, returning the left value if the expression is `true` and the right value if `false`, as follows:
+## Ternary Operators
+
+The _ternary operator_ is a lightweight, compact alternative for simple _if/else_ statements.
+Usually used in (but not restricted to) return statements, it needs just one single line to make the decision, returning the left value if the expression is `true` and the right value if `false`, as follows:
```java
boolean expr = 0 != 200;
diff --git a/exercises/concept/salary-calculator/.docs/introduction.md.tpl b/exercises/concept/salary-calculator/.docs/introduction.md.tpl
new file mode 100644
index 000000000..16bbca24e
--- /dev/null
+++ b/exercises/concept/salary-calculator/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:ternary-operators}
diff --git a/exercises/concept/salary-calculator/.meta/config.json b/exercises/concept/salary-calculator/.meta/config.json
index 716dbbe1f..5f0fd522f 100644
--- a/exercises/concept/salary-calculator/.meta/config.json
+++ b/exercises/concept/salary-calculator/.meta/config.json
@@ -1,8 +1,10 @@
{
- "blurb": "Learn about ternary operators by calculating salaries.",
"authors": [
"TalesDias"
],
+ "contributors": [
+ "smicaliz"
+ ],
"files": {
"solution": [
"src/main/java/SalaryCalculator.java"
@@ -12,6 +14,10 @@
],
"exemplar": [
".meta/src/reference/java/SalaryCalculator.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
- }
+ },
+ "blurb": "Learn about ternary operators by calculating salaries."
}
diff --git a/exercises/concept/salary-calculator/.meta/design.md b/exercises/concept/salary-calculator/.meta/design.md
index c474d4c30..b3c049f13 100644
--- a/exercises/concept/salary-calculator/.meta/design.md
+++ b/exercises/concept/salary-calculator/.meta/design.md
@@ -2,7 +2,7 @@
## Learning objectives
-- Know how to use ternary exepressions - using the `?:` operator.
+- Know how to use ternary expressions - using the `?:` operator.
## Out of scope
@@ -21,6 +21,13 @@ This exercise's prerequisites Concepts are:
## Analyzer
-This exercise could have the following rule added to the analyzer:
+This exercise could benefit from the following rules in the [analyzer]:
-- Verify that ternary operators were used.
+- `essential`: If the student did not use `ternary operators` on `salaryMultiplier`, `bonusMultiplier` or `finalSalary` methods, instruct them to do so.
+- `actionable`: If the student did not reuse the implementation of the `bonusMultiplier` method in the `bonusForProductsSold` method, instruct them to do so.
+- `actionable`: If the student did not reuse the implementation of the `salaryMultiplier` or `bonusForProductsSold` methods in the `finalSalary` method, instruct them to do so.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/salary-calculator/.meta/src/reference/java/SalaryCalculator.java b/exercises/concept/salary-calculator/.meta/src/reference/java/SalaryCalculator.java
index d8d0837b7..fac9e15c3 100644
--- a/exercises/concept/salary-calculator/.meta/src/reference/java/SalaryCalculator.java
+++ b/exercises/concept/salary-calculator/.meta/src/reference/java/SalaryCalculator.java
@@ -1,21 +1,19 @@
public class SalaryCalculator {
- public double multiplierPerDaysSkipped (int daysSkipped) {
+ public double salaryMultiplier(int daysSkipped) {
return daysSkipped < 5 ? 1 : 0.85;
}
- public int multiplierPerProductsSold (int productsSold) {
+ public int bonusMultiplier(int productsSold) {
return productsSold < 20 ? 10 : 13;
}
- public double bonusForProductSold (int productsSold) {
- return productsSold * multiplierPerProductsSold(productsSold);
+ public double bonusForProductsSold(int productsSold) {
+ return productsSold * bonusMultiplier(productsSold);
}
- public double finalSalary (int daysSkipped, int productsSold) {
-
- double finalSalary = 1000.0 * multiplierPerDaysSkipped(daysSkipped) + bonusForProductSold(productsSold);
-
+ public double finalSalary(int daysSkipped, int productsSold) {
+ double finalSalary = 1000.0 * salaryMultiplier(daysSkipped) + bonusForProductsSold(productsSold);
return finalSalary > 2000.0 ? 2000.0 : finalSalary;
}
diff --git a/exercises/concept/salary-calculator/build.gradle b/exercises/concept/salary-calculator/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/salary-calculator/build.gradle
+++ b/exercises/concept/salary-calculator/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/salary-calculator/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/salary-calculator/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/salary-calculator/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/salary-calculator/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/salary-calculator/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/salary-calculator/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/salary-calculator/gradlew b/exercises/concept/salary-calculator/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/salary-calculator/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/salary-calculator/gradlew.bat b/exercises/concept/salary-calculator/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/salary-calculator/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/salary-calculator/src/main/java/SalaryCalculator.java b/exercises/concept/salary-calculator/src/main/java/SalaryCalculator.java
index 7613ed653..e54e16f0b 100644
--- a/exercises/concept/salary-calculator/src/main/java/SalaryCalculator.java
+++ b/exercises/concept/salary-calculator/src/main/java/SalaryCalculator.java
@@ -1,14 +1,14 @@
public class SalaryCalculator {
- public double multiplierPerDaysSkipped(int daysSkipped) {
- throw new UnsupportedOperationException("Please implement the SalaryCalculator.multiplierPerDaysSkipped() method");
+ public double salaryMultiplier(int daysSkipped) {
+ throw new UnsupportedOperationException("Please implement the SalaryCalculator.salaryMultiplier() method");
}
- public int multiplierPerProductsSold(int productsSold) {
- throw new UnsupportedOperationException("Please implement the SalaryCalculator.multiplierPerProductsSold() method");
+ public int bonusMultiplier(int productsSold) {
+ throw new UnsupportedOperationException("Please implement the SalaryCalculator.bonusMultiplier() method");
}
- public double bonusForProductSold(int productsSold) {
- throw new UnsupportedOperationException("Please implement the SalaryCalculator.bonusForProductSold() method");
+ public double bonusForProductsSold(int productsSold) {
+ throw new UnsupportedOperationException("Please implement the SalaryCalculator.bonusForProductsSold() method");
}
public double finalSalary(int daysSkipped, int productsSold) {
diff --git a/exercises/concept/salary-calculator/src/test/java/SalaryCalculatorTest.java b/exercises/concept/salary-calculator/src/test/java/SalaryCalculatorTest.java
index c8e098df5..efe3a4c34 100644
--- a/exercises/concept/salary-calculator/src/test/java/SalaryCalculatorTest.java
+++ b/exercises/concept/salary-calculator/src/test/java/SalaryCalculatorTest.java
@@ -1,72 +1,94 @@
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
-
public class SalaryCalculatorTest {
-
+
public SalaryCalculator calculator;
-
- @Before
+ @BeforeEach
public void setUp() {
calculator = new SalaryCalculator();
}
@Test
- public void regularSalary() {
- assertThat(calculator.finalSalary(0, 0)).isEqualTo(1000.0);
+ @Tag("task:1")
+ @DisplayName("The salaryMultiplier method returns 1.0 when daysSkipped is below the threshold")
+ public void salaryMultiplierWhenDaysSkippedIs4() {
+ assertThat(calculator.salaryMultiplier(4)).isEqualTo(1.0);
}
-
+
@Test
- public void skippedBelowThreshold () {
- assertThat(calculator.finalSalary(3, 0)).isEqualTo(1000.0);
+ @Tag("task:1")
+ @DisplayName("The salaryMultiplier method returns 0.85 when daysSkipped is equal to the threshold")
+ public void salaryMultiplierWhenDaysSkippedIs5() {
+ assertThat(calculator.salaryMultiplier(5)).isEqualTo(0.85);
}
@Test
- public void skippedAboveThreshold() {
- assertThat(calculator.finalSalary(7, 0)).isEqualTo(850.0);
+ @Tag("task:1")
+ @DisplayName("The bonusMultiplier method returns 0.85 when daysSkipped is above the threshold")
+ public void salaryMultiplierWhenDaysSkippedIs6() {
+ assertThat(calculator.salaryMultiplier(6)).isEqualTo(0.85);
}
-
+
@Test
- public void soldBelowThreshold() {
- assertThat(calculator.finalSalary(0, 10)).isEqualTo(1100.0);
+ @Tag("task:2")
+ @DisplayName("The bonusMultiplier method returns 10 when productsSold is below the threshold")
+ public void bonusMultiplierWhenProductsSoldIs19() {
+ assertThat(calculator.bonusMultiplier(19)).isEqualTo(10);
}
@Test
- public void soldAboveThreshold() {
- assertThat(calculator.finalSalary(0, 25)).isEqualTo(1325.0);
+ @Tag("task:2")
+ @DisplayName("The bonusMultiplier method returns 13 when productsSold is equal to the threshold")
+ public void bonusMultiplierWhenProductsSoldIs20() {
+ assertThat(calculator.bonusMultiplier(20)).isEqualTo(13);
}
-
+
@Test
- public void skippedBelowThresholdAndSoldBelowThreshold() {
- assertThat(calculator.finalSalary(2, 5)).isEqualTo(1050.0);
+ @Tag("task:2")
+ @DisplayName("The bonusMultiplier method returns 13 when productsSold is above the threshold")
+ public void bonusMultiplierWhenProductsSoldIs21() {
+ assertThat(calculator.bonusMultiplier(21)).isEqualTo(13);
}
@Test
- public void skippedBelowThresholdAndSoldAboveThreshold() {
- assertThat(calculator.finalSalary(4, 40)).isEqualTo(1520.0);
- }
-
+ @Tag("task:2")
+ @DisplayName("The bonusForProductsSold method returns the right result")
+ public void bonusForProductsSoldWhenProductsSoldIs5() {
+ assertThat(calculator.bonusForProductsSold(5)).isEqualTo(50);
+ }
+
@Test
- public void skippedAboveThresholdAndSoldBelowThreshold() {
- assertThat(calculator.finalSalary(10, 2)).isEqualTo(870.0);
+ @Tag("task:3")
+ @DisplayName("The finalSalary method returns the regular salary without multiplier and bonus")
+ public void regularSalary() {
+ assertThat(calculator.finalSalary(0, 0)).isEqualTo(1000.0);
}
@Test
- public void skippedAboveThresholdAndSoldAboveThreshold() {
- assertThat(calculator.finalSalary(7, 50)).isEqualTo(1500.0);
+ @Tag("task:3")
+ @DisplayName("The finalSalary method returns the correct result when daysSkipped above threshold")
+ public void skippedAboveThreshold() {
+ assertThat(calculator.finalSalary(7, 0)).isEqualTo(850.0);
}
@Test
- public void salaryCanReachCloseToMaximum() {
- assertThat(calculator.finalSalary(0, 76)).isEqualTo(1988.0);
+ @Tag("task:3")
+ @DisplayName("The finalSalary method returns the correct result when daysSkipped and productsSold below threshold")
+ public void skippedBelowThresholdAndSoldBelowThreshold() {
+ assertThat(calculator.finalSalary(2, 5)).isEqualTo(1050.0);
}
-
+
@Test
+ @Tag("task:3")
+ @DisplayName("The finalSalary method returns the correct result capped at maximum salary")
public void salaryRespectMaximum() {
assertThat(calculator.finalSalary(0, 77)).isEqualTo(2000.0);
}
-
+
}
diff --git a/exercises/concept/secrets/.docs/hints.md b/exercises/concept/secrets/.docs/hints.md
new file mode 100644
index 000000000..623afa32b
--- /dev/null
+++ b/exercises/concept/secrets/.docs/hints.md
@@ -0,0 +1,18 @@
+# Hints
+
+## 1. Shift back the bits
+
+- There are two operators for shifting to the right, but only one will always insert a 0.
+
+## 2. Set some bits
+
+- One of the bit wise operators will always set a bit to 1 where the bits in both values are 1.
+
+## 3. Flip specific bits
+
+- There is an bit operation that will flip a bit where the mask is 1.
+
+## 4. Clear specific bits
+
+- One of the bit operations clears bits where the bit in the mask is 0.
+- But you may need to combine it with another operator to clear bits where the mask is 1.
diff --git a/exercises/concept/secrets/.docs/instructions.md b/exercises/concept/secrets/.docs/instructions.md
new file mode 100644
index 000000000..f3e7089b0
--- /dev/null
+++ b/exercises/concept/secrets/.docs/instructions.md
@@ -0,0 +1,58 @@
+# Instructions
+
+Your friend has just sent you a message with an important secret.
+Not wanting to make it easy for others to read it, the message was encrypted by performing a series of bit manipulations.
+You will need to write the methods to help decrypt the message.
+
+## 1. Shift back the bits
+
+The first step in decrypting the message is to undo the shifting from the encryption process by shifting the bits back to the right.
+There will be further steps in the decryption process that assume 0s are inserted from the left hand side.
+
+Implement the `Secrets.shiftBack` method that takes a value and the number of places to shift and peforms the shift.
+
+```java
+Secrets.shiftBack(0b1001, 2);
+# => 0b0010
+```
+
+## 2. Set some bits
+
+Next, there are some bits that need to be set to 1.
+
+Implement the `Secrets.setBits` method that takes a value and a mask and returns the result of setting the bits in value to 1.
+A bit from value should be set to 1 where the bit in the mask is also 1.
+All other bits should be kept unchanged.
+
+```java
+Secrets.setBits(0b0110, 0b0101);
+# => 0b0111
+```
+
+## 3. Flip specific bits
+
+Some bits are flipped during encryption.
+They will need to be flipped back to decrypt the message.
+
+Implement the `Secrets.flipBits` method that takes a value and the mask.
+The mask indicates which bits in the value to flip.
+If the bit is 1 in mask, the bit is flipped in the value.
+All other bits are kept unchanged.
+
+```java
+Secrets.flipBits(0b1100, 0b0101);
+# => 0b1001
+```
+
+## 4. Clear specific bits
+
+Lastly, there are also certain bits that always decrypt to 0.
+
+Implement the `Secrets.clearBits` method that takes a value and a mask.
+The bits in the `value` should be set to 0 where the bit in the mask is 1.
+All other bits should be kept unchanged.
+
+```java
+Secrets.clearBits(0b0110, 0b0101);
+# => 0b0010
+```
diff --git a/exercises/concept/secrets/.docs/introduction.md b/exercises/concept/secrets/.docs/introduction.md
new file mode 100644
index 000000000..132d725f0
--- /dev/null
+++ b/exercises/concept/secrets/.docs/introduction.md
@@ -0,0 +1,89 @@
+# Introduction
+
+## Bit Manipulation
+
+Java has operators for manipulating the bits of a `byte`, `short`, `int`, `long` or `char`.
+
+### Shift operators
+
+Use `<<` to shift bits to the left and `>>` to shift to the right.
+
+```java
+// Shift two places to the left
+0b0000_1011 << 2;
+// # => 0b0010_1100
+
+// Shift two places to the right
+0b0000_1011 >> 2;
+// # => 0b0000_0010
+```
+
+The `<<` operator always inserts 0s on the right hand side.
+However, `>>` inserts the same bit as the left most bit (1 if the number is negative or 0 if positive).
+
+```java
+// Shift 2 places to the right preserves the sign
+// This is a negative value, whose binary representation is
+// 1000_0000_0000_0000_0000_0000_0010_0110
+int value = -0x7FFFFFDA;
+
+// Shift two places to the right, preserving the sign bit
+value >> 2;
+// # => 1110_0000_0000_0000_0000_0000_0000_1001
+```
+
+Use `>>>` instead when 0s are to be inserted when shifting to the right.
+
+```java
+// Shift two places to the right, inserting 0s on the left
+value >>> 2;
+// # => 0010_0000_0000_0000_0000_0000_0000_1001
+```
+
+### Bitwise Operations
+
+#### Bitwise AND
+
+The bitwise AND (`&`) operator takes two values and performs an AND on each bit.
+It compares each bit from the first value with the bit in the same position from the second value.
+If they are both 1, then the result's bit is 1.
+Otherwise, the result's bit is 0.
+
+```java
+0b0110_0101 & 0b0011_1100;
+// # => 0b0010_0100
+```
+
+#### Bitwise OR
+
+The bitwise OR (`|`) operator takes two values and performs an OR on each bit.
+It compares each bit from the first value with the bit in the same position from the second value.
+If either bit is 1, the result's bit is 1.
+Otherwise, it is 0.
+
+```java
+0b0110_0101 | 0b0011_1100;
+// # => 0b0111_1101
+```
+
+#### Bitwise XOR
+
+The bitwise XOR operator (`^`) performs a bitwise XOR on two values.
+Like the bitwise AND and bitwise OR operators, it compares each bit from the first value against the bit in the same position from the second value.
+If only one of them is 1, the resulting bit is 1.
+Otherwise, it is 0.
+
+```java
+0b0110_0101 ^ 0b0011_1100;
+// # => 0b0101_1001
+```
+
+#### Bitwise NOT(`~`)
+
+Lastly, the bitwise NOT operator (`~`) flips each bit.
+Unlike the earlier operators, this is a unary operator, acting only on one value.
+
+```java
+~0b0110_0101;
+// # => 0b1001_1010
+```
diff --git a/exercises/concept/secrets/.docs/introduction.md.tpl b/exercises/concept/secrets/.docs/introduction.md.tpl
new file mode 100644
index 000000000..06ef79bf4
--- /dev/null
+++ b/exercises/concept/secrets/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:bit-manipulation}
diff --git a/exercises/concept/secrets/.meta/config.json b/exercises/concept/secrets/.meta/config.json
new file mode 100644
index 000000000..93231487e
--- /dev/null
+++ b/exercises/concept/secrets/.meta/config.json
@@ -0,0 +1,24 @@
+{
+ "authors": [
+ "kahgoh"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/Secrets.java"
+ ],
+ "test": [
+ "src/test/java/SecretsTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/Secrets.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "forked_from": [
+ "crystal/secrets"
+ ],
+ "icon": "secrets",
+ "blurb": "Learn about bit manipulation by writing a program to decrypt a message."
+}
diff --git a/exercises/concept/secrets/.meta/design.md b/exercises/concept/secrets/.meta/design.md
new file mode 100644
index 000000000..a7c6b6a8f
--- /dev/null
+++ b/exercises/concept/secrets/.meta/design.md
@@ -0,0 +1,34 @@
+# Design
+
+## Goal
+
+The goal of this exercise is to teach the student about bitwise operations in Java.
+
+## Learning objectives
+
+- Learn about the bitwise operators `<<`, `>>`, `>>>`, `&`, `|`, `^` and `~`
+
+## Concepts
+
+- `bit-manipulation`: Know of the bit operators `>>`, `<<`, `>>>`, `&`, `|`, `^`, `~`
+
+## Prerequisites
+
+This exercise's prerequisites Concepts are:
+
+- 'numbers'
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `essential`: If the student did not use the `>>>` operator in the `shiftBack` method, instruct the student to do so.
+- `essential`: If the student did not use the `|` operator in the `setBits` method, instruct the student to do so.
+- `essential`: If the student did not use the `^` operator in the `flipBits` method, instruct the student to do so.
+- `essential`: If the student did not use the `&` operator in the `clearBits` method, instruct the student to do so.
+- `informative`: If the solution did not use the `~` operator in the `clearBits` method, inform the student that with it will achieve a more concise solution.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/secrets/.meta/src/reference/java/Secrets.java b/exercises/concept/secrets/.meta/src/reference/java/Secrets.java
new file mode 100644
index 000000000..6b01a1865
--- /dev/null
+++ b/exercises/concept/secrets/.meta/src/reference/java/Secrets.java
@@ -0,0 +1,17 @@
+public class Secrets {
+ public static int shiftBack(int value, int amount) {
+ return value >>> amount;
+ }
+
+ public static int setBits(int value, int mask) {
+ return value | mask;
+ }
+
+ public static int flipBits(int value, int mask) {
+ return value ^ mask;
+ }
+
+ public static int clearBits(int value, int mask) {
+ return value & ~mask;
+ }
+}
diff --git a/exercises/concept/secrets/build.gradle b/exercises/concept/secrets/build.gradle
new file mode 100644
index 000000000..d28f35dee
--- /dev/null
+++ b/exercises/concept/secrets/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/concept/secrets/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/secrets/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/secrets/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/secrets/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/secrets/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/secrets/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/secrets/gradlew b/exercises/concept/secrets/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/secrets/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/secrets/gradlew.bat b/exercises/concept/secrets/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/secrets/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/secrets/src/main/java/Secrets.java b/exercises/concept/secrets/src/main/java/Secrets.java
new file mode 100644
index 000000000..f70fc6de2
--- /dev/null
+++ b/exercises/concept/secrets/src/main/java/Secrets.java
@@ -0,0 +1,17 @@
+public class Secrets {
+ public static int shiftBack(int value, int amount) {
+ throw new UnsupportedOperationException("Please implement the (static) Secrets.shiftBack() method");
+ }
+
+ public static int setBits(int value, int mask) {
+ throw new UnsupportedOperationException("Please implement the (static) Secrets.setBits() method");
+ }
+
+ public static int flipBits(int value, int mask) {
+ throw new UnsupportedOperationException("Please implement the (static) Secrets.flipBits() method");
+ }
+
+ public static int clearBits(int value, int mask) {
+ throw new UnsupportedOperationException("Please implement the (static) Secrets.clearBits() method");
+ }
+}
\ No newline at end of file
diff --git a/exercises/concept/secrets/src/test/java/SecretsTest.java b/exercises/concept/secrets/src/test/java/SecretsTest.java
new file mode 100644
index 000000000..9f79f478a
--- /dev/null
+++ b/exercises/concept/secrets/src/test/java/SecretsTest.java
@@ -0,0 +1,65 @@
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SecretsTest {
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("shift 8 back 2 places")
+ public void shift8Back2Places() {
+ assertThat(Secrets.shiftBack(8, 2)).isEqualTo(2);
+ }
+
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("shift -2_144_333_657 back 3 places")
+ public void shiftNegativeNumberBack3Places() {
+ assertThat(Secrets.shiftBack(-2_144_333_657, 3)).isEqualTo(268_829_204);
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("set bits in 5")
+ public void setBitsIn5() {
+ assertThat(Secrets.setBits(5, 3)).isEqualTo(7);
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("set bits in 5_652")
+ public void setBitsIn5652() {
+ assertThat(Secrets.setBits(5_652, 26_150)).isEqualTo(30_262);
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("flip bits in 5")
+ public void flipBitsIn5() {
+ assertThat(Secrets.flipBits(5, 11)).isEqualTo(14);
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("flip bits in 38_460")
+ public void flipBitsIn38460() {
+ assertThat(Secrets.flipBits(38_460, 15_471)).isEqualTo(43_603);
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("clear bits from 5")
+ public void clearBitsFrom5() {
+ assertThat(Secrets.clearBits(5, 11)).isEqualTo(4);
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("clear bits from 90")
+ public void clearBitsFrom90() {
+ assertThat(Secrets.clearBits(90, 240)).isEqualTo(10);
+ }
+}
diff --git a/exercises/concept/squeaky-clean/.docs/hints.md b/exercises/concept/squeaky-clean/.docs/hints.md
index f1336552f..804bf6d8f 100644
--- a/exercises/concept/squeaky-clean/.docs/hints.md
+++ b/exercises/concept/squeaky-clean/.docs/hints.md
@@ -3,33 +3,24 @@
## 1. Replace any spaces encountered with underscores
- [This tutorial][chars-tutorial] is useful.
-- [Reference documentation][chars-docs] for `char`s is here.
- You can retrieve `char`s from a string using the [charAt][char-at] method.
- You should use a [`StringBuilder`][string-builder] to build the output string.
-- See [this method][iswhitespace] for detecting spaces. Remember it is a static method.
+- Check the [Character][chars-docs] documentation for a method to detect whitespaces. Remember it is a static method.
- `char` literals are enclosed in single quotes.
-## 2. Replace control characters with the upper case string "CTRL"
+## 2. Convert kebab-case to camel-case
-- See [this method][iscontrol] to check if a character is a control character.
+- Check the [Character][chars-docs] documentation for a method to convert a character to upper case. Remember it is a static method.
-## 3. Convert kebab-case to camel-case
+## 3. Convert leetspeak to normal text
-- See [this method][toupper] to convert a character to upper case.
+- Check the [Character][chars-docs] documentation for a method to detect when a character is a digit. Remember it is a static method.
## 4. Omit characters that are not letters
-- See [this method][isLetter] to check if a character is a letter.
-
-## 5. Omit Greek lower case letters
-
-- `char`s support the default equality and comparison operators.
+- Check the [Character][chars-docs] documentation for a method to detect when a character is a letter. Remember it is a static method.
[chars-docs]: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Character.html
[chars-tutorial]: https://docs.oracle.com/javase/tutorial/java/data/characters.html
[char-at]: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/String.html#charAt(int)
[string-builder]: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/StringBuilder.html
-[iswhitespace]: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Character.html#isWhitespace(char)
-[iscontrol]: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Character.html#isISOControl(char)
-[toupper]: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Character.html#toUpperCase(char)
-[isLetter]: https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Character.html#isLetter(char)
\ No newline at end of file
diff --git a/exercises/concept/squeaky-clean/.docs/instructions.md b/exercises/concept/squeaky-clean/.docs/instructions.md
index 12bbd4902..6b0419d2f 100644
--- a/exercises/concept/squeaky-clean/.docs/instructions.md
+++ b/exercises/concept/squeaky-clean/.docs/instructions.md
@@ -17,22 +17,26 @@ SqueakyClean.clean("my Id");
// => "my___Id"
```
-## 2. Replace control characters with the upper case string "CTRL"
+## 2. Convert kebab-case to camelCase
-Modify the (_static_) `SqueakyClean.clean()` method to replace control characters with the upper case string `"CTRL"`.
+Modify the (_static_) `SqueakyClean.clean()` method to convert kebab-case to camelCase.
```java
-SqueakyClean.clean("my\0Id");
-// => "myCTRLId",
+SqueakyClean.clean("a-bc");
+// => "aBc"
```
-## 3. Convert kebab-case to camelCase
+## 3. Convert leetspeak to normal text
-Modify the (_static_) `SqueakyClean.clean()` method to convert kebab-case to camelCase.
+Modify the (_static_) `SqueakyClean.clean()` method to convert [leetspeak][leet-speak] to normal text.
+
+For simplicity we will only be replacing `4`, `3`, `0`, `1` and `7` with `a`, `e`, `o`, `l`, and `t`, respectively.
```java
-SqueakyClean.clean("à-ḃç");
-// => "àḂç"
+SqueakyClean.clean("H3ll0 W0rld");
+// => "Hello_World"
+SqueakyClean.clean("4 73s7");
+// => "a_test"
```
## 4. Omit characters that are not letters
@@ -40,15 +44,8 @@ SqueakyClean.clean("à-ḃç");
Modify the (_static_) `SqueakyClean.clean()` method to omit any characters that are not letters.
```java
-SqueakyClean.clean("a1😀2😀3😀b");
+SqueakyClean.clean("a$#.b");
// => "ab"
```
-## 5. Omit Greek lower case letters
-
-Modify the (_static_) `SqueakyClean.clean()` method to omit any Greek letters in the range 'α' to 'ω'.
-
-```java
-SqueakyClean.clean("MyΟβιεγτFinder");
-// => "MyΟFinder"
-```
+[leet-speak]: https://en.wikipedia.org/wiki/Leet
diff --git a/exercises/concept/squeaky-clean/.docs/introduction.md b/exercises/concept/squeaky-clean/.docs/introduction.md
index 6a24eae39..016475918 100644
--- a/exercises/concept/squeaky-clean/.docs/introduction.md
+++ b/exercises/concept/squeaky-clean/.docs/introduction.md
@@ -1,17 +1,89 @@
# Introduction
-The Java `char` type represents the smallest addressable components of text.
-Multiple `char`s can comprise a string such as `"word"` or `char`s can be
-processed independently. Their literals have single quotes e.g. `'A'`.
+## Chars
-Java `char`s support Unicode encoding so in addition to the Latin character set
-pretty much all the writing systems in use worldwide can be represented,
-e.g. the Greek letter `'β'`.
+### chars
-There are many builtin library methods to inspect and manipulate `char`s. These
-can be found as static methods of the `java.lang.Character` class.
+The Java `char` primitive type is a 16 bit representation of a single character.
+Multiple `char`s can comprise a string, such as `"word"`, or `char`s can be processed independently.
+A `char` literal is surrounded by single quotes (e.g. `'A'`).
-`char`s are sometimes used in conjunction with a `StringBuilder` object.
-This object has methods that allow a string to be constructed
-character by character and manipulated. At the end of the process
-`toString` can be called on it to output a complete string.
+```java
+char lowerA = 'a';
+char upperB = 'B';
+```
+
+### Getting the `char`s of a `String`
+
+The `String.toCharArray` method returns a String's chars as an array.
+As mentioned in arrays, you can use a `for` loop to iterate over the array.
+
+```java
+String text = "Hello";
+char[] asArray = text.toCharArray();
+
+for (char ch: asArray) {
+ System.out.println(ch);
+}
+
+// Outputs:
+// H
+// e
+// l
+// l
+// o
+```
+
+### The Character class
+
+There are many builtin library methods to inspect and manipulate `char`s.
+These can be found as static methods of the `java.lang.Character` class.
+Here are some examples:
+
+```java
+Character.isWhitespace(' '); // true
+Character.isWhitespace('#'); // false
+
+Character.isLetter('a'); // true
+Character.isLetter('3'); // false
+
+Character.isDigit('6'); // true
+Character.isDigit('?'); // false
+```
+
+### Adding a `char` to a `String`
+
+The `+` operator can be used to add a `char` to a `String`.
+
+```java
+'a' + " banana" // => "a banana"
+"banana " + 'a' // => "banana a"
+```
+
+~~~~exercism/caution
+Becareful _not_ to use `+` to join two `char`s together to form a `String`!
+Adding two `char`s this way gives an `int`, _not_ a `String`!
+For example:
+
+```java
+'b' + 'c';
+// => 197 (not the String "bc")
+```
+
+This is because Java promotes the `char` to an `int` (see [4.2 Primitive Types and Values ][jls-primitives] of the [Java Language Specification][jls-main]).
+
+[jls-main]: https://docs.oracle.com/javase/specs/jls/se21/html/
+[jls-primitives]: https://docs.oracle.com/javase/specs/jls/se21/html/jls-4.html#jls-4.2
+~~~~
+
+However, when there are many characters to be added, it can be more efficient to use a `StringBuilder` instead:
+
+```java
+StringBuilder builder = new StringBuilder();
+builder.append('a');
+builder.append('b');
+builder.append('c');
+
+String builtString = builder.toString();
+// => abc
+```
diff --git a/exercises/concept/squeaky-clean/.docs/introduction.md.tpl b/exercises/concept/squeaky-clean/.docs/introduction.md.tpl
new file mode 100644
index 000000000..6d69d80e4
--- /dev/null
+++ b/exercises/concept/squeaky-clean/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:chars}
diff --git a/exercises/concept/squeaky-clean/.meta/config.json b/exercises/concept/squeaky-clean/.meta/config.json
index 394ccba21..70df5ac7d 100644
--- a/exercises/concept/squeaky-clean/.meta/config.json
+++ b/exercises/concept/squeaky-clean/.meta/config.json
@@ -1,8 +1,14 @@
{
- "blurb": "Learn about characters and StringBuilder by cleaning strings.",
"authors": [
"ystromm"
],
+ "contributors": [
+ "jagdish-15",
+ "kahgoh",
+ "manumafe98",
+ "mrDonoghue",
+ "sanderploegsma"
+ ],
"files": {
"solution": [
"src/main/java/SqueakyClean.java"
@@ -12,9 +18,13 @@
],
"exemplar": [
".meta/src/reference/java/SqueakyClean.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"csharp/squeaky-clean"
- ]
+ ],
+ "blurb": "Learn about characters and StringBuilder by cleaning strings."
}
diff --git a/exercises/concept/squeaky-clean/.meta/design.md b/exercises/concept/squeaky-clean/.meta/design.md
index 382201650..07eef80ae 100644
--- a/exercises/concept/squeaky-clean/.meta/design.md
+++ b/exercises/concept/squeaky-clean/.meta/design.md
@@ -14,7 +14,7 @@
## Out of scope
-- Converting an integer to a character and vice versa.
+- Handling control characters and unicode characters.
- Advanced unicode issues such as surrogates, text normalization, combining characters.
- Cultural considerations and invariants.
@@ -27,3 +27,18 @@
- `strings`: know of the `string` type that will be iterated over and accessed by index.
- `for-loop` for loops (rather than foreach) are the best means of highlighting the relationship between strings and `char`s
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the solution did not use `Character.isWhitespace`, instruct the student to do so, because this concept intends to teach the character concept and methods.
+- `actionable`: If the solution did not use `Character.isDigit`, instruct the student to do so.
+- `actionable`: If the solution did not use `Character.isLetter`, instruct the student to do so.
+- `actionable`: If the solution did not use `Character.toUpperCase`, instruct the student to do so.
+- `informative`: If the solution uses string concatenation instead of `StringBuilder`, inform the student that this cause a small performance and memory penalty compared to `StringBuilder`.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/squeaky-clean/.meta/src/reference/java/SqueakyClean.java b/exercises/concept/squeaky-clean/.meta/src/reference/java/SqueakyClean.java
index d400586f7..961174021 100644
--- a/exercises/concept/squeaky-clean/.meta/src/reference/java/SqueakyClean.java
+++ b/exercises/concept/squeaky-clean/.meta/src/reference/java/SqueakyClean.java
@@ -1,16 +1,36 @@
class SqueakyClean {
+
+ private static char replaceDigit(char digit) {
+ if (digit == '3') {
+ return 'e';
+ }
+
+ if (digit == '4') {
+ return 'a';
+ }
+
+ if (digit == '0') {
+ return 'o';
+ }
+
+ if (digit == '1') {
+ return 'l';
+ }
+ return 't';
+ }
+
static String clean(String identifier) {
final StringBuilder cleanIdentifier = new StringBuilder();
boolean kebab = false;
for (int i = 0; i < identifier.length(); i++) {
final char ch = identifier.charAt(i);
- if (Character.isSpaceChar(ch)) {
+ if (Character.isWhitespace(ch)) {
cleanIdentifier.append("_");
- } else if (Character.isISOControl(ch)) {
- cleanIdentifier.append("CTRL");
+ } else if (Character.isDigit(ch)) {
+ cleanIdentifier.append(SqueakyClean.replaceDigit(ch));
} else if (ch == '-') {
kebab = true;
- } else if (isLetter(ch)) {
+ } else if (Character.isLetter(ch)) {
cleanIdentifier.append(
kebab ? Character.toUpperCase(ch) : ch
);
@@ -19,7 +39,4 @@ static String clean(String identifier) {
}
return cleanIdentifier.toString();
}
- private static boolean isLetter(char ch) {
- return Character.isLetter(ch) && !(ch >= 'α' && ch <= 'ω');
- }
}
diff --git a/exercises/concept/squeaky-clean/build.gradle b/exercises/concept/squeaky-clean/build.gradle
index 8bd005d42..dd3862eb9 100644
--- a/exercises/concept/squeaky-clean/build.gradle
+++ b/exercises/concept/squeaky-clean/build.gradle
@@ -1,23 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/squeaky-clean/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/squeaky-clean/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/squeaky-clean/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/squeaky-clean/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/squeaky-clean/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/squeaky-clean/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/squeaky-clean/gradlew b/exercises/concept/squeaky-clean/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/squeaky-clean/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/squeaky-clean/gradlew.bat b/exercises/concept/squeaky-clean/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/squeaky-clean/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/squeaky-clean/src/test/java/SqueakyCleanTest.java b/exercises/concept/squeaky-clean/src/test/java/SqueakyCleanTest.java
index f5adb33b8..e85a60495 100644
--- a/exercises/concept/squeaky-clean/src/test/java/SqueakyCleanTest.java
+++ b/exercises/concept/squeaky-clean/src/test/java/SqueakyCleanTest.java
@@ -1,66 +1,92 @@
-import org.junit.Test;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class SqueakyCleanTest {
@Test
+ @Tag("task:1")
+ @DisplayName("The clean method returns empty string when invoked on empty string")
public void empty() {
assertThat(SqueakyClean.clean("")).isEmpty();
}
@Test
- public void single_letter() {
+ @Tag("task:1")
+ @DisplayName("The clean method returns the same string when invoked on a single letter string")
+ public void singleLetter() {
assertThat(SqueakyClean.clean("A")).isEqualTo("A");
}
@Test
+ @Tag("task:1")
+ @DisplayName("The clean method returns the same string when invoked on a string of three letters")
public void string() {
- assertThat(SqueakyClean.clean("àḃç")).isEqualTo("àḃç");
+ assertThat(SqueakyClean.clean("abc")).isEqualTo("abc");
}
@Test
+ @Tag("task:1")
+ @DisplayName("The clean method replaces whitespaces with underscores in the middle of the string")
public void spaces() {
assertThat(SqueakyClean.clean("my Id")).isEqualTo("my___Id");
}
@Test
- public void leading_and_trailing_spaces() {
+ @Tag("task:1")
+ @DisplayName("The clean method replaces leading and trailing whitespaces with underscores")
+ public void leadingAndTrailingSpaces() {
assertThat(SqueakyClean.clean(" myId ")).isEqualTo("_myId_");
}
@Test
- public void ctrl() {
- assertThat(SqueakyClean.clean("my\0\r\u007FId")).isEqualTo("myCTRLCTRLCTRLId");
+ @Tag("task:2")
+ @DisplayName("The clean method converts kebab to camel case after removing a dash")
+ public void kebabToCamelCase() {
+ assertThat(SqueakyClean.clean("a-bc")).isEqualTo("aBc");
}
@Test
- public void string_with_no_letters() {
- assertThat(SqueakyClean.clean("\uD83D\uDE00\uD83D\uDE00\uD83D\uDE00")).isEmpty();
+ @Tag("task:2")
+ @DisplayName("The clean method returns a string in camel case after removing a dash and replaces a whitespace")
+ public void kebabToCamelCaseAndNumber() {
+ assertThat(SqueakyClean.clean("a-C ")).isEqualTo("aC_");
}
@Test
- public void keep_only_letters() {
- assertThat(SqueakyClean.clean("a1\uD83D\uDE002\uD83D\uDE003\uD83D\uDE00b")).isEqualTo("ab");
+ @Tag("task:2")
+ @DisplayName("The clean method returns a string in camel case and replaces leading and trailing whitespaces")
+ public void kebabToCamelCaseAndSpaces() {
+ assertThat(SqueakyClean.clean(" hello-world ")).isEqualTo("_helloWorld_");
}
@Test
- public void kebab_to_camel_case() {
- assertThat(SqueakyClean.clean("à-ḃç")).isEqualTo("àḂç");
+ @Tag("task:3")
+ @DisplayName("The clean method converts leetspeak to normal text after replacing numbers with chars")
+ public void leetspeakToNormalText() {
+ assertThat(SqueakyClean.clean("H3ll0 W0rld")).isEqualTo("Hello_World");
}
@Test
- public void kebab_to_camel_case_no_letter() {
- assertThat(SqueakyClean.clean("a-1C")).isEqualTo("aC");
+ @Tag("task:3")
+ @DisplayName("The clean method converts leetspeak to normal text with spaces and special characters")
+ public void leetspeakToNormalTextWithSpacesAndSpecialCharacters() {
+ assertThat(SqueakyClean.clean("¡1337sp34k is fun!")).isEqualTo("leetspeak_is_fun");
}
@Test
- public void omit_lower_case_greek_letters() {
- assertThat(SqueakyClean.clean("MyΟβιεγτFinder")).isEqualTo("MyΟFinder");
+ @Tag("task:4")
+ @DisplayName("The clean method removes all characters that are not letters")
+ public void specialCharacters() {
+ assertThat(SqueakyClean.clean("a$#.b")).isEqualTo("ab");
}
@Test
- public void combine_conversions() {
- assertThat(SqueakyClean.clean("9 -abcĐ\uD83D\uDE00ω\0")).isEqualTo("_AbcĐCTRL");
+ @Tag("task:4")
+ @DisplayName("The clean method removes all characters that are not letters and replaces spaces")
+ public void specialCharactersAndSpaces() {
+ assertThat(SqueakyClean.clean("¡hello world!. ")).isEqualTo("hello_world_");
}
}
diff --git a/exercises/concept/tim-from-marketing/.docs/hints.md b/exercises/concept/tim-from-marketing/.docs/hints.md
new file mode 100644
index 000000000..0faf9ca6e
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/.docs/hints.md
@@ -0,0 +1,20 @@
+# Hints
+
+## 1. Print a badge for an employee
+
+- [String interpolation][string-interpolation] can be used to create description strings.
+- There is a [built-in method to convert a string to uppercase][to-upper-case].
+
+## 2. Print a badge for a new employee
+
+- You should check if the ID is `null` before using it.
+- You can use the [equality operators][equality-operators] to compare the value to `null`
+
+## 3. Print a badge for the owner
+
+- You should check if the department is `null` before using it.
+- You can use the [equality operators][equality-operators] to compare the value to `null`
+
+[string-interpolation]: https://www.baeldung.com/java-string-interpolation
+[to-upper-case]:https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#toUpperCase--
+[equality-operators]: https://docs.oracle.com/cd/E21764_01/apirefs.1111/e17787/com/sigmadynamics/util/Null.html#isNull_java_lang_String_
diff --git a/exercises/concept/tim-from-marketing/.docs/instructions.md b/exercises/concept/tim-from-marketing/.docs/instructions.md
new file mode 100644
index 000000000..ea2fae9ba
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/.docs/instructions.md
@@ -0,0 +1,47 @@
+# Instructions
+
+In this exercise you'll be writing code to print name badges for factory employees.
+
+## 1. Print a badge for an employee
+
+Employees have an ID, name and department name. Employee badge labels are formatted as follows: `"[id] - name - DEPARTMENT"`.
+Implement the `Badge.print()` method to return an employee's badge label:
+
+```java
+Badge badge = new Badge();
+badge.print(734, "Ernest Johnny Payne", "Strategic Communication");
+// => "[734] - Ernest Johnny Payne - STRATEGIC COMMUNICATION"
+```
+
+Note that the department should be uppercased on the label.
+
+## 2. Print a badge for a new employee
+
+Due to a quirk in the computer system, new employees occasionally don't yet have an ID when they start working at the factory.
+As badges are required, they will receive a temporary badge without the ID prefix. Modify the `Badge.print()` method to support new employees that don't yet have an ID:
+
+```java
+Badge badge = new Badge();
+Badge.print(null, "Jane Johnson", "Procurement");
+// => "Jane Johnson - PROCUREMENT"
+```
+
+## 3. Print a badge for the owner
+
+Even the factory's owner has to wear a badge at all times.
+However, an owner does not have a department. In this case, the label should print `"OWNER"` instead of the department name.
+Modify the `Badge.print()` method to print a label for the owner:
+
+```java
+Badge badge = new Badge();
+badge.print(254, "Charlotte Hale", null);
+// => "[254] - Charlotte Hale - OWNER"
+```
+
+Note that it is possible for the owner to also be a new employee:
+
+```java
+Badge badge = new Badge();
+badge.print(null, "Charlotte Hale", null);
+// => "Charlotte Hale - OWNER"
+```
diff --git a/exercises/concept/tim-from-marketing/.docs/introduction.md b/exercises/concept/tim-from-marketing/.docs/introduction.md
new file mode 100644
index 000000000..51ae4574a
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/.docs/introduction.md
@@ -0,0 +1,25 @@
+# Introduction
+
+## Nullability
+
+In Java, the `null` literal is used to denote the absence of a value.
+
+Primitive data types in Java all have a default value and therefore can never be `null`.
+By convention, they start with a lowercase letter e.g `int`.
+
+Reference types contain the memory address of an object and can have a value of `null`.
+They generally start with an uppercase letter, e.g. `String`.
+
+Attempting to assign a primitive variable a value of `null` will result in a compile time error as the variable always holds a primitive value of the type assigned.
+
+```java
+//Throws compile time error stating the required type is int, but null was provided
+int number = null;
+```
+
+Assigning a reference variable a `null` value will not result in a compile time error as reference variables are nullable.
+
+```java
+//No error will occur as the String variable str is nullable
+String str = null;
+```
diff --git a/exercises/concept/tim-from-marketing/.docs/introduction.md.tpl b/exercises/concept/tim-from-marketing/.docs/introduction.md.tpl
new file mode 100644
index 000000000..ea9499a77
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/.docs/introduction.md.tpl
@@ -0,0 +1,5 @@
+# Introduction
+
+## Nullability
+
+%{concept:nullability}
\ No newline at end of file
diff --git a/exercises/concept/tim-from-marketing/.meta/config.json b/exercises/concept/tim-from-marketing/.meta/config.json
new file mode 100644
index 000000000..c1b8f0268
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/.meta/config.json
@@ -0,0 +1,23 @@
+{
+ "authors": [
+ "smcg468"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/Badge.java"
+ ],
+ "test": [
+ "src/test/java/BadgeTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/Badge.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "forked_from": [
+ "csharp/tim-from-marketing"
+ ],
+ "blurb": "Learn about the null literal and nullable variables in java by printing name badges."
+}
diff --git a/exercises/concept/tim-from-marketing/.meta/design.md b/exercises/concept/tim-from-marketing/.meta/design.md
new file mode 100644
index 000000000..6650b8212
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/.meta/design.md
@@ -0,0 +1,33 @@
+# Design
+
+## Learning objectives
+
+- Know of the existence of the `null` literal.
+- Know what a `NullPointerException` is and when it is thrown.
+- Know how to compare a value to `null`.
+
+## Out of scope
+
+- `java.util.Optional`
+
+## Concepts
+
+- `nullability`
+
+## Prerequisites
+
+- `strings`: strings will be compared to null and basic methods from strings will be called.
+- `if-else-statements`: using a conditional statement.
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the solution uses `Optionals` to solve the exercise, encourage the student to try solving it using `null` instead.
+- `informative`: If the solution uses `String.format`, instruct the student to use simple string concatenation instead.
+ Explain that `String.format` is significantly slower than concatenating strings and should be used in more complex scenarios.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/tim-from-marketing/.meta/src/reference/java/Badge.java b/exercises/concept/tim-from-marketing/.meta/src/reference/java/Badge.java
new file mode 100644
index 000000000..7299a697f
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/.meta/src/reference/java/Badge.java
@@ -0,0 +1,20 @@
+public class Badge {
+
+ public String print(Integer id, String name, String department) {
+
+ String worksAt;
+
+ if (department == null) {
+ worksAt = "OWNER";
+ } else {
+ worksAt = department.toUpperCase();
+ }
+
+ if (id == null) {
+ return name + " - " + worksAt;
+ }
+
+ return "[" + id + "] - " + name + " - " + worksAt;
+ }
+
+}
diff --git a/exercises/concept/tim-from-marketing/build.gradle b/exercises/concept/tim-from-marketing/build.gradle
new file mode 100644
index 000000000..d28f35dee
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/concept/tim-from-marketing/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/tim-from-marketing/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/tim-from-marketing/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/tim-from-marketing/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/tim-from-marketing/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/tim-from-marketing/gradlew b/exercises/concept/tim-from-marketing/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/tim-from-marketing/gradlew.bat b/exercises/concept/tim-from-marketing/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/tim-from-marketing/src/main/java/Badge.java b/exercises/concept/tim-from-marketing/src/main/java/Badge.java
new file mode 100644
index 000000000..12b792728
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/src/main/java/Badge.java
@@ -0,0 +1,5 @@
+class Badge {
+ public String print(Integer id, String name, String department) {
+ throw new UnsupportedOperationException("Please implement the Badge.print() method");
+ }
+}
diff --git a/exercises/concept/tim-from-marketing/src/test/java/BadgeTest.java b/exercises/concept/tim-from-marketing/src/test/java/BadgeTest.java
new file mode 100644
index 000000000..929a9fbe6
--- /dev/null
+++ b/exercises/concept/tim-from-marketing/src/test/java/BadgeTest.java
@@ -0,0 +1,40 @@
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BadgeTest {
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Printing a badge for an employee")
+ public void labelForEmployee() {
+ Badge badge = new Badge();
+ assertThat(badge.print(17, "Ryder Herbert", "Marketing"))
+ .isEqualTo("[17] - Ryder Herbert - MARKETING");
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Printing a badge for a new employee")
+ public void labelForNewEmployee() {
+ Badge badge = new Badge();
+ assertThat(badge.print(null, "Bogdan Rosario", "Marketing")).isEqualTo("Bogdan Rosario - MARKETING");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Printing a badge for the owner")
+ public void labelForOwner() {
+ Badge badge = new Badge();
+ assertThat(badge.print(59, "Julie Sokato", null)).isEqualTo("[59] - Julie Sokato - OWNER");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Printing a badge for the owner who is a new employee")
+ public void labelForNewOwner() {
+ Badge badge = new Badge();
+ assertThat(badge.print(null, "Amare Osei", null)).isEqualTo("Amare Osei - OWNER");
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors-2/.docs/hints.md b/exercises/concept/wizards-and-warriors-2/.docs/hints.md
new file mode 100644
index 000000000..3f67b4fac
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.docs/hints.md
@@ -0,0 +1,23 @@
+# Hints
+
+## General
+
+- [Method overloading in Java][method-overloading].
+- [String concatenation][string-concatenation] can be used to create description strings.
+
+## 3. Describe the travel method
+
+- A [simple conditional][if-statement] can be used for the conditional logic.
+
+## 4. Describe a character traveling to a destination
+
+- You can re-use the existing describe functionality.
+- You can call methods directly in the parts of a string concatenation or string formatting.
+
+## 5. Describe a character traveling to a destination without specifying the travel method
+
+- You can re-use the existing functionality that does take an explicit travel method.
+
+[string-concatenation]: https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#concat-java.lang.String-
+[method-overloading]: https://docs.oracle.com/javase/tutorial/java/javaOO/methods.html#overloading
+[if-statement]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/if.html
diff --git a/exercises/concept/wizards-and-warriors-2/.docs/instructions.md b/exercises/concept/wizards-and-warriors-2/.docs/instructions.md
new file mode 100644
index 000000000..36253df23
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.docs/instructions.md
@@ -0,0 +1,89 @@
+# Instructions
+
+In this exercise you're playing a role-playing game named "Wizard and Warriors" with your best friends.
+You are the Game Master, the person tasked with making the game world come alive for the players.
+A key aspect of this is describing the game to the players: what is a character's status, what the town they're visiting looks like, etc.
+
+You have five tasks that have you describe parts of the game to the players.
+
+## 1. Describe a character
+
+Each character has a class, level and number of hit points and is described as: `"You're a level with hit points."`.
+Implement the `GameMaster.describe` method that takes a `Character` as its sole parameter and returns its description.
+
+```java
+Character character = new Character();
+character.setCharacterClass("Wizard");
+character.setLevel(4);
+character.setHitPoints(28);
+
+new GameMaster().describe(character);
+// => "You're a level 4 Wizard with 28 hit points."
+```
+
+## 2. Describe a destination
+
+Each destination has a name and a number of inhabitants and is described as: `"You've arrived at , which has inhabitants."`.
+Implement the `GameMaster.describe` method that takes a `Destination` as its sole parameter and returns its description.
+
+```java
+Destination destination = new Destination();
+destination.setName("Muros");
+destination.setInhabitants(732);
+
+new GameMaster().describe(destination);
+// => "You've arrived at Muros, which has 732 inhabitants."
+```
+
+## 3. Describe the travel method
+
+Characters can travel to a destination using one of two options:
+
+- Walking, described as: `"You're traveling to your destination by walking."`
+- On horseback, described as: `"You're traveling to your destination on horseback."`
+
+Implement the `GameMaster.describe` method that takes a `TravelMethod` as its sole parameter and returns its description.
+
+```java
+new GameMaster().describe(TravelMethod.HORSEBACK);
+// => "You're traveling to your destination on horseback."
+```
+
+## 4. Describe a character traveling to a destination
+
+When a character is traveling to a destination, this is described as a combination of the individual descriptions: `" "`.
+Implement the `GameMaster.describe` method that takes a `Character`, a `Destination` and a `TravelMethod` as its parameters and return its description.
+
+```java
+Character character = new Character();
+character.setCharacterClass("Wizard");
+character.setLevel(4);
+character.setHitPoints(28);
+
+Destination destination = new Destination();
+destination.setName("Muros");
+destination.setInhabitants(732);
+
+new GameMaster().describe(character, destination, TravelMethod.HORSEBACK);
+// => "You're a level 4 Wizard with 28 hit points. You're traveling to your destination on horseback. You've arrived at Muros, which has 732 inhabitants."
+```
+
+## 5. Describe a character traveling to a destination without specifying the travel method
+
+In the majority of cases, characters are traveling to a destination by walking.
+For convenience, players are allowed to omit mentioning their travel method, in which case walking will be assumed to be the travel method.
+Implement the `GameMaster.describe` method that takes a `Character` and a `Destination` as its parameters and return its description.
+
+```java
+Character character = new Character();
+character.setCharacterClass("Wizard");
+character.setLevel(4);
+character.setHitPoints(28);
+
+Destination destination = new Destination();
+destination.setName("Muros");
+destination.setInhabitants(732);
+
+new GameMaster().describe(character, destination);
+// => "You're a level 4 Wizard with 28 hit points. You're traveling to your destination by walking. You've arrived at Muros, which has 732 inhabitants."
+```
diff --git a/exercises/concept/wizards-and-warriors-2/.docs/introduction.md b/exercises/concept/wizards-and-warriors-2/.docs/introduction.md
new file mode 100644
index 000000000..38bbfcf57
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.docs/introduction.md
@@ -0,0 +1,62 @@
+# Introduction
+
+## Method Overloading
+
+In Java, method overloading is a feature that allows a class to have more than one method having the same name, if their
+parameter lists are different.
+It is related to compile-time (or static) polymorphism.
+This concept is crucial for
+creating methods that perform similar tasks but with different inputs.
+
+### Why Overload Methods?
+
+Method overloading increases the readability of the program.
+Different methods can be given the same name but with
+different parameters.
+Depending on the number of parameters or the type of parameters, the corresponding method is called.
+
+### How to Overload Methods?
+
+The key to method overloading is a method's signature.
+Two methods will be considered different if they have different signatures.
+There are two ways to overload a method:
+
+1. **Different Number of Parameters**: Methods can have the same name but a different number of parameters.
+
+ ```java
+ public class Display {
+
+ public void show(int x) {
+ System.out.println("Show with int: " + x);
+ }
+
+ public void show(int x, int y) {
+ System.out.println("Show with two ints: " + x + ", " + y);
+ }
+ }
+ ```
+
+2. **Different Types of Parameters**: Methods can have the same name and the same number of parameters but with
+ different types.
+
+ ```java
+ public class Display {
+
+ public void show(int x) {
+ System.out.println("Show with int: " + x);
+ }
+
+ public void show(String s) {
+ System.out.println("Show with String: " + s);
+ }
+ }
+ ```
+
+### Points to Remember
+
+- Overloaded methods must change the argument list.
+- Overloaded methods can also change the return type, but merely changing the return type does not constitute method
+ overloading.
+- Methods can be overloaded in the same class or in a subclass.
+
+In this concept, we will explore various examples and nuances of method overloading in Java.
diff --git a/exercises/concept/wizards-and-warriors-2/.docs/introduction.md.tpl b/exercises/concept/wizards-and-warriors-2/.docs/introduction.md.tpl
new file mode 100644
index 000000000..653f52789
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:method-overloading}
diff --git a/exercises/concept/wizards-and-warriors-2/.meta/config.json b/exercises/concept/wizards-and-warriors-2/.meta/config.json
new file mode 100644
index 000000000..ec5eca2b8
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.meta/config.json
@@ -0,0 +1,32 @@
+{
+ "authors": [
+ "sougat818"
+ ],
+ "contributors": [
+ "jagdish-15",
+ "sanderploegsma"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/GameMaster.java"
+ ],
+ "test": [
+ "src/test/java/GameMasterTest.java"
+ ],
+ "exemplar": [
+ ".meta/src/reference/java/GameMaster.java"
+ ],
+ "editor": [
+ "src/main/java/Character.java",
+ "src/main/java/Destination.java",
+ "src/main/java/TravelMethod.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "forked_from": [
+ "csharp/wizards-and-warriors-2"
+ ],
+ "blurb": "Learn about method overloading by extending your favorite RPG."
+}
diff --git a/exercises/concept/wizards-and-warriors-2/.meta/design.md b/exercises/concept/wizards-and-warriors-2/.meta/design.md
new file mode 100644
index 000000000..6e2a056d1
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.meta/design.md
@@ -0,0 +1,41 @@
+# Design
+
+## Goal
+
+The goal of this exercise is to teach the student the basics of the Concept of `Method Overloading` in Java.
+
+## Learning objectives
+
+- Know what method overloading is.
+- Know how to overload a method with different number of parameters
+- Know how to overload a method with different argument types
+
+## Concepts
+
+- `method-overloading`
+
+## Prerequisites
+
+This exercise's prerequisites Concepts are:
+
+- `classes`
+- `strings`
+- `enums`
+
+## Representer
+
+This exercise does not require any specific representation logic to be added to the [representer][representer-java].
+
+## Analyzer
+
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the student does not reuse the methods `describe(Character)`, `describe(Destination)` or `describe(TravelMethod)` to solve the `describe(Character, Destination, TravelMethod)` method, instruct them to do so.
+- `actionable`: If the student does not reuse the method `describe(Character)`, `describe(Destination)`, `describe(TravelMethod)` or `describe(Character, Destination, TravelMethod)` to solve the `describe(Character, Destination)` method, instruct them to do so.
+- `informative`: If the solution uses `String.format` in any of the `describe` methods, inform the student that this cause a small performance penalty compared to string concatenation.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
+
+[representer-java]: https://github.com/exercism/java-representer
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/Character.java b/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/Character.java
new file mode 100644
index 000000000..d7a4a4add
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/Character.java
@@ -0,0 +1,29 @@
+public class Character {
+ private String characterClass;
+ private int level;
+ private int hitPoints;
+
+ public String getCharacterClass() {
+ return characterClass;
+ }
+
+ public void setCharacterClass(String characterClass) {
+ this.characterClass = characterClass;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public void setLevel(int level) {
+ this.level = level;
+ }
+
+ public int getHitPoints() {
+ return hitPoints;
+ }
+
+ public void setHitPoints(int hitPoints) {
+ this.hitPoints = hitPoints;
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/Destination.java b/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/Destination.java
new file mode 100644
index 000000000..09f2ce4e3
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/Destination.java
@@ -0,0 +1,20 @@
+public class Destination {
+ private String name;
+ private int inhabitants;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getInhabitants() {
+ return inhabitants;
+ }
+
+ public void setInhabitants(int inhabitants) {
+ this.inhabitants = inhabitants;
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/GameMaster.java b/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/GameMaster.java
new file mode 100644
index 000000000..e32fd7d42
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/GameMaster.java
@@ -0,0 +1,28 @@
+public class GameMaster {
+
+ public String describe(Character character) {
+ return "You're a level %d %s with %d hit points.".formatted(character.getLevel(),
+ character.getCharacterClass(), character.getHitPoints());
+ }
+
+ public String describe(Destination destination) {
+ return "You've arrived at %s, which has %d inhabitants.".formatted(destination.getName(),
+ destination.getInhabitants());
+ }
+
+ public String describe(TravelMethod travelMethod) {
+ if (travelMethod == TravelMethod.WALKING) {
+ return "You're traveling to your destination by walking.";
+ }
+ return "You're traveling to your destination on horseback.";
+
+ }
+
+ public String describe(Character character, Destination destination, TravelMethod travelMethod) {
+ return "%s %s %s".formatted(describe(character), describe(travelMethod), describe(destination));
+ }
+
+ public String describe(Character character, Destination destination) {
+ return describe(character, destination, TravelMethod.WALKING);
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/TravelMethod.java b/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/TravelMethod.java
new file mode 100644
index 000000000..59d258ca8
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/.meta/src/reference/java/TravelMethod.java
@@ -0,0 +1,4 @@
+public enum TravelMethod {
+ WALKING,
+ HORSEBACK
+}
diff --git a/exercises/concept/wizards-and-warriors-2/build.gradle b/exercises/concept/wizards-and-warriors-2/build.gradle
new file mode 100644
index 000000000..d28f35dee
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors-2/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/wizards-and-warriors-2/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/wizards-and-warriors-2/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/wizards-and-warriors-2/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/wizards-and-warriors-2/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/wizards-and-warriors-2/gradlew b/exercises/concept/wizards-and-warriors-2/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/wizards-and-warriors-2/gradlew.bat b/exercises/concept/wizards-and-warriors-2/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/wizards-and-warriors-2/src/main/java/Character.java b/exercises/concept/wizards-and-warriors-2/src/main/java/Character.java
new file mode 100644
index 000000000..d7a4a4add
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/src/main/java/Character.java
@@ -0,0 +1,29 @@
+public class Character {
+ private String characterClass;
+ private int level;
+ private int hitPoints;
+
+ public String getCharacterClass() {
+ return characterClass;
+ }
+
+ public void setCharacterClass(String characterClass) {
+ this.characterClass = characterClass;
+ }
+
+ public int getLevel() {
+ return level;
+ }
+
+ public void setLevel(int level) {
+ this.level = level;
+ }
+
+ public int getHitPoints() {
+ return hitPoints;
+ }
+
+ public void setHitPoints(int hitPoints) {
+ this.hitPoints = hitPoints;
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors-2/src/main/java/Destination.java b/exercises/concept/wizards-and-warriors-2/src/main/java/Destination.java
new file mode 100644
index 000000000..09f2ce4e3
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/src/main/java/Destination.java
@@ -0,0 +1,20 @@
+public class Destination {
+ private String name;
+ private int inhabitants;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getInhabitants() {
+ return inhabitants;
+ }
+
+ public void setInhabitants(int inhabitants) {
+ this.inhabitants = inhabitants;
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors-2/src/main/java/GameMaster.java b/exercises/concept/wizards-and-warriors-2/src/main/java/GameMaster.java
new file mode 100644
index 000000000..180078609
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/src/main/java/GameMaster.java
@@ -0,0 +1,12 @@
+public class GameMaster {
+
+ // TODO: define a 'describe' method that returns a description of a Character
+
+ // TODO: define a 'describe' method that returns a description of a Destination
+
+ // TODO: define a 'describe' method that returns a description of a TravelMethod
+
+ // TODO: define a 'describe' method that returns a description of a Character, Destination and TravelMethod
+
+ // TODO: define a 'describe' method that returns a description of a Character and Destination
+}
diff --git a/exercises/concept/wizards-and-warriors-2/src/main/java/TravelMethod.java b/exercises/concept/wizards-and-warriors-2/src/main/java/TravelMethod.java
new file mode 100644
index 000000000..59d258ca8
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/src/main/java/TravelMethod.java
@@ -0,0 +1,4 @@
+public enum TravelMethod {
+ WALKING,
+ HORSEBACK
+}
diff --git a/exercises/concept/wizards-and-warriors-2/src/test/java/GameMasterProxy.java b/exercises/concept/wizards-and-warriors-2/src/test/java/GameMasterProxy.java
new file mode 100644
index 000000000..ca555b883
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/src/test/java/GameMasterProxy.java
@@ -0,0 +1,36 @@
+/**
+ * This is a helper class to be able to run the tests for this exercise.
+ * The tests are located in the {@link GameMasterTest} class.
+ *
+ * @see GameMasterTest
+ */
+public class GameMasterProxy extends ReflectionProxy {
+
+ @Override
+ public String getTargetClassName() {
+ return "GameMaster";
+ }
+
+ public String describe(Character character) {
+ return invokeMethod("describe", String.class, new Class[] { Character.class }, character);
+ }
+
+ public String describe(Destination character) {
+ return invokeMethod("describe", String.class, new Class[] { Destination.class }, character);
+ }
+
+ public String describe(TravelMethod character) {
+ return invokeMethod("describe", String.class, new Class[] { TravelMethod.class }, character);
+ }
+
+ public String describe(Character character, Destination destination, TravelMethod travelMethod) {
+ return invokeMethod("describe", String.class,
+ new Class[] { Character.class, Destination.class, TravelMethod.class },
+ character, destination, travelMethod);
+ }
+
+ public String describe(Character character, Destination destination) {
+ return invokeMethod("describe", String.class, new Class[] { Character.class, Destination.class },
+ character, destination);
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors-2/src/test/java/GameMasterTest.java b/exercises/concept/wizards-and-warriors-2/src/test/java/GameMasterTest.java
new file mode 100644
index 000000000..4aba1596c
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/src/test/java/GameMasterTest.java
@@ -0,0 +1,197 @@
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class GameMasterTest {
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Implemented the describeCharacter method")
+ public void implementedDescribeCharacter() {
+ assertThat(new GameMasterProxy().hasMethod("describe", Character.class))
+ .withFailMessage("Please implement the 'describe(Character character) method")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodPublic("describe", Character.class))
+ .withFailMessage("Method 'describe(Character character)' must be public")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodReturnType(String.class, "describe", Character.class))
+ .withFailMessage("Method 'describe(Character character)' must return a String")
+ .isTrue();
+
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Describe a character by class: Warrior")
+ public void describeWarriorCharacter() {
+ Character character = new Character();
+ character.setCharacterClass("Warrior");
+ character.setLevel(16);
+ character.setHitPoints(89);
+
+ assertThat(new GameMasterProxy().describe(character)).isEqualTo("You're a level 16 Warrior with 89 hit points" +
+ ".");
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("Describe a character by class: Wizard")
+ public void describeWizardCharacter() {
+ Character character = new Character();
+ character.setCharacterClass("Wizard");
+ character.setLevel(7);
+ character.setHitPoints(33);
+
+ assertThat(new GameMasterProxy().describe(character)).isEqualTo("You're a level 7 Wizard with 33 hit points.");
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Implemented the describeDestination method")
+ public void implementedDescribeDestination() {
+ assertThat(new GameMasterProxy().hasMethod("describe", Destination.class))
+ .withFailMessage("Please implement the 'describe(Destination destination)' method")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodPublic("describe", Destination.class))
+ .withFailMessage("Method 'describe(Destination destination)' must be public")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodReturnType(String.class, "describe", Destination.class))
+ .withFailMessage("Method 'describe(Destination destination)' must return a String")
+ .isTrue();
+
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Describe a destination: Tol Honeth")
+ public void describeSmallTownDestination() {
+ Destination destination = new Destination();
+ destination.setName("Tol Honeth");
+ destination.setInhabitants(41);
+
+ assertThat(new GameMasterProxy().describe(destination)).isEqualTo("You've arrived at Tol Honeth, which has 41" +
+ " inhabitants.");
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("Describe a destination: Ashaba")
+ public void describeLargeTownDestination() {
+ Destination destination = new Destination();
+ destination.setName("Ashaba");
+ destination.setInhabitants(1500);
+
+ assertThat(new GameMasterProxy().describe(destination)).isEqualTo("You've arrived at Ashaba, which has 1500 " +
+ "inhabitants.");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Implemented the describeTravelMethod method")
+ public void implementedDescribeTravelMethod() {
+ assertThat(new GameMasterProxy().hasMethod("describe", TravelMethod.class))
+ .withFailMessage("Please implement the 'describe(TravelMethod travelMethod)' " +
+ "method")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodPublic("describe", TravelMethod.class))
+ .withFailMessage("Method 'describe(TravelMethod travelMethod)' must be public")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodReturnType(String.class, "describe", TravelMethod.class))
+ .withFailMessage("Method 'describe(TravelMethod travelMethod)' must return a String")
+ .isTrue();
+
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Describe the travel method: walking")
+ public void describeWalkingTravelMethod() {
+ assertThat(new GameMasterProxy().describe(TravelMethod.WALKING)).isEqualTo("You're traveling to your " +
+ "destination by walking.");
+ }
+
+ @Test
+ @Tag("task:3")
+ @DisplayName("Describe the travel method: horseback")
+ public void describeHorseTravelMethod() {
+ assertThat(new GameMasterProxy().describe(TravelMethod.HORSEBACK)).isEqualTo("You're traveling to your " +
+ "destination on horseback.");
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("Implemented the describeCharacterToDestinationByTravelMethod method")
+ public void implementedDescribeCharacterTravelingToDestinationWithExplicitTravelMethod() {
+ assertThat(new GameMasterProxy().hasMethod("describe", Character.class, Destination.class, TravelMethod.class))
+ .withFailMessage("Please implement the 'describe(Character character, Destination destination, " +
+ "TravelMethod travelMethod)' method")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodPublic("describe", Character.class, Destination.class,
+ TravelMethod.class))
+ .withFailMessage("Method 'describe(Character character, Destination destination, TravelMethod " +
+ "travelMethod)' must be public")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodReturnType(String.class, "describe", Character.class,
+ Destination.class, TravelMethod.class))
+ .withFailMessage("Method 'describe(Character character, Destination destination, TravelMethod " +
+ "travelMethod)' must return a String")
+ .isTrue();
+
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("Describe a character traveling to a destination")
+ public void describeCharacterTravelingToDestinationWithExplicitTravelMethod() {
+ Character character = new Character();
+ character.setCharacterClass("Wizard");
+ character.setLevel(20);
+ character.setHitPoints(120);
+
+ Destination destination = new Destination();
+ destination.setName("Camaar");
+ destination.setInhabitants(999);
+
+ assertThat(new GameMasterProxy().describe(character, destination, TravelMethod.HORSEBACK)).isEqualTo(
+ "You're a level 20 Wizard with 120 hit points. You're traveling to your destination on horseback. " +
+ "You've arrived at Camaar, which has 999 inhabitants.");
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("Implemented the describeCharacterToDestination method")
+ public void implementedDescribeCharacterTravelingToDestinationWithoutExplicitTravelMethod() {
+ assertThat(new GameMasterProxy().hasMethod("describe", Character.class, Destination.class))
+ .withFailMessage("Please implement the 'describe(Character character, Destination destination)' method")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodPublic("describe", Character.class, Destination.class,
+ TravelMethod.class))
+ .withFailMessage("Method 'describe(Character character, Destination destination)' must be public")
+ .isTrue();
+ assertThat(new GameMasterProxy().isMethodReturnType(String.class, "describe", Character.class,
+ Destination.class, TravelMethod.class))
+ .withFailMessage("Method 'describe(Character character, Destination destination)' must return a String")
+ .isTrue();
+
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("Combined description should handle character and destination with default travel method")
+ public void describeCharacterTravelingToDestinationWithoutExplicitTravelMethod() {
+ Character character = new Character();
+ character.setCharacterClass("Warrior");
+ character.setLevel(1);
+ character.setHitPoints(30);
+
+ Destination destination = new Destination();
+ destination.setName("Vo Mimbre");
+ destination.setInhabitants(332);
+
+ assertThat(new GameMasterProxy().describe(character, destination)).isEqualTo(
+ "You're a level 1 Warrior with 30 hit points. You're traveling to your destination by walking. You've" +
+ " arrived at Vo Mimbre, which has 332 inhabitants.");
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors-2/src/test/java/ReflectionProxy.java b/exercises/concept/wizards-and-warriors-2/src/test/java/ReflectionProxy.java
new file mode 100644
index 000000000..ff21b5527
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors-2/src/test/java/ReflectionProxy.java
@@ -0,0 +1,507 @@
+import java.lang.reflect.*;
+
+import static java.lang.Class.forName;
+
+/**
+ * This is a helper class to be able to run the tests for this exercise.
+ * The tests are located in the {@link GameMasterTest} class.
+ * @see GameMasterTest
+ */
+public abstract class ReflectionProxy {
+
+ /**
+ * An instance of the target class (if found)
+ */
+ private final Object target;
+
+ /**
+ * A constructor to instantiate the target class with parameters
+ *
+ * @param args An array of parameters matching the constructor from the target class
+ */
+ protected ReflectionProxy(Object... args) {
+ this.target = instantiateTarget(args);
+ }
+
+ /**
+ * The default constructor, for when you have already an instance of the target class
+ *
+ * @param target An instance of the target class
+ */
+ protected ReflectionProxy(Object target) {
+ this.target = target;
+ }
+
+ /**
+ * Abstract method that defines the fully qualified name of the target class
+ *
+ * @return The fully qualified name of the target class
+ */
+ public abstract String getTargetClassName();
+
+ /**
+ * Getter for the target instance
+ *
+ * @return The target instance
+ */
+ public Object getTarget() {
+ return target;
+ }
+
+ /**
+ * Gets the target class
+ *
+ * @return The target class if it exists, null otherwise
+ */
+ public Class> getTargetClass() {
+ try {
+ return forName(this.getTargetClassName());
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Checks if the target class has a specific method
+ *
+ * @param name The name of the method to find
+ * @param parameterTypes The list of parameter types
+ * @return True if the method is found, false otherwise
+ */
+ public boolean hasMethod(String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ return m != null;
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a method from the target class is public
+ *
+ * @param name The name of the method
+ * @param parameterTypes A list of method parameters
+ * @return True if the method exists and is public, false otherwise
+ */
+ public boolean isMethodPublic(String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ return Modifier.isPublic(m.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a method from the target class returns the correct type
+ *
+ * @param returnType The type of return value
+ * @param name The name of the method
+ * @param parameterTypes The list of method parameters
+ * @return
+ */
+ public boolean isMethodReturnType(Class> returnType, String name, Class>... parameterTypes) {
+ return isMethodReturnType(returnType, null, name, parameterTypes);
+ }
+
+ /**
+ * Invokes a method from the target instance
+ *
+ * @param methodName The name of the method
+ * @param returnType The class representing the expected return type
+ * @param parameterTypes The list of parameter types
+ * @param parameterValues The list with values for the method parameters
+ * @param The result type we expect the method to be
+ * @return The value returned by the method
+ */
+ protected T invokeMethod(String methodName, Class returnType, Class>[] parameterTypes,
+ Object... parameterValues) {
+ if (target == null) {
+ throw new UnsupportedOperationException();
+ }
+ try {
+ try {
+ // getDeclaredMethod is used to get protected/private methods
+ Method method = target.getClass().getDeclaredMethod(methodName, parameterTypes);
+ method.setAccessible(true);
+ return returnType.cast(method.invoke(target, parameterValues));
+ } catch (NoSuchMethodException e) {
+ // try getting it from parent class, but only public methods will work
+ Method method = target.getClass().getMethod(methodName, parameterTypes);
+ method.setAccessible(true);
+ return returnType.cast(method.invoke(target, parameterValues));
+ }
+ } catch (Exception e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ /**
+ * Creates an instance of the target class
+ *
+ * @param args The list of constructor parameters
+ * @return An instance of the target class, if found, or null otherwise
+ */
+ private Object instantiateTarget(Object... args) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return null;
+ }
+ Constructor>[] constructors = getAllConstructors();
+ for (Constructor> c : constructors) {
+ if (c.getParameterCount() == args.length) {
+ try {
+ return c.newInstance(args);
+ } catch (Exception e) {
+ // do nothing;
+ }
+ }
+ }
+ return null;
+ }
+
+
+ //region Unused
+
+ /**
+ * Gets a list with all the constructors defined by the target class
+ *
+ * @return A list with all constructor definitions
+ */
+ private Constructor>[] getAllConstructors() {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return new Constructor>[]{};
+ }
+ return targetClass.getConstructors();
+ }
+
+ /**
+ * Checks if the target class exists
+ *
+ * @return True if the class exists, false otherwise
+ */
+ public boolean existsClass() {
+ return getTargetClass() != null;
+ }
+
+ /**
+ * Checks if the class implements a specific interface
+ *
+ * @param anInterface The interface to check
+ * @return True if the class implements the referred interface, false otherwise
+ */
+ public boolean implementsInterface(Class> anInterface) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || anInterface == null) {
+ return false;
+ }
+ return anInterface.isAssignableFrom(targetClass);
+ }
+
+ /**
+ * Checks if the target class has a specific property
+ *
+ * @param name The name of the property to find
+ * @return True if the property is found, false otherwise
+ */
+ public boolean hasProperty(String name) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Field f = targetClass.getDeclaredField(name);
+ return f != null;
+ } catch (NoSuchFieldException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if an existing property has the type we expect
+ *
+ * @param name The name of the property to check
+ * @param type The type you are expecting the property to be
+ * @return True if the property is found and has the specified type, false otherwise
+ */
+ public boolean isPropertyOfType(String name, Class> type) {
+ return isPropertyOfType(name, type, null);
+ }
+
+ /**
+ * Checks if an existing Collection type has the parameterized type (Generics) as expected (eg. List)
+ *
+ * @param name The name of the property
+ * @param type The type of the property (eg. List)
+ * @param parameterizedType The parameterized property (eg. String)
+ * @return True if the parameterized type matches the desired type, false otherwise
+ */
+ public boolean isPropertyOfType(String name, Class> type, Class> parameterizedType) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null || type == null) {
+ return false;
+ }
+ try {
+ Field f = targetClass.getDeclaredField(name);
+ if (!f.getType().equals(type)) {
+ return false;
+ }
+ if (parameterizedType == null) {
+ return true;
+ }
+ if (!(f.getGenericType() instanceof ParameterizedType)) {
+ return false;
+ }
+ ParameterizedType pType = (ParameterizedType) f.getGenericType();
+ return pType.getActualTypeArguments()[0].equals(parameterizedType);
+
+ } catch (NoSuchFieldException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a property is private
+ *
+ * @param name The name of the property
+ * @return True if the property exists and is private, false otherwise
+ */
+ public boolean isPropertyPrivate(String name) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Field f = targetClass.getDeclaredField(name);
+ return Modifier.isPrivate(f.getModifiers());
+ } catch (NoSuchFieldException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a method from the target class returns a correct parameterized collection (Generics)
+ *
+ * @param returnType The return type we expect (eg. List)
+ * @param parameterizedType The parameterized type we expect (eg. String)
+ * @param name The name of the method
+ * @param parameterTypes A list of method parameter types
+ * @return True if the method returns the correct parameterized collection, false otherwise
+ */
+ public boolean isMethodReturnType(Class> returnType, Class> parameterizedType,
+ String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ if (!m.getReturnType().equals(returnType)) {
+ return false;
+ }
+ if (parameterizedType == null) {
+ return true;
+ }
+ if (!(m.getGenericReturnType() instanceof ParameterizedType)) {
+ return false;
+ }
+ ParameterizedType pType = (ParameterizedType) m.getGenericReturnType();
+ return pType.getActualTypeArguments()[0].equals(parameterizedType);
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a target class has a specific constructor
+ *
+ * @param parameterTypes The list of desired parameter types
+ * @return True if the constructor exists, false otherwise
+ */
+ public boolean hasConstructor(Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ try {
+ Constructor> c = targetClass.getDeclaredConstructor(parameterTypes);
+ return c != null;
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a specific constructor from the target class is public
+ *
+ * @param parameterTypes A list of parameter types
+ * @return True if the constructor is found and is public, false otherwise
+ */
+ public boolean isConstructorPublic(Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ try {
+ Constructor> c = targetClass.getDeclaredConstructor(parameterTypes);
+ return Modifier.isPublic(c.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Proxy for the 'equals' method
+ * @param obj The ReflexionProxy object you want to compare against
+ * @return True if both targets are equal, false otherwise
+ */
+ public boolean equals(Object obj) {
+ if (target == null || !(obj instanceof ReflectionProxy)) {
+ return false;
+ }
+ try {
+ Method method = target.getClass().getMethod("equals", Object.class);
+ method.setAccessible(true);
+ return (boolean) method.invoke(target, ((ReflectionProxy) obj).getTarget());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Proxy for the 'hashCode' method
+ * @return The hashCode from the target class
+ */
+ public int hashCode() {
+ if (target == null) {
+ return 0;
+ }
+ try {
+ Method method = target.getClass().getMethod("hashCode");
+ method.setAccessible(true);
+ return (int) method.invoke(target);
+ } catch (Exception e) {
+ return 0;
+ }
+ }
+
+ /**
+ * Proxy for the 'toString' method from the target class
+ * @return The result of 'toString' from the target instance
+ */
+ public String toString() {
+ return invokeMethod("toString", String.class, new Class[]{ });
+ }
+
+ /**
+ * Gets a property value from the target instance (if it exists)
+ * @param propertyName The name of the property
+ * @param The type we are expecting it to be
+ * @return The value of the property (if it exists)
+ */
+ protected T getPropertyValue(String propertyName, Class propertyType) {
+ if (target == null || !hasProperty(propertyName)) {
+ return null;
+ }
+ try {
+ Field field = target.getClass().getDeclaredField(propertyName);
+ field.setAccessible(true);
+ return propertyType.cast(field.get(target));
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Checks if the target class is abstract
+ * @return True if the target class exists and is abstract, false otherwise
+ */
+ public boolean isAbstract() {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ return Modifier.isAbstract(targetClass.getModifiers());
+ }
+
+ /**
+ * Checks if the target class extends another
+ * @param className The fully qualified name of the class it should extend
+ * @return True if the target class extends the specified one, false otherwise
+ */
+ public boolean extendsClass(String className) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ try {
+ Class> parentClass = Class.forName(className);
+ return parentClass.isAssignableFrom(targetClass);
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the target class is an interface
+ * @return True if the target class exists and is an interface, false otherwise
+ */
+ public boolean isInterface() {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ return targetClass.isInterface();
+ }
+
+ /**
+ * Checks if a method is abstract
+ * @param name The name of the method
+ * @param parameterTypes The list of method parameter types
+ * @return True if the method exists and is abstract, false otherwise
+ */
+ public boolean isMethodAbstract(String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ return Modifier.isAbstract(m.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a method is protected
+ * @param name The name of the method
+ * @param parameterTypes The list of method parameter types
+ * @return True if the method exists and is protected, false otherwise
+ */
+ public boolean isMethodProtected(String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ return Modifier.isProtected(m.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ //endregion
+}
+
diff --git a/exercises/concept/wizards-and-warriors/.docs/hints.md b/exercises/concept/wizards-and-warriors/.docs/hints.md
index 7ef6c2a6e..d9d52fc58 100644
--- a/exercises/concept/wizards-and-warriors/.docs/hints.md
+++ b/exercises/concept/wizards-and-warriors/.docs/hints.md
@@ -2,45 +2,56 @@
## General
-Detailed explanation of inheritance can be found at [Inheritance][inheritance-main].
+Detailed explanation of inheritance can be found at [Inheritance][inheritance-concept].
The whole inheritance concept has a lot to do with the concepts around [overriding][java-overriding].
-## 1. Describe a Fighter.
+## 1. Create the Warrior class
-- In Java, the `toString()` method is actually present inside the Object class (which is a superclass to all the classes in Java).
- You can read more about it [here][object-class-java].
+- Review the [concept:java/classes](https://github.com/exercism/java/tree/main/concepts/classes) concept.
+- Review the [inheritance][inheritance-concept] concept.
-- To override this method inside your implementation class, you should have a method with same name i.e `toString()` and same return type
- i.e `String`.
+## 2. Describe a Warrior
-## 2. Making Fighters not vulnerable by default.
+- In Java, the `toString()` method is actually present inside the `Object` class (which is a superclass to all the classes in Java).
+ You can read more about it at the official [Oracle documentation][object-class-java].
+- To override this method inside your implementation class, you should have a method with same name i.e. `toString()` and same return type i.e. `String`.
+- The `toString()` method must be `public`.
-- Consider having a method `isVulnerable()` inside the `Fighter` class which states vulnerability of the fighter, return `false` to make it non-vulnerable by default.
-- This can than be overridden by any child class(the class extending `Fighter`), according to its requirements.
+## 3. Make Warriors invulnerable
-- Again the [overriding][java-overriding] concept will come handy.
+- Override the `isVulnerable()` method in the `Warrior` class to make Warriors always invulnerable.
-## 3. Allowing wizards to prepare a spell.
+## 4. Calculate the damage points for a Warrior
-- Preparing a spell can only be done by a wizard. So, it makes sense to have this property defined inside the `Wizard` class.
+- Override the `getDamagePoints(Fighter)` method in the `Warrior` class.
+- Use a [conditional statement][if-else] to return the damage points, taking into account the vulnerability of the target.
-- Create `prepareSpell()` method inside `Wizard` class.
+## 5. Create the Wizard class
-- Remember : Parent class(here `Fighter`) has no access to the properties of the child class(for example, `Wizard`)
+- Review the [concept:java/classes](https://github.com/exercism/java/tree/main/concepts/classes) concept.
+- Review the [inheritance][inheritance-concept] concept.
-## 4. Make Wizards vulnerable when not having prepared a spell
+## 6. Describe a Wizard
-- Override the `isVulnerable()` method in the `Wizard` class to make Wizards vulnerable if they haven't prepared a spell.
+- In Java, the `toString()` method is actually present inside the `Object` class (which is a superclass to all the classes in Java).
+ You can read more about it at the official [Oracle documentation][object-class-java].
+- To override this method inside your implementation class, you should have a method with same name i.e. `toString()` and same return type i.e. `String`.
+- The `toString()` method must be `public`.
-## 5. Calculate the damage points for a Wizard
+## 7. Allow Wizards to prepare a spell and make them vulnerable when not having prepared a spell
-- Use a [conditional statement][if-else] to return the damage points, taking into account the value of the prepare spell field.
+- Preparing a spell can only be done by a wizard. So, it makes sense to have this property defined inside the `Wizard` class.
+- Create `prepareSpell()` method inside `Wizard` class.
+- Remember: Parent class (here `Fighter`) has no access to the properties of the child class (for example, `Wizard`)
+- Remember: As the method does not have a return type you should use `void` instead.
+- Override the `isVulnerable()` method in the `Wizard` class to make Wizards vulnerable if they haven't prepared a spell.
-## 6. Calculate the damage points for a Warrior
+## 8. Calculate the damage points for a Wizard
-- Use a [conditional statement][if-else] to return the damage points, taking into account the vulnerability of the target.
+- Override the `getDamagePoints(Fighter)` method in the `Wizard` class.
+- Use a [conditional statement][if-else] to return the damage points, taking into account the value of the prepare spell field.
-[inheritance-main]: https://www.geeksforgeeks.org/inheritance-in-java/
+[inheritance-concept]: https://www.geeksforgeeks.org/inheritance-in-java/
[object-class-java]: https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
[java-overriding]: https://docs.oracle.com/javase/tutorial/java/IandI/override.html
[if-else]: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/if.html
diff --git a/exercises/concept/wizards-and-warriors/.docs/instructions.md b/exercises/concept/wizards-and-warriors/.docs/instructions.md
index b34d6f468..d832c0fad 100644
--- a/exercises/concept/wizards-and-warriors/.docs/instructions.md
+++ b/exercises/concept/wizards-and-warriors/.docs/instructions.md
@@ -1,83 +1,97 @@
# Instructions
-In this exercise you're playing a role-playing game named "Wizards and Warriors," which allows you to play as either a Wizard or a Warrior.
-
-There are different rules for Warriors and Wizards to determine how much damage points they deal.
+In this exercise you're playing a role-playing game where different types of fighters can combat each other.
+The game has different rules for each type of fighter.
+We are going to focus on two specific types: Wizards and Warriors.
For a Warrior, these are the rules:
-- Deal 6 points of damage if the fighter they are attacking is not vulnerable
-- Deal 10 points of damage if the fighter they are attacking is vulnerable
+- A Warrior is never vulnerable.
+- A Warrior deals `6` points of damage if the fighter they are attacking is not vulnerable.
+- A Warrior deals `10` points of damage if the fighter they are attacking is vulnerable.
For a Wizard, these are the rules:
-- Deal 12 points of damage if the Wizard prepared a spell in advance
-- Deal 3 points of damage if the Wizard did not prepare a spell in advance
+- A Wizard can prepare a spell in advance.
+- A Wizard is vulnerable unless they have prepared a spell in advance.
+- A Wizard deals `12` points of damage if they prepared a spell in advance.
+- A Wizard deals `3` points of damage if they did not prepare a spell in advance.
-In general, fighters are never vulnerable. However, Wizards _are_ vulnerable if they haven't prepared a spell.
+## 1. Create the Warrior class
-You have six tasks that work with Warriors and Wizard fighters.
+Create a new class called `Warrior`.
+This class should inherit from the existing `Fighter` class.
-## 1. Describe a fighter
+## 2. Describe a Warrior
-Override the `toString()` method on the `Fighter` class to return a description of the fighter, formatted as `"Fighter is a "`.
+Update the `Warrior` class so that its `toString()` method describes what kind of fighter they are.
+The method should return the string `"Fighter is a Warrior"`.
```java
-Fighter warrior = new Warrior();
+Warrior warrior = new Warrior();
warrior.toString();
// => "Fighter is a Warrior"
```
-## 2. Make fighters not vulnerable by default
+## 3. Make Warriors invulnerable
-Ensure that the `Fighter.isVulnerable()` method always returns `false`.
+Update the `Warrior` class so that its `isVulnerable()` method always returns `false`.
```java
-Fighter warrior = new Warrior();
+Warrior warrior = new Warrior();
warrior.isVulnerable();
// => false
```
-## 3. Allow Wizards to prepare a spell
+## 4. Calculate the damage points for a Warrior
-Implement the `Wizard.prepareSpell()` method to allow a Wizard to prepare a spell in advance.
+Update the `Warrior` class so that its `getDamagePoints(Fighter)` method calculates the damage dealt by a Warrior according to the rules above.
```java
+Warrior warrior = new Warrior();
Wizard wizard = new Wizard();
-wizard.prepareSpell();
+
+warrior.getDamagePoints(wizard);
+// => 10
```
-## 4. Make Wizards vulnerable when not having prepared a spell
+## 5. Create the Wizard class
+
+Create another new class called `Wizard`.
+This class should also inherit from the existing `Fighter` class.
+
+## 6. Describe a Wizard
-Ensure that the `isVulnerable()` method returns `true` if the wizard did not prepare a spell; otherwise, return `false`.
+Update the `Wizard` class so that its `toString()` method describes what kind of fighter they are.
+The method should return the string `"Fighter is a Wizard"`.
```java
-Fighter wizard = new Wizard();
-wizard.isVulnerable();
-// => true
+Wizard wizard = new Wizard();
+wizard.toString();
+// => "Fighter is a Wizard"
```
-## 5. Calculate the damage points for a Wizard
+## 7. Allow Wizards to prepare a spell and make them vulnerable when not having prepared a spell
-Implement the `Wizard.damagePoints()` method to return the damage points dealt: 12 damage points when a spell has been prepared, 3 damage points when not.
+Update the `Wizard` class to add a method called `prepareSpell()`.
+The class should remember when this method is called, and make sure that its `isVulnerable()` method returns `false` only when a spell is prepared.
```java
Wizard wizard = new Wizard();
-Warrior warrior = new Warrior();
-
wizard.prepareSpell();
-wizard.damagePoints(warrior);
-// => 12
+wizard.isVulnerable();
+// => false
```
-## 6. Calculate the damage points for a Warrior
+## 8. Calculate the damage points for a Wizard
-Implement the `Warrior.damagePoints()` method to return the damage points dealt: 10 damage points when the target is vulnerable, 6 damage points when not.
+Update the `Wizard` class so that its `getDamagePoints(Fighter)` method calculates the damage dealt by a Wizard according to the rules above.
```java
-Warrior warrior = new Warrior();
Wizard wizard = new Wizard();
+Warrior warrior = new Warrior();
-warrior.damagePoints(wizard);
-// => 10
+wizard.prepareSpell();
+wizard.getDamagePoints(warrior);
+// => 12
```
diff --git a/exercises/concept/wizards-and-warriors/.docs/introduction.md b/exercises/concept/wizards-and-warriors/.docs/introduction.md
index 66d1c52e0..a7e899cc0 100644
--- a/exercises/concept/wizards-and-warriors/.docs/introduction.md
+++ b/exercises/concept/wizards-and-warriors/.docs/introduction.md
@@ -1,8 +1,10 @@
# Introduction
-Inheritance is a core concept in OOP (Object Oriented Programming). It donates IS-A relationship.
-It literally means in programming as it means in english, inheriting features from parent(in programming features is normally functions
-and variables).
+## Inheritance
+
+Inheritance is a core concept in OOP (Object-Oriented Programming).
+It represents an IS-A relationship.
+It literally means in programming as it means in english, inheriting features from parent (in programming features is normally functions and variables).
Consider a class, `Animal` as shown,
@@ -11,7 +13,7 @@ Consider a class, `Animal` as shown,
public class Animal {
public void bark() {
- System.out.println("This is a animal");
+ System.out.println("This is an animal");
}
}
@@ -25,6 +27,7 @@ Consider an animal named `Lion`, having a class like,
//Lion class is a child class of Animal.
public class Lion extends Animal {
+ @Override
public void bark() {
System.out.println("Lion here!!");
}
@@ -32,6 +35,11 @@ public class Lion extends Animal {
}
```
+~~~~exercism/note
+The `Override` annotation is used to indicate that a method in a subclass is overriding a method of its superclass.
+It's not strictly necessary but it's a best practice to use it.
+~~~~
+
Now whenever we do,
```java
@@ -46,7 +54,7 @@ The output will look like
Lion here!!
```
-According to OOP, there are many types of inheritance, but Java supports only some of them(Multi-level and Hierarchical).
+According to OOP, there are many types of inheritance, but Java supports only some of them (Multi-level and Hierarchical).
To read more about it, please read [this][java-inheritance].
[java-inheritance]: https://www.javatpoint.com/inheritance-in-java#:~:text=On%20the%20basis%20of%20class,will%20learn%20about%20interfaces%20later.
diff --git a/exercises/concept/wizards-and-warriors/.docs/introduction.md.tpl b/exercises/concept/wizards-and-warriors/.docs/introduction.md.tpl
new file mode 100644
index 000000000..a0edac8b8
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors/.docs/introduction.md.tpl
@@ -0,0 +1,3 @@
+# Introduction
+
+%{concept:inheritance}
diff --git a/exercises/concept/wizards-and-warriors/.meta/config.json b/exercises/concept/wizards-and-warriors/.meta/config.json
index 7060ccf6c..78a8489e7 100644
--- a/exercises/concept/wizards-and-warriors/.meta/config.json
+++ b/exercises/concept/wizards-and-warriors/.meta/config.json
@@ -1,8 +1,11 @@
{
- "blurb": "Learn about inheritance by creating an RPG.",
"authors": [
"himanshugoyal1065"
],
+ "contributors": [
+ "manumafe98",
+ "sanderploegsma"
+ ],
"files": {
"solution": [
"src/main/java/Fighter.java"
@@ -12,9 +15,13 @@
],
"exemplar": [
".meta/src/reference/java/Fighter.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"forked_from": [
"csharp/wizards-and-warriors"
- ]
+ ],
+ "blurb": "Learn about inheritance by creating an RPG."
}
diff --git a/exercises/concept/wizards-and-warriors/.meta/design.md b/exercises/concept/wizards-and-warriors/.meta/design.md
index 7725581b1..2349e648b 100644
--- a/exercises/concept/wizards-and-warriors/.meta/design.md
+++ b/exercises/concept/wizards-and-warriors/.meta/design.md
@@ -9,25 +9,24 @@ The goal of this exercise is to teach the student the basics of the Concept of `
- Know what inheritance is.
- Know how to inherit from a class.
- Know that all types inherit from object.
+- Know what the override annotation means.
## Out of scope
- Inheritance from interfaces
+- Abstract classes
## Concepts
-- `inheritence`
-- `objects`
+- `inheritance`
## Prerequisites
This exercise's prerequisites Concepts are:
- `classes`
-- `abstract`
-- `functions`
- `strings`
-- `boolean`
+- `if-else-statements`
## Representer
@@ -35,7 +34,13 @@ This exercise does not require any specific representation logic to be added to
## Analyzer
-This exercise does not require any specific analyzer logic to be added to the [analyzer][analyzer-java].
+This exercise could benefit from the following rules in the [analyzer]:
+
+- `actionable`: If the student left any `// TODO: ...` comments in the code, instruct them to remove these.
+- `informative`: If the solution didn't use the `Override` annotation for the overrided methods, inform the student that it's a good practice to add it.
+
+If the solution does not receive any of the above feedback, it must be exemplar.
+Leave a `celebratory` comment to celebrate the success!
[representer-java]: https://github.com/exercism/java-representer
-[analyzer-java]: https://github.com/exercism/java-analyzer
+[analyzer]: https://github.com/exercism/java-analyzer
diff --git a/exercises/concept/wizards-and-warriors/.meta/src/reference/java/Fighter.java b/exercises/concept/wizards-and-warriors/.meta/src/reference/java/Fighter.java
index 87d865c72..3365aa2d6 100644
--- a/exercises/concept/wizards-and-warriors/.meta/src/reference/java/Fighter.java
+++ b/exercises/concept/wizards-and-warriors/.meta/src/reference/java/Fighter.java
@@ -1,16 +1,12 @@
-abstract class Fighter {
+class Fighter {
- /**
- * this method sets the default vulnerability to false for all the child classes.
- *
- * @return the vulnerability i.e false.
- */
boolean isVulnerable() {
- return false;
+ return true;
}
- abstract int damagePoints(Fighter fighter);
-
+ int getDamagePoints(Fighter fighter) {
+ return 1;
+ }
}
class Warrior extends Fighter {
@@ -21,12 +17,13 @@ public String toString() {
}
@Override
- int damagePoints(Fighter wizard) {
- if (wizard.isVulnerable()) {
- return 10;
- } else {
- return 6;
- }
+ public boolean isVulnerable() {
+ return false;
+ }
+
+ @Override
+ int getDamagePoints(Fighter target) {
+ return target.isVulnerable() ? 10 : 6;
}
}
@@ -34,25 +31,22 @@ class Wizard extends Fighter {
boolean isSpellPrepared = false;
+ @Override
+ public String toString() {
+ return "Fighter is a Wizard";
+ }
+
@Override
boolean isVulnerable() {
- if (isSpellPrepared == false) {
- return true;
- }
- return false;
+ return !isSpellPrepared;
}
@Override
- int damagePoints(Fighter warrior) {
- if (isSpellPrepared) {
- return 12;
- } else {
- return 3;
- }
+ int getDamagePoints(Fighter target) {
+ return isSpellPrepared ? 12 : 3;
}
void prepareSpell() {
isSpellPrepared = true;
}
-
}
diff --git a/exercises/concept/wizards-and-warriors/build.gradle b/exercises/concept/wizards-and-warriors/build.gradle
index ca30f8eea..d28f35dee 100644
--- a/exercises/concept/wizards-and-warriors/build.gradle
+++ b/exercises/concept/wizards-and-warriors/build.gradle
@@ -1,19 +1,24 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
+plugins {
+ id "java"
+}
repositories {
mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
+ useJUnitPlatform()
+
testLogging {
- exceptionFormat = 'full'
+ exceptionFormat = "full"
showStandardStreams = true
events = ["passed", "failed", "skipped"]
}
diff --git a/exercises/concept/wizards-and-warriors/gradle/wrapper/gradle-wrapper.jar b/exercises/concept/wizards-and-warriors/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/concept/wizards-and-warriors/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/concept/wizards-and-warriors/gradle/wrapper/gradle-wrapper.properties b/exercises/concept/wizards-and-warriors/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/concept/wizards-and-warriors/gradlew b/exercises/concept/wizards-and-warriors/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/concept/wizards-and-warriors/gradlew.bat b/exercises/concept/wizards-and-warriors/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/concept/wizards-and-warriors/src/main/java/Fighter.java b/exercises/concept/wizards-and-warriors/src/main/java/Fighter.java
index 6f4f8c392..752e6ff79 100644
--- a/exercises/concept/wizards-and-warriors/src/main/java/Fighter.java
+++ b/exercises/concept/wizards-and-warriors/src/main/java/Fighter.java
@@ -1,40 +1,14 @@
-abstract class Fighter {
+class Fighter {
boolean isVulnerable() {
- throw new UnsupportedOperationException("Please provide implementation for this method");
+ return true;
}
- abstract int damagePoints(Fighter fighter);
-
-}
-
-class Warrior extends Fighter {
-
- @Override
- public String toString() {
- throw new UnsupportedOperationException("Please implement the toString() method with the required text");
- }
-
- @Override
- int damagePoints(Fighter wizard) {
- throw new UnsupportedOperationException("Please implement Warrior.damagePoints() method");
+ int getDamagePoints(Fighter fighter) {
+ return 1;
}
}
-class Wizard extends Fighter {
-
- @Override
- boolean isVulnerable() {
- throw new UnsupportedOperationException("Please implement Wizard.isVulnerable() method");
- }
-
- @Override
- int damagePoints(Fighter warrior) {
- throw new UnsupportedOperationException("Please implement Wizard.damagePoints() method");
- }
+// TODO: define the Warrior class
- void prepareSpell() {
- throw new UnsupportedOperationException("Please implement Wizard.prepareSpell() method");
- }
-
-}
+// TODO: define the Wizard class
diff --git a/exercises/concept/wizards-and-warriors/src/test/java/FighterTest.java b/exercises/concept/wizards-and-warriors/src/test/java/FighterTest.java
index 09a3d8cb8..64c088199 100644
--- a/exercises/concept/wizards-and-warriors/src/test/java/FighterTest.java
+++ b/exercises/concept/wizards-and-warriors/src/test/java/FighterTest.java
@@ -1,50 +1,227 @@
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
-import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.*;
-public class FighterTest {
+class FighterTest {
+ private WarriorProxy warrior;
+ private WizardProxy wizard;
+
+ @BeforeEach
+ void setup() {
+ warrior = new WarriorProxy();
+ wizard = new WizardProxy();
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("The Warrior class is defined")
+ void testWarriorClassExists() {
+ try {
+ Class.forName("Warrior");
+ } catch (ClassNotFoundException e) {
+ fail("Should have a class called Warrior");
+ }
+ }
+
+ @Test
+ @Tag("task:1")
+ @DisplayName("The Warrior class inherits from the Fighter class")
+ void testWarriorIsInstanceOfFighter() throws ClassNotFoundException {
+ assertThat(Class.forName("Warrior")).isAssignableTo(Fighter.class);
+ }
+
+ @Test
+ @Tag("task:2")
+ @DisplayName("The Warrior class overrides the toString method")
+ void testWarriorOverridesToStringMethod() {
+ assertThat(warrior.hasMethod("toString"))
+ .withFailMessage("Method toString must be created")
+ .isTrue();
+ assertThat(warrior.isMethodPublic("toString"))
+ .withFailMessage("Method toString must be public")
+ .isTrue();
+ assertThat(warrior.isMethodReturnType(String.class, "toString"))
+ .withFailMessage("Method toString must return a String")
+ .isTrue();
+ }
@Test
- public void testWarriorToString() {
- Fighter warrior = new Warrior();
+ @Tag("task:2")
+ @DisplayName("The toString method of the Warrior returns the correct description of the fighter")
+ void testWarriorToString() {
assertThat(warrior.toString()).isEqualTo("Fighter is a Warrior");
}
@Test
- public void testWizardToString() {
- Wizard wizard = new Wizard();
- assertThat(wizard.toString()).isEqualTo("Fighter is a Wizard");
+ @Tag("task:3")
+ @DisplayName("The Warrior class overrides the isVulnerable method")
+ void testWarriorOverridesIsVulnerableMethod() {
+ assertThat(warrior.hasMethod("isVulnerable"))
+ .withFailMessage("Method isVulnerable must be created")
+ .isTrue();
+ assertThat(warrior.isMethodReturnType(boolean.class, "isVulnerable"))
+ .withFailMessage("Method isVulnerable must return a boolean")
+ .isTrue();
}
@Test
- public void testFighterVulnerableByDefault() {
- Fighter warrior = new Warrior();
+ @Tag("task:3")
+ @DisplayName("A Warrior is never vulnerable")
+ void testWarriorAlwaysInvulnerable() {
assertThat(warrior.isVulnerable()).isFalse();
}
@Test
- public void testWizardVulnerable() {
- Wizard wizard = new Wizard();
+ @Tag("task:4")
+ @DisplayName("The Warrior class overrides the getDamagePoints(Fighter) method")
+ void testWarriorOverridesGetDamagePointsMethod() {
+ assertThat(warrior.hasMethod("getDamagePoints", Fighter.class))
+ .withFailMessage("Method getDamagePoints(Fighter) must be created")
+ .isTrue();
+ assertThat(warrior.isMethodReturnType(int.class, "getDamagePoints", Fighter.class))
+ .withFailMessage("Method getDamagePoints(Fighter) must return an int")
+ .isTrue();
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("A Warrior deals 10 damage to a vulnerable target")
+ void testWarriorsDamagePointsWhenTargetVulnerable() {
+ assertThat(warrior.getDamagePoints(new VulnerableFighter())).isEqualTo(10);
+ }
+
+ @Test
+ @Tag("task:4")
+ @DisplayName("A Warrior deals 6 damage to an invulnerable target")
+ void testWarriorsDamagePointsWhenTargetNotVulnerable() {
+ assertThat(warrior.getDamagePoints(new InvulnerableFighter())).isEqualTo(6);
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("The Wizard class is defined")
+ void testWizardClassExists() {
+ try {
+ Class.forName("Wizard");
+ } catch (ClassNotFoundException e) {
+ fail("Should have a class called Wizard");
+ }
+ }
+
+ @Test
+ @Tag("task:5")
+ @DisplayName("The Wizard class inherits from the Fighter class")
+ void testWizardIsInstanceOfFighter() throws ClassNotFoundException {
+ assertThat(Class.forName("Wizard")).isAssignableTo(Fighter.class);
+ }
+
+ @Test
+ @Tag("task:6")
+ @DisplayName("The Wizard class overrides the toString method")
+ void testWizardOverridesToStringMethod() {
+ assertThat(wizard.hasMethod("toString"))
+ .withFailMessage("Method toString must be created")
+ .isTrue();
+ assertThat(warrior.isMethodPublic("toString"))
+ .withFailMessage("Method toString must be public")
+ .isTrue();
+ assertThat(wizard.isMethodReturnType(String.class, "toString"))
+ .withFailMessage("Method toString must return a String")
+ .isTrue();
+ }
+
+ @Test
+ @Tag("task:6")
+ @DisplayName("The toString method of the Wizard returns the correct description of the fighter")
+ void testWizardToString() {
+ assertThat(wizard.toString()).isEqualTo("Fighter is a Wizard");
+ }
+
+ @Test
+ @Tag("task:7")
+ @DisplayName("The Wizard class contains the prepareSpell method")
+ void testWizardHasPrepareSpellMethod() {
+ assertThat(wizard.hasMethod("prepareSpell"))
+ .withFailMessage("Method prepareSpell must be created")
+ .isTrue();
+ }
+
+ @Test
+ @Tag("task:7")
+ @DisplayName("The Fighter class does not contain the prepareSpell method")
+ void testFighterDoesNotHavePrepareSpellMethod() {
+ assertThatExceptionOfType(NoSuchMethodException.class)
+ .isThrownBy(() -> Fighter.class.getDeclaredMethod("prepareSpell"));
+ }
+
+ @Test
+ @Tag("task:7")
+ @DisplayName("The Wizard class overrides the isVulnerable method")
+ void testWizardOverridesIsVulnerableMethod() {
+ assertThat(wizard.hasMethod("isVulnerable"))
+ .withFailMessage("Method isVulnerable must be created")
+ .isTrue();
+ assertThat(wizard.isMethodReturnType(boolean.class, "isVulnerable"))
+ .withFailMessage("Method isVulnerable must return a boolean")
+ .isTrue();
+ }
+
+ @Test
+ @Tag("task:7")
+ @DisplayName("A Wizard is vulnerable when not prepared with a spell")
+ void testWizardVulnerableByDefault() {
assertThat(wizard.isVulnerable()).isTrue();
+ }
+
+ @Test
+ @Tag("task:7")
+ @DisplayName("A Wizard is not vulnerable when prepared with a spell")
+ void testWizardVulnerable() {
wizard.prepareSpell();
assertThat(wizard.isVulnerable()).isFalse();
}
@Test
- public void testWizardsDamagePoints() {
- Wizard wizard = new Wizard();
- Warrior warrior = new Warrior();
- assertThat(wizard.damagePoints(warrior)).isEqualTo(3);
- wizard.prepareSpell();
- assertThat(wizard.damagePoints(warrior)).isEqualTo(12);
+ @Tag("task:8")
+ @DisplayName("The Wizard class overrides the getDamagePoints(Fighter) method")
+ void testWizardOverridesGetDamagePointsMethod() {
+ assertThat(wizard.hasMethod("getDamagePoints", Fighter.class))
+ .withFailMessage("Method getDamagePoints(Fighter) must be created")
+ .isTrue();
+ assertThat(wizard.isMethodReturnType(int.class, "getDamagePoints", Fighter.class))
+ .withFailMessage("Method getDamagePoints(Fighter) must return an int")
+ .isTrue();
}
@Test
- public void testWarriorsDamagePoints() {
- Warrior warrior = new Warrior();
- Wizard wizard = new Wizard();
- assertThat(warrior.damagePoints(wizard)).isEqualTo(10);
+ @Tag("task:8")
+ @DisplayName("A Wizard deals 3 damage when no spell has been prepared")
+ void testWizardsDamagePoints() {
+ assertThat(wizard.getDamagePoints(new Fighter())).isEqualTo(3);
+ }
+
+ @Test
+ @Tag("task:8")
+ @DisplayName("A Wizard deals 12 damage after a spell has been prepared")
+ void testWizardsDamagePointsAfterPreparingSpell() {
wizard.prepareSpell();
- assertThat(warrior.damagePoints(wizard)).isEqualTo(6);
+ assertThat(wizard.getDamagePoints(new Fighter())).isEqualTo(12);
+ }
+
+ private static class VulnerableFighter extends Fighter {
+ @Override
+ boolean isVulnerable() {
+ return true;
+ }
+ }
+
+ private static class InvulnerableFighter extends Fighter {
+ @Override
+ boolean isVulnerable() {
+ return false;
+ }
}
}
diff --git a/exercises/concept/wizards-and-warriors/src/test/java/ReflectionProxy.java b/exercises/concept/wizards-and-warriors/src/test/java/ReflectionProxy.java
new file mode 100644
index 000000000..02a6460c7
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors/src/test/java/ReflectionProxy.java
@@ -0,0 +1,488 @@
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+
+import static java.lang.Class.forName;
+
+public abstract class ReflectionProxy {
+
+ /**
+ * An instance of the target class (if found)
+ */
+ private final Object target;
+
+ /**
+ * A constructor to instantiate the target class with parameters
+ * @param args An array of parameters matching the constructor from the target class
+ */
+ protected ReflectionProxy(Object... args) {
+ this.target = instantiateTarget(args);
+ }
+
+ /**
+ * Abstract method that defines the fully qualified name of the target class
+ * @return The fully qualified name of the target class
+ */
+ public abstract String getTargetClassName();
+
+ /**
+ * Getter for the target instance
+ * @return The target instance
+ */
+ public Object getTarget() {
+ return target;
+ }
+
+ /**
+ * Gets the target class
+ * @return The target class if it exists, null otherwise
+ */
+ public Class> getTargetClass() {
+ try {
+ return forName(this.getTargetClassName());
+ } catch (ClassNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Checks if the target class has a specific method
+ * @param name The name of the method to find
+ * @param parameterTypes The list of parameter types
+ * @return True if the method is found, false otherwise
+ */
+ public boolean hasMethod(String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ return m != null;
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a method from the target class is public
+ * @param name The name of the method
+ * @param parameterTypes A list of method parameters
+ * @return True if the method exists and is public, false otherwise
+ */
+ public boolean isMethodPublic(String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ return Modifier.isPublic(m.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a method from the target class returns the correct type
+ * @param returnType The type of return value
+ * @param name The name of the method
+ * @param parameterTypes The list of method parameters
+ * @return
+ */
+ public boolean isMethodReturnType(Class> returnType, String name, Class>... parameterTypes) {
+ return isMethodReturnType(returnType, null, name, parameterTypes);
+ }
+
+ /**
+ * Invokes a method from the target instance
+ * @param methodName The name of the method
+ * @param returnType The class representing the expected return type
+ * @param parameterTypes The list of parameter types
+ * @param parameterValues The list with values for the method parameters
+ * @param The result type we expect the method to be
+ * @return The value returned by the method
+ */
+ protected T invokeMethod(String methodName, Class returnType, Class>[] parameterTypes,
+ Object... parameterValues) {
+ if (target == null) {
+ return null;
+ }
+ try {
+ // getDeclaredMethod is used to get protected/private methods
+ Method method = target.getClass().getDeclaredMethod(methodName, parameterTypes);
+ method.setAccessible(true);
+ return returnType.cast(method.invoke(target, parameterValues));
+ } catch (NoSuchMethodException e) {
+ try {
+ // try getting it from parent class, but only public methods will work
+ Method method = target.getClass().getMethod(methodName, parameterTypes);
+ method.setAccessible(true);
+ return returnType.cast(method.invoke(target, parameterValues));
+ } catch (Exception ex) {
+ return null;
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Creates an instance of the target class
+ * @param args The list of constructor parameters
+ * @return An instance of the target class, if found, or null otherwise
+ */
+ private Object instantiateTarget(Object... args) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return null;
+ }
+ Constructor>[] constructors = getAllConstructors();
+ for (Constructor> c : constructors) {
+ if (c.getParameterCount() == args.length) {
+ try {
+ return c.newInstance(args);
+ } catch (Exception e) {
+ // do nothing;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets a list with all the constructors defined by the target class
+ * @return A list with all constructor definitions
+ */
+ private Constructor>[] getAllConstructors() {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return new Constructor>[]{};
+ }
+ return targetClass.getDeclaredConstructors();
+ }
+
+
+ //region Unused
+
+ /**
+ * The default constructor, for when you have already an instance of the target class
+ * @param target An instance of the target class
+ */
+ protected ReflectionProxy(Object target) {
+ this.target = target;
+ }
+
+ /**
+ * Checks if the target class exists
+ * @return True if the class exists, false otherwise
+ */
+ public boolean existsClass() {
+ return getTargetClass() != null;
+ }
+
+ /**
+ * Checks if the class implements a specific interface
+ * @param anInterface The interface to check
+ * @return True if the class implements the referred interface, false otherwise
+ */
+ public boolean implementsInterface(Class> anInterface) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || anInterface == null) {
+ return false;
+ }
+ return anInterface.isAssignableFrom(targetClass);
+ }
+
+ /**
+ * Checks if the target class has a specific property
+ * @param name The name of the property to find
+ * @return True if the property is found, false otherwise
+ */
+ public boolean hasProperty(String name) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Field f = targetClass.getDeclaredField(name);
+ return f != null;
+ } catch (NoSuchFieldException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if an existing property has the type we expect
+ * @param name The name of the property to check
+ * @param type The type you are expecting the property to be
+ * @return True if the property is found and has the specified type, false otherwise
+ */
+ public boolean isPropertyOfType(String name, Class> type) {
+ return isPropertyOfType(name, type, null);
+ }
+
+ /**
+ * Checks if an existing Collection type has the parameterized type (Generics) as expected (eg. List)
+ * @param name The name of the property
+ * @param type The type of the property (eg. List)
+ * @param parameterizedType The parameterized property (eg. String)
+ * @return True if the parameterized type matches the desired type, false otherwise
+ */
+ public boolean isPropertyOfType(String name, Class> type, Class> parameterizedType) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null || type == null) {
+ return false;
+ }
+ try {
+ Field f = targetClass.getDeclaredField(name);
+ if (!f.getType().equals(type)) {
+ return false;
+ }
+ if (parameterizedType == null) {
+ return true;
+ }
+ if (!(f.getGenericType() instanceof ParameterizedType)) {
+ return false;
+ }
+ ParameterizedType pType = (ParameterizedType) f.getGenericType();
+ return pType.getActualTypeArguments()[0].equals(parameterizedType);
+
+ } catch (NoSuchFieldException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a property is private
+ * @param name The name of the property
+ * @return True if the property exists and is private, false otherwise
+ */
+ public boolean isPropertyPrivate(String name) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Field f = targetClass.getDeclaredField(name);
+ return Modifier.isPrivate(f.getModifiers());
+ } catch (NoSuchFieldException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a method from the target class returns a correct parameterized collection (Generics)
+ * @param returnType The return type we expect (eg. List)
+ * @param parameterizedType The parameterized type we expect (eg. String)
+ * @param name The name of the method
+ * @param parameterTypes A list of method parameter types
+ * @return True if the method returns the correct parameterized collection, false otherwise
+ */
+ public boolean isMethodReturnType(Class> returnType, Class> parameterizedType,
+ String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ if (!m.getReturnType().equals(returnType)) {
+ return false;
+ }
+ if (parameterizedType == null) {
+ return true;
+ }
+ if (!(m.getGenericReturnType() instanceof ParameterizedType)) {
+ return false;
+ }
+ ParameterizedType pType = (ParameterizedType) m.getGenericReturnType();
+ return pType.getActualTypeArguments()[0].equals(parameterizedType);
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a target class has a specific constructor
+ * @param parameterTypes The list of desired parameter types
+ * @return True if the constructor exists, false otherwise
+ */
+ public boolean hasConstructor(Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ try {
+ Constructor> c = targetClass.getDeclaredConstructor(parameterTypes);
+ return c != null;
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a specific constructor from the target class is public
+ * @param parameterTypes A list of parameter types
+ * @return True if the constructor is found and is public, false otherwise
+ */
+ public boolean isConstructorPublic(Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ try {
+ Constructor> c = targetClass.getDeclaredConstructor(parameterTypes);
+ return Modifier.isPublic(c.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Proxy for the 'equals' method
+ * @param obj The ReflexionProxy object you want to compare against
+ * @return True if both targets are equal, false otherwise
+ */
+ public boolean equals(Object obj) {
+ if (target == null || !(obj instanceof ReflectionProxy)) {
+ return false;
+ }
+ try {
+ Method method = target.getClass().getMethod("equals", Object.class);
+ method.setAccessible(true);
+ return (boolean) method.invoke(target, ((ReflectionProxy) obj).getTarget());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ /**
+ * Proxy for the 'hashCode' method
+ * @return The hashCode from the target class
+ */
+ public int hashCode() {
+ if (target == null) {
+ return 0;
+ }
+ try {
+ Method method = target.getClass().getMethod("hashCode");
+ method.setAccessible(true);
+ return (int) method.invoke(target);
+ } catch (Exception e) {
+ return 0;
+ }
+ }
+
+ /**
+ * Proxy for the 'toString' method from the target class
+ * @return The result of 'toString' from the target instance
+ */
+ public String toString() {
+ return invokeMethod("toString", String.class, new Class[]{ });
+ }
+
+ /**
+ * Gets a property value from the target instance (if it exists)
+ * @param propertyName The name of the property
+ * @param propertyType The class representing the property's type
+ * @param The type we are expecting it to be
+ * @return The value of the property (if it exists)
+ */
+ protected T getPropertyValue(String propertyName, Class propertyType) {
+ if (target == null || !hasProperty(propertyName)) {
+ return null;
+ }
+ try {
+ Field field = target.getClass().getDeclaredField(propertyName);
+ field.setAccessible(true);
+ return propertyType.cast(field.get(target));
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Checks if the target class is abstract
+ * @return True if the target class exists and is abstract, false otherwise
+ */
+ public boolean isAbstract() {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ return Modifier.isAbstract(targetClass.getModifiers());
+ }
+
+ /**
+ * Checks if the target class extends another
+ * @param className The fully qualified name of the class it should extend
+ * @return True if the target class extends the specified one, false otherwise
+ */
+ public boolean extendsClass(String className) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ try {
+ Class> parentClass = Class.forName(className);
+ return parentClass.isAssignableFrom(targetClass);
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the target class is an interface
+ * @return True if the target class exists and is an interface, false otherwise
+ */
+ public boolean isInterface() {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null) {
+ return false;
+ }
+ return targetClass.isInterface();
+ }
+
+ /**
+ * Checks if a method is abstract
+ * @param name The name of the method
+ * @param parameterTypes The list of method parameter types
+ * @return True if the method exists and is abstract, false otherwise
+ */
+ public boolean isMethodAbstract(String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ return Modifier.isAbstract(m.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if a method is protected
+ * @param name The name of the method
+ * @param parameterTypes The list of method parameter types
+ * @return True if the method exists and is protected, false otherwise
+ */
+ public boolean isMethodProtected(String name, Class>... parameterTypes) {
+ Class> targetClass = getTargetClass();
+ if (targetClass == null || name == null) {
+ return false;
+ }
+ try {
+ Method m = targetClass.getDeclaredMethod(name, parameterTypes);
+ return Modifier.isProtected(m.getModifiers());
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ //endregion
+}
diff --git a/exercises/concept/wizards-and-warriors/src/test/java/WarriorProxy.java b/exercises/concept/wizards-and-warriors/src/test/java/WarriorProxy.java
new file mode 100644
index 000000000..2b6540578
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors/src/test/java/WarriorProxy.java
@@ -0,0 +1,19 @@
+class WarriorProxy extends ReflectionProxy {
+
+ @Override
+ public String getTargetClassName() {
+ return "Warrior";
+ }
+
+ public String toString() {
+ return invokeMethod("toString", String.class, new Class[0]);
+ }
+
+ boolean isVulnerable() {
+ return invokeMethod("isVulnerable", Boolean.class, new Class[0]);
+ }
+
+ int getDamagePoints(Fighter target) {
+ return invokeMethod("getDamagePoints", Integer.class, new Class[]{Fighter.class}, target);
+ }
+}
diff --git a/exercises/concept/wizards-and-warriors/src/test/java/WizardProxy.java b/exercises/concept/wizards-and-warriors/src/test/java/WizardProxy.java
new file mode 100644
index 000000000..5e47bf0ea
--- /dev/null
+++ b/exercises/concept/wizards-and-warriors/src/test/java/WizardProxy.java
@@ -0,0 +1,23 @@
+class WizardProxy extends ReflectionProxy {
+
+ @Override
+ public String getTargetClassName() {
+ return "Wizard";
+ }
+
+ public String toString() {
+ return invokeMethod("toString", String.class, new Class[0]);
+ }
+
+ boolean isVulnerable() {
+ return invokeMethod("isVulnerable", Boolean.class, new Class[0]);
+ }
+
+ int getDamagePoints(Fighter target) {
+ return invokeMethod("getDamagePoints", Integer.class, new Class[]{Fighter.class}, target);
+ }
+
+ void prepareSpell() {
+ invokeMethod("prepareSpell", Void.class, new Class[0]);
+ }
+}
diff --git a/exercises/gradle/wrapper/gradle-wrapper.jar b/exercises/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/gradle/wrapper/gradle-wrapper.properties b/exercises/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..23449a2b5
--- /dev/null
+++ b/exercises/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/gradlew b/exercises/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/gradlew.bat b/exercises/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/accumulate/.meta/config.json b/exercises/practice/accumulate/.meta/config.json
index 760cd77be..ade12e87b 100644
--- a/exercises/practice/accumulate/.meta/config.json
+++ b/exercises/practice/accumulate/.meta/config.json
@@ -29,6 +29,9 @@
],
"example": [
".meta/src/reference/java/Accumulate.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"source": "Conversation with James Edward Gray II",
diff --git a/exercises/practice/accumulate/src/main/java/Accumulate.java b/exercises/practice/accumulate/src/main/java/Accumulate.java
index e69de29bb..4dc8b701d 100644
--- a/exercises/practice/accumulate/src/main/java/Accumulate.java
+++ b/exercises/practice/accumulate/src/main/java/Accumulate.java
@@ -0,0 +1,10 @@
+import java.util.List;
+import java.util.function.UnaryOperator;
+
+public class Accumulate {
+
+ public static List accumulate(List list, UnaryOperator operator) {
+ throw new UnsupportedOperationException("Please implement the Accumulate.accumulate() method.");
+ }
+
+}
diff --git a/exercises/practice/acronym/.docs/instructions.md b/exercises/practice/acronym/.docs/instructions.md
index e0515b4d1..133bd2cbb 100644
--- a/exercises/practice/acronym/.docs/instructions.md
+++ b/exercises/practice/acronym/.docs/instructions.md
@@ -4,5 +4,14 @@ Convert a phrase to its acronym.
Techies love their TLA (Three Letter Acronyms)!
-Help generate some jargon by writing a program that converts a long name
-like Portable Network Graphics to its acronym (PNG).
+Help generate some jargon by writing a program that converts a long name like Portable Network Graphics to its acronym (PNG).
+
+Punctuation is handled as follows: hyphens are word separators (like whitespace); all other punctuation can be removed from the input.
+
+For example:
+
+| Input | Output |
+| ------------------------- | ------ |
+| As Soon As Possible | ASAP |
+| Liquid-crystal display | LCD |
+| Thank George It's Friday! | TGIF |
diff --git a/exercises/practice/acronym/.meta/config.json b/exercises/practice/acronym/.meta/config.json
index 41a47b459..653555482 100644
--- a/exercises/practice/acronym/.meta/config.json
+++ b/exercises/practice/acronym/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Convert a long phrase to its acronym.",
"authors": [
"matthewmorgan"
],
@@ -37,8 +36,12 @@
],
"example": [
".meta/src/reference/java/Acronym.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
+ "blurb": "Convert a long phrase to its acronym.",
"source": "Julien Vanier",
"source_url": "https://github.com/monkbroc"
}
diff --git a/exercises/practice/acronym/.meta/version b/exercises/practice/acronym/.meta/version
deleted file mode 100644
index bd8bf882d..000000000
--- a/exercises/practice/acronym/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.7.0
diff --git a/exercises/practice/acronym/build.gradle b/exercises/practice/acronym/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/acronym/build.gradle
+++ b/exercises/practice/acronym/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/acronym/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/acronym/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/acronym/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/acronym/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/acronym/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/acronym/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/acronym/gradlew b/exercises/practice/acronym/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/acronym/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/acronym/gradlew.bat b/exercises/practice/acronym/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/acronym/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/acronym/src/test/java/AcronymTest.java b/exercises/practice/acronym/src/test/java/AcronymTest.java
index 4cdcbdef5..09b419c4a 100644
--- a/exercises/practice/acronym/src/test/java/AcronymTest.java
+++ b/exercises/practice/acronym/src/test/java/AcronymTest.java
@@ -1,67 +1,77 @@
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class AcronymTest {
-
+
@Test
+ @DisplayName("basic")
public void basic() {
assertThat(new Acronym("Portable Network Graphics").get())
.isEqualTo("PNG");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("lowercase words")
public void lowercaseWords() {
assertThat(new Acronym("Ruby on Rails").get())
.isEqualTo("ROR");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("punctuation")
public void punctuation() {
assertThat(new Acronym("First In, First Out").get())
.isEqualTo("FIFO");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("all caps word")
public void nonAcronymAllCapsWord() {
assertThat(new Acronym("GNU Image Manipulation Program").get())
.isEqualTo("GIMP");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("punctuation without whitespace")
public void punctuationWithoutWhitespace() {
assertThat(new Acronym("Complementary metal-oxide semiconductor").get())
.isEqualTo("CMOS");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("very long abbreviation")
public void veryLongAbbreviation() {
assertThat(new Acronym("Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me").get())
.isEqualTo("ROTFLSHTMDCOALM");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("consecutive delimiters")
public void consecutiveDelimiters() {
assertThat(new Acronym("Something - I made up from thin air").get())
.isEqualTo("SIMUFTA");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("apostrophes")
public void apostrophes() {
assertThat(new Acronym("Halley's Comet").get())
.isEqualTo("HC");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("underscore emphasis")
public void underscoreEmphasis() {
assertThat(new Acronym("The Road _Not_ Taken").get())
.isEqualTo("TRNT");
diff --git a/exercises/practice/affine-cipher/.docs/instructions.append.md b/exercises/practice/affine-cipher/.docs/instructions.append.md
index 8dc0d94ea..e724f3faa 100644
--- a/exercises/practice/affine-cipher/.docs/instructions.append.md
+++ b/exercises/practice/affine-cipher/.docs/instructions.append.md
@@ -1,4 +1,4 @@
# Instructions append
-Please notice that the `%` operator is not equivalent to the one described in the problem description
+Please notice that the `%` operator is not equivalent to the one described in the problem description
([see Wikipedia entry for Modulo operation](https://en.wikipedia.org/wiki/Modulo_operation)).
diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md
index b3ebb9c76..1603dbbce 100644
--- a/exercises/practice/affine-cipher/.docs/instructions.md
+++ b/exercises/practice/affine-cipher/.docs/instructions.md
@@ -1,70 +1,74 @@
# Instructions
-Create an implementation of the affine cipher,
-an ancient encryption system created in the Middle East.
+Create an implementation of the affine cipher, an ancient encryption system created in the Middle East.
The affine cipher is a type of monoalphabetic substitution cipher.
-Each character is mapped to its numeric equivalent, encrypted with
-a mathematical function and then converted to the letter relating to
-its new numeric value. Although all monoalphabetic ciphers are weak,
-the affine cypher is much stronger than the atbash cipher,
-because it has many more keys.
+Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value.
+Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the Atbash cipher, because it has many more keys.
+
+[//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic "
+
+## Encryption
The encryption function is:
- `E(x) = (ax + b) mod m`
- - where `x` is the letter's index from 0 - length of alphabet - 1
- - `m` is the length of the alphabet. For the roman alphabet `m == 26`.
- - and `a` and `b` make the key
+```text
+E(x) = (ai + b) mod m
+```
-The decryption function is:
+Where:
+
+- `i` is the letter's index from `0` to the length of the alphabet - 1.
+- `m` is the length of the alphabet.
+ For the Latin alphabet `m` is `26`.
+- `a` and `b` are integers which make up the encryption key.
+
+Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]).
+In case `a` is not coprime to `m`, your program should indicate that this is an error.
+Otherwise it should encrypt or decrypt with the provided key.
+
+For the purpose of this exercise, digits are valid input but they are not encrypted.
+Spaces and punctuation characters are excluded.
+Ciphertext is written out in groups of fixed length separated by space, the traditional group size being `5` letters.
+This is to make it harder to guess encrypted text based on word boundaries.
- `D(y) = a^-1(y - b) mod m`
- - where `y` is the numeric value of an encrypted letter, ie. `y = E(x)`
- - it is important to note that `a^-1` is the modular multiplicative inverse
- of `a mod m`
- - the modular multiplicative inverse of `a` only exists if `a` and `m` are
- coprime.
+## Decryption
-To find the MMI of `a`:
+The decryption function is:
+
+```text
+D(y) = (a^-1)(y - b) mod m
+```
- `an mod m = 1`
- - where `n` is the modular multiplicative inverse of `a mod m`
+Where:
-More information regarding how to find a Modular Multiplicative Inverse
-and what it means can be found [here.](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse)
+- `y` is the numeric value of an encrypted letter, i.e., `y = E(x)`
+- it is important to note that `a^-1` is the modular multiplicative inverse (MMI) of `a mod m`
+- the modular multiplicative inverse only exists if `a` and `m` are coprime.
-Because automatic decryption fails if `a` is not coprime to `m` your
-program should return status 1 and `"Error: a and m must be coprime."`
-if they are not. Otherwise it should encode or decode with the
-provided key.
+The MMI of `a` is `x` such that the remainder after dividing `ax` by `m` is `1`:
-The Caesar (shift) cipher is a simple affine cipher where `a` is 1 and
-`b` as the magnitude results in a static displacement of the letters.
-This is much less secure than a full implementation of the affine cipher.
+```text
+ax mod m = 1
+```
-Ciphertext is written out in groups of fixed length, the traditional group
-size being 5 letters, and punctuation is excluded. This is to make it
-harder to guess things based on word boundaries.
+More information regarding how to find a Modular Multiplicative Inverse and what it means can be found in the [related Wikipedia article][mmi].
## General Examples
- - Encoding `test` gives `ybty` with the key a=5 b=7
- - Decoding `ybty` gives `test` with the key a=5 b=7
- - Decoding `ybty` gives `lqul` with the wrong key a=11 b=7
- - Decoding `kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx`
- - gives `thequickbrownfoxjumpsoverthelazydog` with the key a=19 b=13
- - Encoding `test` with the key a=18 b=13
- - gives `Error: a and m must be coprime.`
- - because a and m are not relatively prime
-
-## Examples of finding a Modular Multiplicative Inverse (MMI)
-
- - simple example:
- - `9 mod 26 = 9`
- - `9 * 3 mod 26 = 27 mod 26 = 1`
- - `3` is the MMI of `9 mod 26`
- - a more complicated example:
- - `15 mod 26 = 15`
- - `15 * 7 mod 26 = 105 mod 26 = 1`
- - `7` is the MMI of `15 mod 26`
+- Encrypting `"test"` gives `"ybty"` with the key `a = 5`, `b = 7`
+- Decrypting `"ybty"` gives `"test"` with the key `a = 5`, `b = 7`
+- Decrypting `"ybty"` gives `"lqul"` with the wrong key `a = 11`, `b = 7`
+- Decrypting `"kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx"` gives `"thequickbrownfoxjumpsoverthelazydog"` with the key `a = 19`, `b = 13`
+- Encrypting `"test"` with the key `a = 18`, `b = 13` is an error because `18` and `26` are not coprime
+
+## Example of finding a Modular Multiplicative Inverse (MMI)
+
+Finding MMI for `a = 15`:
+
+- `(15 * x) mod 26 = 1`
+- `(15 * 7) mod 26 = 1`, ie. `105 mod 26 = 1`
+- `7` is the MMI of `15 mod 26`
+
+[mmi]: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse
+[coprime-integers]: https://en.wikipedia.org/wiki/Coprime_integers
diff --git a/exercises/practice/affine-cipher/.meta/config.json b/exercises/practice/affine-cipher/.meta/config.json
index 11dcbcdcd..ee95ddda4 100644
--- a/exercises/practice/affine-cipher/.meta/config.json
+++ b/exercises/practice/affine-cipher/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.",
"authors": [
"lemoncurry"
],
@@ -20,8 +19,12 @@
],
"example": [
".meta/src/reference/java/AffineCipher.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
+ "blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.",
"source": "Wikipedia",
- "source_url": "http://en.wikipedia.org/wiki/Affine_cipher"
+ "source_url": "https://en.wikipedia.org/wiki/Affine_cipher"
}
diff --git a/exercises/practice/affine-cipher/.meta/version b/exercises/practice/affine-cipher/.meta/version
deleted file mode 100644
index 227cea215..000000000
--- a/exercises/practice/affine-cipher/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-2.0.0
diff --git a/exercises/practice/affine-cipher/build.gradle b/exercises/practice/affine-cipher/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/affine-cipher/build.gradle
+++ b/exercises/practice/affine-cipher/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/affine-cipher/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/affine-cipher/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/affine-cipher/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/affine-cipher/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/affine-cipher/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/affine-cipher/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/affine-cipher/gradlew b/exercises/practice/affine-cipher/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/affine-cipher/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/affine-cipher/gradlew.bat b/exercises/practice/affine-cipher/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/affine-cipher/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/affine-cipher/src/main/java/AffineCipher.java b/exercises/practice/affine-cipher/src/main/java/AffineCipher.java
index 6178f1beb..ed2f14c46 100644
--- a/exercises/practice/affine-cipher/src/main/java/AffineCipher.java
+++ b/exercises/practice/affine-cipher/src/main/java/AffineCipher.java
@@ -1,10 +1,10 @@
-/*
+public class AffineCipher {
+
+ public String encode(String text, int coefficient1, int coefficient2){
+ throw new UnsupportedOperationException("Please implement AffineCipher.encode() method.");
+ }
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
-
-Please remove this comment when submitting your solution.
-
-*/
+ public String decode(String text, int coefficient1, int coefficient2){
+ throw new UnsupportedOperationException("Please implement AffineCipher.decode() method.");
+ }
+}
\ No newline at end of file
diff --git a/exercises/practice/affine-cipher/src/test/java/AffineCipherTest.java b/exercises/practice/affine-cipher/src/test/java/AffineCipherTest.java
index ab534bda8..25bad1a0b 100644
--- a/exercises/practice/affine-cipher/src/test/java/AffineCipherTest.java
+++ b/exercises/practice/affine-cipher/src/test/java/AffineCipherTest.java
@@ -1,5 +1,6 @@
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -9,108 +10,123 @@ public class AffineCipherTest {
private AffineCipher affineCipher = new AffineCipher();
@Test
+ @DisplayName("encode yes")
public void testEncodeYes() {
assertThat(affineCipher.encode("yes", 5, 7)).isEqualTo("xbt");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode no")
public void testEncodeNo() {
assertThat(affineCipher.encode("no", 15, 18)).isEqualTo("fu");
}
-
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
+ @DisplayName("encode OMG")
@Test
public void testEncodeOMG() {
assertThat(affineCipher.encode("OMG", 21, 3)).isEqualTo("lvz");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void testEncodeO_M_G() {
+ @DisplayName("encode O M G")
+ public void testEncodeOMGWithSpaces() {
assertThat(affineCipher.encode("O M G", 25, 47)).isEqualTo("hjp");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode mindblowingly")
public void testEncodeMindBlowingly() {
assertThat(affineCipher.encode("mindblowingly", 11, 15)).isEqualTo("rzcwa gnxzc dgt");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode numbers")
public void testEncodeNumbers() {
assertThat(affineCipher.encode("Testing,1 2 3, testing.", 3, 4))
.isEqualTo("jqgjc rw123 jqgjc rw");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode deep thought")
public void testEncodeDeepThought() {
assertThat(affineCipher.encode("Truth is fiction.", 5, 17))
.isEqualTo("iynia fdqfb ifje");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode all the letters")
public void testEncodeAllTheLetters() {
assertThat(affineCipher.encode("The quick brown fox jumps over the lazy dog.", 17, 33))
.isEqualTo("swxtj npvyk lruol iejdc blaxk swxmh qzglf");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode with a not coprime to m")
public void testEncodeThrowsMeaningfulException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> affineCipher.encode("This is a test", 6, 17))
.withMessage("Error: keyA and alphabet size must be coprime.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode exercism")
public void testDecodeExercism() {
assertThat(affineCipher.decode("tytgn fjr", 3, 7))
.isEqualTo("exercism");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode a sentence")
public void testDecodeSentence() {
assertThat(affineCipher.decode("qdwju nqcro muwhn odqun oppmd aunwd o", 19, 16))
.isEqualTo("anobstacleisoftenasteppingstone");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode numbers")
public void testDecodeNumbers() {
assertThat(affineCipher.decode("odpoz ub123 odpoz ub", 25, 7))
.isEqualTo("testing123testing");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode all the letters")
public void testDecodeAllTheLetters() {
assertThat(affineCipher.decode("swxtj npvyk lruol iejdc blaxk swxmh qzglf", 17, 33))
.isEqualTo("thequickbrownfoxjumpsoverthelazydog");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode with no spaces in input")
public void testDecodeWithNoSpaces() {
assertThat(affineCipher.decode("swxtjnpvyklruoliejdcblaxkswxmhqzglf", 17, 33))
.isEqualTo("thequickbrownfoxjumpsoverthelazydog");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode with too many spaces")
public void testDecodeWithTooManySpaces() {
assertThat(affineCipher.decode("vszzm cly yd cg qdp", 15, 16))
.isEqualTo("jollygreengiant");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode with a not coprime to m")
public void testDecodeThrowsMeaningfulException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> affineCipher.decode("Test", 13, 5))
diff --git a/exercises/practice/all-your-base/.docs/instructions.md b/exercises/practice/all-your-base/.docs/instructions.md
index c39686f28..1b688b691 100644
--- a/exercises/practice/all-your-base/.docs/instructions.md
+++ b/exercises/practice/all-your-base/.docs/instructions.md
@@ -1,32 +1,28 @@
# Instructions
-Convert a number, represented as a sequence of digits in one base, to any other base.
+Convert a sequence of digits in one base, representing a number, into a sequence of digits in another base, representing the same number.
-Implement general base conversion. Given a number in base **a**,
-represented as a sequence of digits, convert it to base **b**.
+~~~~exercism/note
+Try to implement the conversion yourself.
+Do not use something else to perform the conversion for you.
+~~~~
-## Note
+## About [Positional Notation][positional-notation]
-- Try to implement the conversion yourself.
- Do not use something else to perform the conversion for you.
+In positional notation, a number in base **b** can be understood as a linear combination of powers of **b**.
-## About [Positional Notation](https://en.wikipedia.org/wiki/Positional_notation)
+The number 42, _in base 10_, means:
-In positional notation, a number in base **b** can be understood as a linear
-combination of powers of **b**.
+`(4 × 10¹) + (2 × 10⁰)`
-The number 42, *in base 10*, means:
+The number 101010, _in base 2_, means:
-(4 * 10^1) + (2 * 10^0)
+`(1 × 2⁵) + (0 × 2⁴) + (1 × 2³) + (0 × 2²) + (1 × 2¹) + (0 × 2⁰)`
-The number 101010, *in base 2*, means:
+The number 1120, _in base 3_, means:
-(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)
+`(1 × 3³) + (1 × 3²) + (2 × 3¹) + (0 × 3⁰)`
-The number 1120, *in base 3*, means:
+_Yes. Those three numbers above are exactly the same. Congratulations!_
-(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)
-
-I think you got the idea!
-
-*Yes. Those three numbers above are exactly the same. Congratulations!*
+[positional-notation]: https://en.wikipedia.org/wiki/Positional_notation
diff --git a/exercises/practice/all-your-base/.docs/introduction.md b/exercises/practice/all-your-base/.docs/introduction.md
new file mode 100644
index 000000000..68aaffbed
--- /dev/null
+++ b/exercises/practice/all-your-base/.docs/introduction.md
@@ -0,0 +1,8 @@
+# Introduction
+
+You've just been hired as professor of mathematics.
+Your first week went well, but something is off in your second week.
+The problem is that every answer given by your students is wrong!
+Luckily, your math skills have allowed you to identify the problem: the student answers _are_ correct, but they're all in base 2 (binary)!
+Amazingly, it turns out that each week, the students use a different base.
+To help you quickly verify the student answers, you'll be building a tool to translate between bases.
diff --git a/exercises/practice/all-your-base/.meta/config.json b/exercises/practice/all-your-base/.meta/config.json
index b4d65dcc0..d190857ee 100644
--- a/exercises/practice/all-your-base/.meta/config.json
+++ b/exercises/practice/all-your-base/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Convert a number, represented as a sequence of digits in one base, to any other base.",
"authors": [
"stkent"
],
@@ -33,6 +32,10 @@
],
"example": [
".meta/src/reference/java/BaseConverter.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
- }
+ },
+ "blurb": "Convert a number, represented as a sequence of digits in one base, to any other base."
}
diff --git a/exercises/practice/all-your-base/.meta/version b/exercises/practice/all-your-base/.meta/version
deleted file mode 100644
index 276cbf9e2..000000000
--- a/exercises/practice/all-your-base/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-2.3.0
diff --git a/exercises/practice/all-your-base/build.gradle b/exercises/practice/all-your-base/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/all-your-base/build.gradle
+++ b/exercises/practice/all-your-base/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/all-your-base/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/all-your-base/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/all-your-base/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/all-your-base/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/all-your-base/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/all-your-base/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/all-your-base/gradlew b/exercises/practice/all-your-base/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/all-your-base/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/all-your-base/gradlew.bat b/exercises/practice/all-your-base/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/all-your-base/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/all-your-base/src/main/java/BaseConverter.java b/exercises/practice/all-your-base/src/main/java/BaseConverter.java
index 6178f1beb..c347144ec 100644
--- a/exercises/practice/all-your-base/src/main/java/BaseConverter.java
+++ b/exercises/practice/all-your-base/src/main/java/BaseConverter.java
@@ -1,10 +1,11 @@
-/*
+class BaseConverter {
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+ BaseConverter(int originalBase, int[] originalDigits) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-Please remove this comment when submitting your solution.
+ int[] convertToBase(int newBase) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-*/
+}
\ No newline at end of file
diff --git a/exercises/practice/all-your-base/src/test/java/BaseConverterTest.java b/exercises/practice/all-your-base/src/test/java/BaseConverterTest.java
index 67e03f144..c2ca95a70 100644
--- a/exercises/practice/all-your-base/src/test/java/BaseConverterTest.java
+++ b/exercises/practice/all-your-base/src/test/java/BaseConverterTest.java
@@ -1,5 +1,6 @@
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -7,6 +8,7 @@
public class BaseConverterTest {
@Test
+ @DisplayName("single bit one to decimal")
public void testSingleBitOneToDecimal() {
BaseConverter baseConverter = new BaseConverter(2, new int[]{1});
@@ -14,8 +16,9 @@ public void testSingleBitOneToDecimal() {
.containsExactly(1);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("binary to single decimal")
public void testBinaryToSingleDecimal() {
BaseConverter baseConverter = new BaseConverter(2, new int[]{1, 0, 1});
@@ -23,8 +26,9 @@ public void testBinaryToSingleDecimal() {
.containsExactly(5);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("single decimal to binary")
public void testSingleDecimalToBinary() {
BaseConverter baseConverter = new BaseConverter(10, new int[]{5});
@@ -32,8 +36,9 @@ public void testSingleDecimalToBinary() {
.containsExactly(1, 0, 1);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("binary to multiple decimal")
public void testBinaryToMultipleDecimal() {
BaseConverter baseConverter = new BaseConverter(2, new int[]{1, 0, 1, 0, 1, 0});
@@ -41,8 +46,9 @@ public void testBinaryToMultipleDecimal() {
.containsExactly(4, 2);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decimal to binary")
public void testDecimalToBinary() {
BaseConverter baseConverter = new BaseConverter(10, new int[]{4, 2});
@@ -50,8 +56,9 @@ public void testDecimalToBinary() {
.containsExactly(1, 0, 1, 0, 1, 0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("trinary to hexadecimal")
public void testTrinaryToHexadecimal() {
BaseConverter baseConverter = new BaseConverter(3, new int[]{1, 1, 2, 0});
@@ -59,8 +66,9 @@ public void testTrinaryToHexadecimal() {
.containsExactly(2, 10);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("hexadecimal to trinary")
public void testHexadecimalToTrinary() {
BaseConverter baseConverter = new BaseConverter(16, new int[]{2, 10});
@@ -68,8 +76,9 @@ public void testHexadecimalToTrinary() {
.containsExactly(1, 1, 2, 0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("15-bit integer")
public void test15BitInteger() {
BaseConverter baseConverter = new BaseConverter(97, new int[]{3, 46, 60});
@@ -77,8 +86,9 @@ public void test15BitInteger() {
.containsExactly(6, 10, 45);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("empty list")
public void testEmptyDigits() {
BaseConverter baseConverter = new BaseConverter(2, new int[]{});
@@ -86,8 +96,9 @@ public void testEmptyDigits() {
.containsExactly(0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("single zero")
public void testSingleZero() {
BaseConverter baseConverter = new BaseConverter(10, new int[]{0});
@@ -95,8 +106,9 @@ public void testSingleZero() {
.containsExactly(0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("multiple zeros")
public void testMultipleZeros() {
BaseConverter baseConverter = new BaseConverter(10, new int[]{0, 0, 0});
@@ -104,8 +116,9 @@ public void testMultipleZeros() {
.containsExactly(0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("leading zeros")
public void testLeadingZeros() {
BaseConverter baseConverter = new BaseConverter(7, new int[]{0, 6, 0});
@@ -113,48 +126,54 @@ public void testLeadingZeros() {
.containsExactly(4, 2);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("input base is one")
public void testFirstBaseIsOne() {
assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> new BaseConverter(1, new int[]{1}))
+ .isThrownBy(() -> new BaseConverter(1, new int[]{0}))
.withMessage("Bases must be at least 2.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("input base is zero")
public void testFirstBaseIsZero() {
assertThatExceptionOfType(IllegalArgumentException.class)
- .isThrownBy(() -> new BaseConverter(0, new int[]{1}))
+ .isThrownBy(() -> new BaseConverter(0, new int[]{}))
.withMessage("Bases must be at least 2.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("input base is negative")
public void testFirstBaseIsNegative() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new BaseConverter(-2, new int[]{1}))
.withMessage("Bases must be at least 2.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("negative digit")
public void testNegativeDigit() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new BaseConverter(2, new int[]{1, -1, 1, 0, 1, 0}))
.withMessage("Digits may not be negative.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("invalid positive digit")
public void testInvalidPositiveDigit() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> new BaseConverter(2, new int[]{1, 2, 1, 0, 1, 0}))
.withMessage("All digits must be strictly less than the base.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("output base is one")
public void testSecondBaseIsOne() {
BaseConverter baseConverter =
new BaseConverter(2, new int[]{1, 0, 1, 0, 1, 0});
@@ -164,8 +183,9 @@ public void testSecondBaseIsOne() {
.withMessage("Bases must be at least 2.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("output base is zero")
public void testSecondBaseIsZero() {
BaseConverter baseConverter = new BaseConverter(10, new int[]{7});
@@ -174,8 +194,9 @@ public void testSecondBaseIsZero() {
.withMessage("Bases must be at least 2.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("output base is negative")
public void testSecondBaseIsNegative() {
BaseConverter baseConverter = new BaseConverter(2, new int[]{1});
diff --git a/exercises/practice/allergies/.docs/instructions.append.md b/exercises/practice/allergies/.docs/instructions.append.md
deleted file mode 100644
index 1dac9bb20..000000000
--- a/exercises/practice/allergies/.docs/instructions.append.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Instructions append
-
-Since this exercise has difficulty 5 it doesn't come with any starter implementation.
-This is so that you get to practice creating classes and methods which is an important part of programming in Java.
-It does mean that when you first try to run the tests, they won't compile.
-They will give you an error similar to:
-```
- path-to-exercism-dir\exercism\java\name-of-exercise\src\test\java\ExerciseClassNameTest.java:14: error: cannot find symbol
- ExerciseClassName exerciseClassName = new ExerciseClassName();
- ^
- symbol: class ExerciseClassName
- location: class ExerciseClassNameTest
-```
-This error occurs because the test refers to a class that hasn't been created yet (`ExerciseClassName`).
-To resolve the error you need to add a file matching the class name in the error to the `src/main/java` directory.
-For example, for the error above you would add a file called `ExerciseClassName.java`.
-
-When you try to run the tests again you will get slightly different errors.
-You might get an error similar to:
-```
- constructor ExerciseClassName in class ExerciseClassName cannot be applied to given types;
- ExerciseClassName exerciseClassName = new ExerciseClassName("some argument");
- ^
- required: no arguments
- found: String
- reason: actual and formal argument lists differ in length
-```
-This error means that you need to add a [constructor](https://docs.oracle.com/javase/tutorial/java/javaOO/constructors.html) to your new class.
-If you don't add a constructor, Java will add a default one for you.
-This default constructor takes no arguments.
-So if the tests expect your class to have a constructor which takes arguments, then you need to create this constructor yourself.
-In the example above you could add:
-```
-ExerciseClassName(String input) {
-
-}
-```
-That should make the error go away, though you might need to add some more code to your constructor to make the test pass!
-
-You might also get an error similar to:
-```
- error: cannot find symbol
- assertThat(exerciseClassName.someMethod()).isEqualTo(expectedOutput);
- ^
- symbol: method someMethod()
- location: variable exerciseClassName of type ExerciseClassName
-```
-This error means that you need to add a method called `someMethod` to your new class.
-In the example above you would add:
-```
-String someMethod() {
- return "";
-}
-```
-Make sure the return type matches what the test is expecting.
-You can find out which return type it should have by looking at the type of object it's being compared to in the tests.
-Or you could set your method to return some random type (e.g. `void`), and run the tests again.
-The new error should tell you which type it's expecting.
-
-After having resolved these errors you should be ready to start making the tests pass!
diff --git a/exercises/practice/allergies/.docs/instructions.md b/exercises/practice/allergies/.docs/instructions.md
index b8bbd5a3f..daf8cfde2 100644
--- a/exercises/practice/allergies/.docs/instructions.md
+++ b/exercises/practice/allergies/.docs/instructions.md
@@ -2,20 +2,18 @@
Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.
-An allergy test produces a single numeric score which contains the
-information about all the allergies the person has (that they were
-tested for).
+An allergy test produces a single numeric score which contains the information about all the allergies the person has (that they were tested for).
The list of items (and their value) that were tested are:
-* eggs (1)
-* peanuts (2)
-* shellfish (4)
-* strawberries (8)
-* tomatoes (16)
-* chocolate (32)
-* pollen (64)
-* cats (128)
+- eggs (1)
+- peanuts (2)
+- shellfish (4)
+- strawberries (8)
+- tomatoes (16)
+- chocolate (32)
+- pollen (64)
+- cats (128)
So if Tom is allergic to peanuts and chocolate, he gets a score of 34.
@@ -24,7 +22,6 @@ Now, given just that score of 34, your program should be able to say:
- Whether Tom is allergic to any one of those allergens listed above.
- All the allergens Tom is allergic to.
-Note: a given score may include allergens **not** listed above (i.e.
-allergens that score 256, 512, 1024, etc.). Your program should
-ignore those components of the score. For example, if the allergy
-score is 257, your program should only report the eggs (1) allergy.
+Note: a given score may include allergens **not** listed above (i.e. allergens that score 256, 512, 1024, etc.).
+Your program should ignore those components of the score.
+For example, if the allergy score is 257, your program should only report the eggs (1) allergy.
diff --git a/exercises/practice/allergies/.meta/config.json b/exercises/practice/allergies/.meta/config.json
index 04e78b6b1..5131edc5e 100644
--- a/exercises/practice/allergies/.meta/config.json
+++ b/exercises/practice/allergies/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.",
"authors": [],
"contributors": [
"c-thornton",
@@ -31,16 +30,20 @@
"solution": [
"src/main/java/Allergies.java"
],
- "editor": [
- "src/main/java/Allergen.java"
- ],
"test": [
"src/test/java/AllergiesTest.java"
],
"example": [
".meta/src/reference/java/Allergies.java"
+ ],
+ "editor": [
+ "src/main/java/Allergen.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
- "source": "Jumpstart Lab Warm-up",
- "source_url": "http://jumpstartlab.com"
+ "blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.",
+ "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.",
+ "source_url": "https://turing.edu"
}
diff --git a/exercises/practice/allergies/.meta/tests.toml b/exercises/practice/allergies/.meta/tests.toml
index 8a754c20d..799ab8563 100644
--- a/exercises/practice/allergies/.meta/tests.toml
+++ b/exercises/practice/allergies/.meta/tests.toml
@@ -1,150 +1,160 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
[17fc7296-2440-4ac4-ad7b-d07c321bc5a0]
-description = "not allergic to anything"
+description = "testing for eggs allergy -> not allergic to anything"
[07ced27b-1da5-4c2e-8ae2-cb2791437546]
-description = "allergic only to eggs"
+description = "testing for eggs allergy -> allergic only to eggs"
[5035b954-b6fa-4b9b-a487-dae69d8c5f96]
-description = "allergic to eggs and something else"
+description = "testing for eggs allergy -> allergic to eggs and something else"
[64a6a83a-5723-4b5b-a896-663307403310]
-description = "allergic to something, but not eggs"
+description = "testing for eggs allergy -> allergic to something, but not eggs"
[90c8f484-456b-41c4-82ba-2d08d93231c6]
-description = "allergic to everything"
+description = "testing for eggs allergy -> allergic to everything"
[d266a59a-fccc-413b-ac53-d57cb1f0db9d]
-description = "not allergic to anything"
+description = "testing for peanuts allergy -> not allergic to anything"
[ea210a98-860d-46b2-a5bf-50d8995b3f2a]
-description = "allergic only to peanuts"
+description = "testing for peanuts allergy -> allergic only to peanuts"
[eac69ae9-8d14-4291-ac4b-7fd2c73d3a5b]
-description = "allergic to peanuts and something else"
+description = "testing for peanuts allergy -> allergic to peanuts and something else"
[9152058c-ce39-4b16-9b1d-283ec6d25085]
-description = "allergic to something, but not peanuts"
+description = "testing for peanuts allergy -> allergic to something, but not peanuts"
[d2d71fd8-63d5-40f9-a627-fbdaf88caeab]
-description = "allergic to everything"
+description = "testing for peanuts allergy -> allergic to everything"
[b948b0a1-cbf7-4b28-a244-73ff56687c80]
-description = "not allergic to anything"
+description = "testing for shellfish allergy -> not allergic to anything"
[9ce9a6f3-53e9-4923-85e0-73019047c567]
-description = "allergic only to shellfish"
+description = "testing for shellfish allergy -> allergic only to shellfish"
[b272fca5-57ba-4b00-bd0c-43a737ab2131]
-description = "allergic to shellfish and something else"
+description = "testing for shellfish allergy -> allergic to shellfish and something else"
[21ef8e17-c227-494e-8e78-470a1c59c3d8]
-description = "allergic to something, but not shellfish"
+description = "testing for shellfish allergy -> allergic to something, but not shellfish"
[cc789c19-2b5e-4c67-b146-625dc8cfa34e]
-description = "allergic to everything"
+description = "testing for shellfish allergy -> allergic to everything"
[651bde0a-2a74-46c4-ab55-02a0906ca2f5]
-description = "not allergic to anything"
+description = "testing for strawberries allergy -> not allergic to anything"
[b649a750-9703-4f5f-b7f7-91da2c160ece]
-description = "allergic only to strawberries"
+description = "testing for strawberries allergy -> allergic only to strawberries"
[50f5f8f3-3bac-47e6-8dba-2d94470a4bc6]
-description = "allergic to strawberries and something else"
+description = "testing for strawberries allergy -> allergic to strawberries and something else"
[23dd6952-88c9-48d7-a7d5-5d0343deb18d]
-description = "allergic to something, but not strawberries"
+description = "testing for strawberries allergy -> allergic to something, but not strawberries"
[74afaae2-13b6-43a2-837a-286cd42e7d7e]
-description = "allergic to everything"
+description = "testing for strawberries allergy -> allergic to everything"
[c49a91ef-6252-415e-907e-a9d26ef61723]
-description = "not allergic to anything"
+description = "testing for tomatoes allergy -> not allergic to anything"
[b69c5131-b7d0-41ad-a32c-e1b2cc632df8]
-description = "allergic only to tomatoes"
+description = "testing for tomatoes allergy -> allergic only to tomatoes"
[1ca50eb1-f042-4ccf-9050-341521b929ec]
-description = "allergic to tomatoes and something else"
+description = "testing for tomatoes allergy -> allergic to tomatoes and something else"
[e9846baa-456b-4eff-8025-034b9f77bd8e]
-description = "allergic to something, but not tomatoes"
+description = "testing for tomatoes allergy -> allergic to something, but not tomatoes"
[b2414f01-f3ad-4965-8391-e65f54dad35f]
-description = "allergic to everything"
+description = "testing for tomatoes allergy -> allergic to everything"
[978467ab-bda4-49f7-b004-1d011ead947c]
-description = "not allergic to anything"
+description = "testing for chocolate allergy -> not allergic to anything"
[59cf4e49-06ea-4139-a2c1-d7aad28f8cbc]
-description = "allergic only to chocolate"
+description = "testing for chocolate allergy -> allergic only to chocolate"
[b0a7c07b-2db7-4f73-a180-565e07040ef1]
-description = "allergic to chocolate and something else"
+description = "testing for chocolate allergy -> allergic to chocolate and something else"
[f5506893-f1ae-482a-b516-7532ba5ca9d2]
-description = "allergic to something, but not chocolate"
+description = "testing for chocolate allergy -> allergic to something, but not chocolate"
[02debb3d-d7e2-4376-a26b-3c974b6595c6]
-description = "allergic to everything"
+description = "testing for chocolate allergy -> allergic to everything"
[17f4a42b-c91e-41b8-8a76-4797886c2d96]
-description = "not allergic to anything"
+description = "testing for pollen allergy -> not allergic to anything"
[7696eba7-1837-4488-882a-14b7b4e3e399]
-description = "allergic only to pollen"
+description = "testing for pollen allergy -> allergic only to pollen"
[9a49aec5-fa1f-405d-889e-4dfc420db2b6]
-description = "allergic to pollen and something else"
+description = "testing for pollen allergy -> allergic to pollen and something else"
[3cb8e79f-d108-4712-b620-aa146b1954a9]
-description = "allergic to something, but not pollen"
+description = "testing for pollen allergy -> allergic to something, but not pollen"
[1dc3fe57-7c68-4043-9d51-5457128744b2]
-description = "allergic to everything"
+description = "testing for pollen allergy -> allergic to everything"
[d3f523d6-3d50-419b-a222-d4dfd62ce314]
-description = "not allergic to anything"
+description = "testing for cats allergy -> not allergic to anything"
[eba541c3-c886-42d3-baef-c048cb7fcd8f]
-description = "allergic only to cats"
+description = "testing for cats allergy -> allergic only to cats"
[ba718376-26e0-40b7-bbbe-060287637ea5]
-description = "allergic to cats and something else"
+description = "testing for cats allergy -> allergic to cats and something else"
[3c6dbf4a-5277-436f-8b88-15a206f2d6c4]
-description = "allergic to something, but not cats"
+description = "testing for cats allergy -> allergic to something, but not cats"
[1faabb05-2b98-4995-9046-d83e4a48a7c1]
-description = "allergic to everything"
+description = "testing for cats allergy -> allergic to everything"
[f9c1b8e7-7dc5-4887-aa93-cebdcc29dd8f]
-description = "no allergies"
+description = "list when: -> no allergies"
[9e1a4364-09a6-4d94-990f-541a94a4c1e8]
-description = "just eggs"
+description = "list when: -> just eggs"
[8851c973-805e-4283-9e01-d0c0da0e4695]
-description = "just peanuts"
+description = "list when: -> just peanuts"
[2c8943cb-005e-435f-ae11-3e8fb558ea98]
-description = "just strawberries"
+description = "list when: -> just strawberries"
[6fa95d26-044c-48a9-8a7b-9ee46ec32c5c]
-description = "eggs and peanuts"
+description = "list when: -> eggs and peanuts"
[19890e22-f63f-4c5c-a9fb-fb6eacddfe8e]
-description = "more than eggs but not peanuts"
+description = "list when: -> more than eggs but not peanuts"
[4b68f470-067c-44e4-889f-c9fe28917d2f]
-description = "lots of stuff"
+description = "list when: -> lots of stuff"
[0881b7c5-9efa-4530-91bd-68370d054bc7]
-description = "everything"
+description = "list when: -> everything"
[12ce86de-b347-42a0-ab7c-2e0570f0c65b]
-description = "no allergen score parts"
+description = "list when: -> no allergen score parts"
+
+[93c2df3e-4f55-4fed-8116-7513092819cd]
+description = "list when: -> no allergen score parts without highest valid score"
diff --git a/exercises/practice/allergies/.meta/version b/exercises/practice/allergies/.meta/version
deleted file mode 100644
index 227cea215..000000000
--- a/exercises/practice/allergies/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-2.0.0
diff --git a/exercises/practice/allergies/build.gradle b/exercises/practice/allergies/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/allergies/build.gradle
+++ b/exercises/practice/allergies/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/allergies/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/allergies/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/allergies/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/allergies/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/allergies/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/allergies/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/allergies/gradlew b/exercises/practice/allergies/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/allergies/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/allergies/gradlew.bat b/exercises/practice/allergies/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/allergies/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/allergies/src/main/java/Allergies.java b/exercises/practice/allergies/src/main/java/Allergies.java
index 6178f1beb..3a82497f0 100644
--- a/exercises/practice/allergies/src/main/java/Allergies.java
+++ b/exercises/practice/allergies/src/main/java/Allergies.java
@@ -1,10 +1,15 @@
-/*
+import java.util.List;
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+class Allergies {
+ Allergies(int score) {
+ throw new UnsupportedOperationException("Please implement the Allergies constructor");
+ }
-Please remove this comment when submitting your solution.
+ boolean isAllergicTo(Allergen allergen) {
+ throw new UnsupportedOperationException("Please implement the isAllergicTo method");
+ }
-*/
+ List getList() {
+ throw new UnsupportedOperationException("Please implement the getList method");
+ }
+}
diff --git a/exercises/practice/allergies/src/test/java/AllergiesTest.java b/exercises/practice/allergies/src/test/java/AllergiesTest.java
index ff9d47535..cd0ae5cf7 100644
--- a/exercises/practice/allergies/src/test/java/AllergiesTest.java
+++ b/exercises/practice/allergies/src/test/java/AllergiesTest.java
@@ -1,5 +1,6 @@
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -9,38 +10,43 @@ public class AllergiesTest {
// Testing for eggs allergy
@Test
+ @DisplayName("not allergic to anything")
public void eggsNotAllergicToAnything() {
Allergies allergies = new Allergies(0);
assertThat(allergies.isAllergicTo(Allergen.EGGS)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic only to eggs")
public void eggsAllergicOnlyToEggs() {
Allergies allergies = new Allergies(1);
assertThat(allergies.isAllergicTo(Allergen.EGGS)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to eggs and something else")
public void eggsAllergicToEggsAndSomethingElse() {
Allergies allergies = new Allergies(3);
assertThat(allergies.isAllergicTo(Allergen.EGGS)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to something, but not eggs")
public void eggsAllergicToSomethingButNotEggs() {
Allergies allergies = new Allergies(2);
assertThat(allergies.isAllergicTo(Allergen.EGGS)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to everything")
public void eggsAllergicToEverything() {
Allergies allergies = new Allergies(255);
@@ -51,38 +57,43 @@ public void eggsAllergicToEverything() {
// Testing for peanuts allergy
@Test
+ @DisplayName("not allergic to anything")
public void peanutsNotAllergicToAnything() {
Allergies allergies = new Allergies(0);
assertThat(allergies.isAllergicTo(Allergen.PEANUTS)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic only to peanuts")
public void peanutsAllergicOnlyToPeanuts() {
Allergies allergies = new Allergies(2);
assertThat(allergies.isAllergicTo(Allergen.PEANUTS)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to peanuts and something else")
public void peanutsAllergicToPeanutsAndSomethingElse() {
Allergies allergies = new Allergies(7);
assertThat(allergies.isAllergicTo(Allergen.PEANUTS)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to something, but not peanuts")
public void peanutsAllergicToSomethingButNotPeanuts() {
Allergies allergies = new Allergies(5);
assertThat(allergies.isAllergicTo(Allergen.PEANUTS)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to everything")
public void peanutsAllergicToEverything() {
Allergies allergies = new Allergies(255);
@@ -93,38 +104,43 @@ public void peanutsAllergicToEverything() {
// Testing for shellfish allergy
@Test
+ @DisplayName("not allergic to anything")
public void shellfishNotAllergicToAnything() {
Allergies allergies = new Allergies(0);
assertThat(allergies.isAllergicTo(Allergen.SHELLFISH)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic only to shellfish")
public void shellfishAllergicOnlyToShellfish() {
Allergies allergies = new Allergies(4);
assertThat(allergies.isAllergicTo(Allergen.SHELLFISH)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to shellfish and something else")
public void shellfishAllergicToShellfishAndSomethingElse() {
Allergies allergies = new Allergies(14);
assertThat(allergies.isAllergicTo(Allergen.SHELLFISH)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to something, but not shellfish")
public void shellfishAllergicToSomethingButNotShellfish() {
Allergies allergies = new Allergies(10);
assertThat(allergies.isAllergicTo(Allergen.SHELLFISH)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to everything")
public void shellfishAllergicToEverything() {
Allergies allergies = new Allergies(255);
@@ -135,38 +151,43 @@ public void shellfishAllergicToEverything() {
// Testing for strawberries allergy
@Test
+ @DisplayName("not allergic to anything")
public void strawberriesNotAllergicToAnything() {
Allergies allergies = new Allergies(0);
assertThat(allergies.isAllergicTo(Allergen.STRAWBERRIES)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic only to strawberries")
public void strawberriesAllergicOnlyToStrawberries() {
Allergies allergies = new Allergies(8);
assertThat(allergies.isAllergicTo(Allergen.STRAWBERRIES)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to strawberries and something else")
public void strawberriesAllergicToStrawberriesAndSomethingElse() {
Allergies allergies = new Allergies(28);
assertThat(allergies.isAllergicTo(Allergen.STRAWBERRIES)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to something, but not strawberries")
public void strawberriesAllergicToSomethingButNotStrawberries() {
Allergies allergies = new Allergies(20);
assertThat(allergies.isAllergicTo(Allergen.STRAWBERRIES)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to everything")
public void strawberriesAllergicToEverything() {
Allergies allergies = new Allergies(255);
@@ -177,38 +198,43 @@ public void strawberriesAllergicToEverything() {
// Testing for tomatoes allergy
@Test
+ @DisplayName("not allergic to anything")
public void tomatoesNotAllergicToAnything() {
Allergies allergies = new Allergies(0);
assertThat(allergies.isAllergicTo(Allergen.TOMATOES)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic only to tomatoes")
public void tomatoesAllergicOnlyToTomatoes() {
Allergies allergies = new Allergies(16);
assertThat(allergies.isAllergicTo(Allergen.TOMATOES)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to tomatoes and something else")
public void tomatoesAllergicToTomatoesAndSomethingElse() {
Allergies allergies = new Allergies(56);
assertThat(allergies.isAllergicTo(Allergen.TOMATOES)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to something, but not tomatoes")
public void tomatoesAllergicToSomethingButNotTomatoes() {
Allergies allergies = new Allergies(40);
assertThat(allergies.isAllergicTo(Allergen.TOMATOES)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to everything")
public void tomatoesAllergicToEverything() {
Allergies allergies = new Allergies(255);
@@ -219,38 +245,43 @@ public void tomatoesAllergicToEverything() {
// Testing for chocolate allergy
@Test
+ @DisplayName("not allergic to anything")
public void chocolateNotAllergicToAnything() {
Allergies allergies = new Allergies(0);
assertThat(allergies.isAllergicTo(Allergen.CHOCOLATE)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic only to chocolate")
public void chocolateAllergicOnlyToChocolate() {
Allergies allergies = new Allergies(32);
assertThat(allergies.isAllergicTo(Allergen.CHOCOLATE)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to chocolate and something else")
public void chocolateAllergicToChocolateAndSomethingElse() {
Allergies allergies = new Allergies(112);
assertThat(allergies.isAllergicTo(Allergen.CHOCOLATE)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to something, but not chocolate")
public void chocolateAllergicToSomethingButNotChocolate() {
Allergies allergies = new Allergies(80);
assertThat(allergies.isAllergicTo(Allergen.CHOCOLATE)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to everything")
public void chocolateAllergicToEverything() {
Allergies allergies = new Allergies(255);
@@ -261,38 +292,43 @@ public void chocolateAllergicToEverything() {
// Testing for pollen allergy
@Test
+ @DisplayName("not allergic to anything")
public void pollenNotAllergicToAnything() {
Allergies allergies = new Allergies(0);
assertThat(allergies.isAllergicTo(Allergen.POLLEN)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic only to pollen")
public void pollenAllergicOnlyToPollen() {
Allergies allergies = new Allergies(64);
assertThat(allergies.isAllergicTo(Allergen.POLLEN)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to pollen and something else")
public void pollenAllergicToPollenAndSomethingElse() {
Allergies allergies = new Allergies(224);
assertThat(allergies.isAllergicTo(Allergen.POLLEN)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to something, but not pollen")
public void pollenAllergicToSomethingButNotPollen() {
Allergies allergies = new Allergies(160);
assertThat(allergies.isAllergicTo(Allergen.POLLEN)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to everything")
public void pollenAllergicToEverything() {
Allergies allergies = new Allergies(255);
@@ -303,38 +339,43 @@ public void pollenAllergicToEverything() {
// Testing for cats allergy
@Test
+ @DisplayName("not allergic to anything")
public void catsNotAllergicToAnything() {
Allergies allergies = new Allergies(0);
assertThat(allergies.isAllergicTo(Allergen.CATS)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic only to cats")
public void catsAllergicOnlyToCats() {
Allergies allergies = new Allergies(128);
assertThat(allergies.isAllergicTo(Allergen.CATS)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to cats and something else")
public void catsAllergicToCatsAndSomethingElse() {
Allergies allergies = new Allergies(192);
assertThat(allergies.isAllergicTo(Allergen.CATS)).isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to something, but not cats")
public void catsAllergicToSomethingButNotCats() {
Allergies allergies = new Allergies(64);
assertThat(allergies.isAllergicTo(Allergen.CATS)).isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("allergic to everything")
public void catsAllergicToEverything() {
Allergies allergies = new Allergies(255);
@@ -344,16 +385,18 @@ public void catsAllergicToEverything() {
// Testing listing allergies
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("no allergies")
public void listNoAllergies() {
Allergies allergies = new Allergies(0);
assertThat(allergies.getList().size()).isEqualTo(0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("just eggs")
public void listJustEggs() {
Allergies allergies = new Allergies(1);
@@ -361,8 +404,9 @@ public void listJustEggs() {
.containsExactly(Allergen.EGGS);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("just peanuts")
public void listJustPeanuts() {
Allergies allergies = new Allergies(2);
@@ -370,8 +414,9 @@ public void listJustPeanuts() {
.containsExactly(Allergen.PEANUTS);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("just strawberries")
public void listJustStrawberries() {
Allergies allergies = new Allergies(8);
@@ -379,8 +424,9 @@ public void listJustStrawberries() {
.containsExactly(Allergen.STRAWBERRIES);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("eggs and peanuts")
public void listEggsAndPeanuts() {
Allergies allergies = new Allergies(3);
@@ -390,8 +436,9 @@ public void listEggsAndPeanuts() {
Allergen.PEANUTS);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("more than eggs but not peanuts")
public void listoMoreThanEggsButNotPeanuts() {
Allergies allergies = new Allergies(5);
@@ -401,8 +448,9 @@ public void listoMoreThanEggsButNotPeanuts() {
Allergen.SHELLFISH);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("lots of stuff")
public void listManyAllergies() {
Allergies allergies = new Allergies(248);
@@ -415,8 +463,9 @@ public void listManyAllergies() {
Allergen.CATS);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("everything")
public void listEverything() {
Allergies allergies = new Allergies(255);
@@ -432,8 +481,9 @@ public void listEverything() {
Allergen.CATS);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("no allergen score parts")
public void listNoAllergenScoreParts() {
Allergies allergies = new Allergies(509);
@@ -447,4 +497,14 @@ public void listNoAllergenScoreParts() {
Allergen.POLLEN,
Allergen.CATS);
}
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("no allergen score parts without highest valid score")
+ public void listNoAllergenScorePartsWithoutHighestValidScore() {
+ Allergies allergies = new Allergies(257);
+
+ assertThat(allergies.getList())
+ .containsExactly(Allergen.EGGS);
+ }
}
diff --git a/exercises/practice/alphametics/.docs/instructions.md b/exercises/practice/alphametics/.docs/instructions.md
index 6936c192d..ef2cbb4a7 100644
--- a/exercises/practice/alphametics/.docs/instructions.md
+++ b/exercises/practice/alphametics/.docs/instructions.md
@@ -1,9 +1,8 @@
# Instructions
-Write a function to solve alphametics puzzles.
+Given an alphametics puzzle, find the correct solution.
-[Alphametics](https://en.wikipedia.org/wiki/Alphametics) is a puzzle where
-letters in words are replaced with numbers.
+[Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers.
For example `SEND + MORE = MONEY`:
@@ -23,10 +22,8 @@ Replacing these with valid numbers gives:
1 0 6 5 2
```
-This is correct because every letter is replaced by a different number and the
-words, translated into numbers, then make a valid sum.
+This is correct because every letter is replaced by a different number and the words, translated into numbers, then make a valid sum.
-Each letter must represent a different digit, and the leading digit of
-a multi-digit number must not be zero.
+Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero.
-Write a function to solve alphametics puzzles.
+[alphametics]: https://en.wikipedia.org/wiki/Alphametics
diff --git a/exercises/practice/alphametics/.meta/config.json b/exercises/practice/alphametics/.meta/config.json
index d4efea745..f2e3d0bfd 100644
--- a/exercises/practice/alphametics/.meta/config.json
+++ b/exercises/practice/alphametics/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Write a function to solve alphametics puzzles.",
"authors": [
"Zhiyuan-Amos"
],
@@ -20,14 +19,18 @@
"solution": [
"src/main/java/Alphametics.java"
],
- "editor": [
- "src/main/java/UnsolvablePuzzleException.java"
- ],
"test": [
"src/test/java/AlphameticsTest.java"
],
"example": [
".meta/src/reference/java/Alphametics.java"
+ ],
+ "editor": [
+ "src/main/java/UnsolvablePuzzleException.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
- }
+ },
+ "blurb": "Given an alphametics puzzle, find the correct solution."
}
diff --git a/exercises/practice/alphametics/.meta/version b/exercises/practice/alphametics/.meta/version
deleted file mode 100644
index f0bb29e76..000000000
--- a/exercises/practice/alphametics/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.3.0
diff --git a/exercises/practice/alphametics/build.gradle b/exercises/practice/alphametics/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/alphametics/build.gradle
+++ b/exercises/practice/alphametics/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/alphametics/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/alphametics/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/alphametics/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/alphametics/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/alphametics/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/alphametics/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/alphametics/gradlew b/exercises/practice/alphametics/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/alphametics/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/alphametics/gradlew.bat b/exercises/practice/alphametics/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/alphametics/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/alphametics/src/main/java/Alphametics.java b/exercises/practice/alphametics/src/main/java/Alphametics.java
index 6178f1beb..1ac637d9c 100644
--- a/exercises/practice/alphametics/src/main/java/Alphametics.java
+++ b/exercises/practice/alphametics/src/main/java/Alphametics.java
@@ -1,10 +1,13 @@
-/*
+import java.util.Map;
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+class Alphametics {
-Please remove this comment when submitting your solution.
+ Alphametics(String userInput) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-*/
+ Map solve() throws UnsolvablePuzzleException {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
+
+}
\ No newline at end of file
diff --git a/exercises/practice/alphametics/src/test/java/AlphameticsTest.java b/exercises/practice/alphametics/src/test/java/AlphameticsTest.java
index 216b41fd1..8661a8de3 100644
--- a/exercises/practice/alphametics/src/test/java/AlphameticsTest.java
+++ b/exercises/practice/alphametics/src/test/java/AlphameticsTest.java
@@ -1,5 +1,6 @@
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import static java.util.Map.entry;
import static org.assertj.core.api.Assertions.assertThat;
@@ -8,6 +9,7 @@
public class AlphameticsTest {
@Test
+ @DisplayName("puzzle with three letters")
public void testThreeLetters() throws UnsolvablePuzzleException {
assertThat(new Alphametics("I + BB == ILL").solve())
.containsOnly(
@@ -16,8 +18,9 @@ public void testThreeLetters() throws UnsolvablePuzzleException {
entry('L', 0));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("solution must have unique value for each letter")
public void testUniqueValue() {
Alphametics alphametics = new Alphametics("A == B");
@@ -25,8 +28,9 @@ public void testUniqueValue() {
.isThrownBy(alphametics::solve);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("leading zero solution is invalid")
public void testLeadingZero() {
Alphametics alphametics = new Alphametics("ACA + DD == BD");
@@ -34,8 +38,9 @@ public void testLeadingZero() {
.isThrownBy(alphametics::solve);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("puzzle with two digits final carry")
public void testTwoDigitsFinalCarry() throws UnsolvablePuzzleException {
assertThat(new Alphametics("A + A + A + A + A + A + A + A + A + A + A + B == BCC").solve())
.containsOnly(
@@ -44,8 +49,9 @@ public void testTwoDigitsFinalCarry() throws UnsolvablePuzzleException {
entry('C', 0));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("puzzle with four letters")
public void testFourLetters() throws UnsolvablePuzzleException {
assertThat(new Alphametics("AS + A == MOM").solve())
.containsOnly(
@@ -55,8 +61,9 @@ public void testFourLetters() throws UnsolvablePuzzleException {
entry('O', 0));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("puzzle with six letters")
public void testSixLetters() throws UnsolvablePuzzleException {
assertThat(new Alphametics("NO + NO + TOO == LATE").solve())
.containsOnly(
@@ -68,8 +75,9 @@ public void testSixLetters() throws UnsolvablePuzzleException {
entry('E', 2));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("puzzle with seven letters")
public void testSevenLetters() throws UnsolvablePuzzleException {
assertThat(new Alphametics("HE + SEES + THE == LIGHT").solve())
.containsOnly(
@@ -82,8 +90,9 @@ public void testSevenLetters() throws UnsolvablePuzzleException {
entry('T', 7));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("puzzle with eight letters")
public void testEightLetters() throws UnsolvablePuzzleException {
assertThat(new Alphametics("SEND + MORE == MONEY").solve())
.containsOnly(
@@ -97,8 +106,9 @@ public void testEightLetters() throws UnsolvablePuzzleException {
entry('Y', 2));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("puzzle with ten letters")
public void testTenLetters() throws UnsolvablePuzzleException {
assertThat(new Alphametics("AND + A + STRONG + OFFENSE + AS + A + GOOD == DEFENSE").solve())
.containsOnly(
@@ -114,9 +124,10 @@ public void testTenLetters() throws UnsolvablePuzzleException {
entry('T', 9));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void testTenLetters41Addends() throws UnsolvablePuzzleException {
+ @DisplayName("puzzle with ten letters and 199 addends")
+ public void testTenLetters199Addends() throws UnsolvablePuzzleException {
assertThat(new Alphametics("THIS + A + FIRE + THEREFORE + FOR + ALL + HISTORIES + I + TELL + A + " +
"TALE + THAT + FALSIFIES + ITS + TITLE + TIS + A + LIE + THE + TALE + OF + THE + LAST + FIRE + " +
"HORSES + LATE + AFTER + THE + FIRST + FATHERS + FORESEE + THE + HORRORS + THE + LAST + FREE + " +
diff --git a/exercises/practice/anagram/.docs/instructions.append.md b/exercises/practice/anagram/.docs/instructions.append.md
new file mode 100644
index 000000000..8d71a920b
--- /dev/null
+++ b/exercises/practice/anagram/.docs/instructions.append.md
@@ -0,0 +1,3 @@
+# Instructions Append
+
+The anagrams can be returned in any order.
diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md
index 2675b5836..dca24f526 100644
--- a/exercises/practice/anagram/.docs/instructions.md
+++ b/exercises/practice/anagram/.docs/instructions.md
@@ -1,8 +1,12 @@
# Instructions
-An anagram is a rearrangement of letters to form a new word.
-Given a word and a list of candidates, select the sublist of anagrams of the given word.
+Given a target word and one or more candidate words, your task is to find the candidates that are anagrams of the target.
-Given `"listen"` and a list of candidates like `"enlists" "google"
-"inlets" "banana"` the program should return a list containing
-`"inlets"`.
+An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`.
+A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`.
+
+The target word and candidate words are made up of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`).
+Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `"StoP"` is not an anagram of `"sTOp"`.
+The words you need to find should be taken from the candidate words, using the same letter case.
+
+Given the target `"stone"` and the candidate words `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, and `"Seton"`, the anagram words you need to find are `"tones"`, `"notes"`, and `"Seton"`.
diff --git a/exercises/practice/anagram/.docs/introduction.md b/exercises/practice/anagram/.docs/introduction.md
new file mode 100644
index 000000000..1acbdf00b
--- /dev/null
+++ b/exercises/practice/anagram/.docs/introduction.md
@@ -0,0 +1,12 @@
+# Introduction
+
+At a garage sale, you find a lovely vintage typewriter at a bargain price!
+Excitedly, you rush home, insert a sheet of paper, and start typing away.
+However, your excitement wanes when you examine the output: all words are garbled!
+For example, it prints "stop" instead of "post" and "least" instead of "stale."
+Carefully, you try again, but now it prints "spot" and "slate."
+After some experimentation, you find there is a random delay before each letter is printed, which messes up the order.
+You now understand why they sold it for so little money!
+
+You realize this quirk allows you to generate anagrams, which are words formed by rearranging the letters of another word.
+Pleased with your finding, you spend the rest of the day generating hundreds of anagrams.
diff --git a/exercises/practice/anagram/.meta/config.json b/exercises/practice/anagram/.meta/config.json
index f519bc3c3..1fae320a1 100644
--- a/exercises/practice/anagram/.meta/config.json
+++ b/exercises/practice/anagram/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Given a word and a list of possible anagrams, select the correct sublist.",
"authors": [
"wdjunaidi"
],
@@ -18,6 +17,7 @@
"muzimuzhi",
"redshirt4",
"rohit1104",
+ "sanderploegsma",
"sjwarner-bp",
"SleeplessByte",
"Smarticles101",
@@ -36,8 +36,12 @@
],
"example": [
".meta/src/reference/java/Anagram.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
+ "blurb": "Given a word and a list of possible anagrams, select the correct sublist.",
"source": "Inspired by the Extreme Startup game",
"source_url": "https://github.com/rchatley/extreme_startup"
}
diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml
index 7fb93f71b..4d9056270 100644
--- a/exercises/practice/anagram/.meta/tests.toml
+++ b/exercises/practice/anagram/.meta/tests.toml
@@ -1,12 +1,24 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
[dd40c4d2-3c8b-44e5-992a-f42b393ec373]
description = "no matches"
[b3cca662-f50a-489e-ae10-ab8290a09bdc]
description = "detects two anagrams"
+include = false
+
+[03eb9bbe-8906-4ea0-84fa-ffe711b52c8b]
+description = "detects two anagrams"
+reimplements = "b3cca662-f50a-489e-ae10-ab8290a09bdc"
[a27558ee-9ba0-4552-96b1-ecf665b06556]
description = "does not detect anagram subsets"
@@ -34,12 +46,41 @@ description = "detects anagrams using case-insensitive possible matches"
[7cc195ad-e3c7-44ee-9fd2-d3c344806a2c]
description = "does not detect an anagram if the original word is repeated"
+include = false
+
+[630abb71-a94e-4715-8395-179ec1df9f91]
+description = "does not detect an anagram if the original word is repeated"
+reimplements = "7cc195ad-e3c7-44ee-9fd2-d3c344806a2c"
[9878a1c9-d6ea-4235-ae51-3ea2befd6842]
description = "anagrams must use all letters exactly once"
[85757361-4535-45fd-ac0e-3810d40debc1]
description = "words are not anagrams of themselves (case-insensitive)"
+include = false
+
+[68934ed0-010b-4ef9-857a-20c9012d1ebf]
+description = "words are not anagrams of themselves"
+reimplements = "85757361-4535-45fd-ac0e-3810d40debc1"
+
+[589384f3-4c8a-4e7d-9edc-51c3e5f0c90e]
+description = "words are not anagrams of themselves even if letter case is partially different"
+reimplements = "85757361-4535-45fd-ac0e-3810d40debc1"
+
+[ba53e423-7e02-41ee-9ae2-71f91e6d18e6]
+description = "words are not anagrams of themselves even if letter case is completely different"
+reimplements = "85757361-4535-45fd-ac0e-3810d40debc1"
[a0705568-628c-4b55-9798-82e4acde51ca]
description = "words other than themselves can be anagrams"
+include = false
+
+[33d3f67e-fbb9-49d3-a90e-0beb00861da7]
+description = "words other than themselves can be anagrams"
+reimplements = "a0705568-628c-4b55-9798-82e4acde51ca"
+
+[a6854f66-eec1-4afd-a137-62ef2870c051]
+description = "handles case of greek letters"
+
+[fd3509e5-e3ba-409d-ac3d-a9ac84d13296]
+description = "different characters may have the same bytes"
diff --git a/exercises/practice/anagram/.meta/version b/exercises/practice/anagram/.meta/version
deleted file mode 100644
index bc80560fa..000000000
--- a/exercises/practice/anagram/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.5.0
diff --git a/exercises/practice/anagram/build.gradle b/exercises/practice/anagram/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/anagram/build.gradle
+++ b/exercises/practice/anagram/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/anagram/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/anagram/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/anagram/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/anagram/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/anagram/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/anagram/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/anagram/gradlew b/exercises/practice/anagram/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/anagram/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/anagram/gradlew.bat b/exercises/practice/anagram/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/anagram/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/anagram/src/main/java/Anagram.java b/exercises/practice/anagram/src/main/java/Anagram.java
index 6178f1beb..b5037342c 100644
--- a/exercises/practice/anagram/src/main/java/Anagram.java
+++ b/exercises/practice/anagram/src/main/java/Anagram.java
@@ -1,10 +1,12 @@
-/*
+import java.util.List;
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+class Anagram {
+ public Anagram(String word) {
+ throw new UnsupportedOperationException("Please implement the Anagram(String word) constructor");
+ }
-Please remove this comment when submitting your solution.
+ public List match(List candidates) {
+ throw new UnsupportedOperationException("Please implement the match() method");
+ }
-*/
+}
\ No newline at end of file
diff --git a/exercises/practice/anagram/src/test/java/AnagramTest.java b/exercises/practice/anagram/src/test/java/AnagramTest.java
index 8d47c4553..79a3a261b 100644
--- a/exercises/practice/anagram/src/test/java/AnagramTest.java
+++ b/exercises/practice/anagram/src/test/java/AnagramTest.java
@@ -1,152 +1,206 @@
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Collections;
+import static org.assertj.core.api.Assertions.assertThat;
+
public class AnagramTest {
@Test
+ @DisplayName("no matches")
public void testNoMatches() {
Anagram detector = new Anagram("diaper");
assertThat(
- detector.match(
- Arrays.asList("hello", "world", "zombies", "pants")))
- .isEmpty();
+ detector.match(
+ Arrays.asList("hello", "world", "zombies", "pants")))
+ .isEmpty();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void testDetectMultipleAnagrams() {
- Anagram detector = new Anagram("master");
+ @DisplayName("detects two anagrams")
+ public void testDetectsTwoAnagrams() {
+ Anagram detector = new Anagram("solemn");
- assertThat(detector.match(Arrays.asList("stream", "pigeon", "maters")))
- .containsExactlyInAnyOrder("maters", "stream");
+ assertThat(detector.match(Arrays.asList("lemons", "cherry", "melons")))
+ .containsExactlyInAnyOrder("lemons", "melons");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("does not detect anagram subsets")
public void testEliminateAnagramSubsets() {
Anagram detector = new Anagram("good");
assertThat(detector.match(Arrays.asList("dog", "goody"))).isEmpty();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("detects anagram")
public void testDetectLongerAnagram() {
Anagram detector = new Anagram("listen");
assertThat(
- detector.match(
- Arrays.asList("enlists", "google", "inlets", "banana")))
- .containsExactlyInAnyOrder("inlets");
+ detector.match(
+ Arrays.asList("enlists", "google", "inlets", "banana")))
+ .containsExactlyInAnyOrder("inlets");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("detects three anagrams")
public void testDetectMultipleAnagramsForLongerWord() {
Anagram detector = new Anagram("allergy");
assertThat(
- detector.match(
- Arrays.asList(
- "gallery",
- "ballerina",
- "regally",
- "clergy",
- "largely",
- "leading")))
- .containsExactlyInAnyOrder("gallery", "regally", "largely");
+ detector.match(
+ Arrays.asList(
+ "gallery",
+ "ballerina",
+ "regally",
+ "clergy",
+ "largely",
+ "leading")))
+ .containsExactlyInAnyOrder("gallery", "regally", "largely");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("detects multiple anagrams with different case")
public void testDetectsMultipleAnagramsWithDifferentCase() {
Anagram detector = new Anagram("nose");
assertThat(detector.match(Arrays.asList("Eons", "ONES")))
- .containsExactlyInAnyOrder("Eons", "ONES");
+ .containsExactlyInAnyOrder("Eons", "ONES");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("does not detect non-anagrams with identical checksum")
public void testEliminateAnagramsWithSameChecksum() {
Anagram detector = new Anagram("mass");
assertThat(detector.match(Collections.singletonList("last")))
- .isEmpty();
+ .isEmpty();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("detects anagrams case-insensitively")
public void testCaseInsensitiveWhenBothAnagramAndSubjectStartWithUpperCaseLetter() {
Anagram detector = new Anagram("Orchestra");
assertThat(
- detector.match(
- Arrays.asList("cashregister", "Carthorse", "radishes")))
- .containsExactlyInAnyOrder("Carthorse");
+ detector.match(
+ Arrays.asList("cashregister", "Carthorse", "radishes")))
+ .containsExactlyInAnyOrder("Carthorse");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("detects anagrams using case-insensitive subject")
public void testCaseInsensitiveWhenSubjectStartsWithUpperCaseLetter() {
Anagram detector = new Anagram("Orchestra");
assertThat(
- detector.match(
- Arrays.asList("cashregister", "carthorse", "radishes")))
- .containsExactlyInAnyOrder("carthorse");
+ detector.match(
+ Arrays.asList("cashregister", "carthorse", "radishes")))
+ .containsExactlyInAnyOrder("carthorse");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("detects anagrams using case-insensitive possible matches")
public void testCaseInsensitiveWhenAnagramStartsWithUpperCaseLetter() {
Anagram detector = new Anagram("orchestra");
assertThat(
- detector.match(
- Arrays.asList("cashregister", "Carthorse", "radishes")))
- .containsExactlyInAnyOrder("Carthorse");
+ detector.match(
+ Arrays.asList("cashregister", "Carthorse", "radishes")))
+ .containsExactlyInAnyOrder("Carthorse");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("does not detect an anagram if the original word is repeated")
public void testIdenticalWordRepeatedIsNotAnagram() {
Anagram detector = new Anagram("go");
- assertThat(detector.match(Collections.singletonList("go Go GO")))
- .isEmpty();
+ assertThat(detector.match(Collections.singletonList("goGoGO")))
+ .isEmpty();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("anagrams must use all letters exactly once")
public void testAnagramMustUseAllLettersExactlyOnce() {
Anagram detector = new Anagram("tapper");
assertThat(detector.match(Collections.singletonList("patter")))
- .isEmpty();
+ .isEmpty();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("words are not anagrams of themselves")
public void testWordsAreNotAnagramsOfThemselvesCaseInsensitive() {
Anagram detector = new Anagram("BANANA");
- assertThat(detector.match(Arrays.asList("BANANA", "Banana", "banana")))
- .isEmpty();
+ assertThat(detector.match(Collections.singletonList("BANANA")))
+ .isEmpty();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("words are not anagrams of themselves even if letter case is partially different")
+ public void testWordsAreNotAnagramsOfThemselvesEvenIfLetterCaseIsPartiallyDifferent() {
+ Anagram detector = new Anagram("BANANA");
+
+ assertThat(detector.match(Collections.singletonList("Banana")))
+ .isEmpty();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("words are not anagrams of themselves even if letter case is completely different")
+ public void testWordsAreNotAnagramsOfThemselvesEvenIfLetterCaseIsCompletelyDifferent() {
+ Anagram detector = new Anagram("BANANA");
+
+ assertThat(detector.match(Collections.singletonList("banana")))
+ .isEmpty();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("words other than themselves can be anagrams")
public void testWordsOtherThanThemselvesCanBeAnagrams() {
Anagram detector = new Anagram("LISTEN");
- assertThat(detector.match(Arrays.asList("Listen", "Silent", "LISTEN")))
- .containsExactlyInAnyOrder("Silent");
+ assertThat(detector.match(Arrays.asList("LISTEN", "Silent")))
+ .containsExactlyInAnyOrder("Silent");
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("handles case of greek letters")
+ public void testHandlesCaseOfGreekLetters() {
+ Anagram detector = new Anagram("ΑΒΓ");
+
+ assertThat(detector.match(Arrays.asList("ΒΓΑ", "ΒΓΔ", "γβα", "αβγ")))
+ .containsExactlyInAnyOrder("ΒΓΑ", "γβα");
}
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("different characters may have the same bytes")
+ public void testDifferentCharactersWithSameBytes() {
+ Anagram detector = new Anagram("a⬂");
+
+ assertThat(detector.match(Collections.singletonList("€a")))
+ .isEmpty();
+ }
}
diff --git a/exercises/practice/armstrong-numbers/.docs/instructions.md b/exercises/practice/armstrong-numbers/.docs/instructions.md
index 452a996fb..5e56bbe46 100644
--- a/exercises/practice/armstrong-numbers/.docs/instructions.md
+++ b/exercises/practice/armstrong-numbers/.docs/instructions.md
@@ -1,12 +1,14 @@
# Instructions
-An [Armstrong number](https://en.wikipedia.org/wiki/Narcissistic_number) is a number that is the sum of its own digits each raised to the power of the number of digits.
+An [Armstrong number][armstrong-number] is a number that is the sum of its own digits each raised to the power of the number of digits.
For example:
- 9 is an Armstrong number, because `9 = 9^1 = 9`
-- 10 is *not* an Armstrong number, because `10 != 1^2 + 0^2 = 1`
+- 10 is _not_ an Armstrong number, because `10 != 1^2 + 0^2 = 1`
- 153 is an Armstrong number, because: `153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153`
-- 154 is *not* an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190`
+- 154 is _not_ an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190`
Write some code to determine whether a number is an Armstrong number.
+
+[armstrong-number]: https://en.wikipedia.org/wiki/Narcissistic_number
diff --git a/exercises/practice/armstrong-numbers/.meta/config.json b/exercises/practice/armstrong-numbers/.meta/config.json
index 686d1ee03..3ec7af8d6 100644
--- a/exercises/practice/armstrong-numbers/.meta/config.json
+++ b/exercises/practice/armstrong-numbers/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Determine if a number is an Armstrong number.",
"authors": [
"sjwarner-bp"
],
@@ -23,8 +22,12 @@
],
"example": [
".meta/src/reference/java/ArmstrongNumbers.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
+ "blurb": "Determine if a number is an Armstrong number.",
"source": "Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Narcissistic_number"
}
diff --git a/exercises/practice/armstrong-numbers/.meta/tests.toml b/exercises/practice/armstrong-numbers/.meta/tests.toml
index fdada6d1e..6fdedacb6 100644
--- a/exercises/practice/armstrong-numbers/.meta/tests.toml
+++ b/exercises/practice/armstrong-numbers/.meta/tests.toml
@@ -1,30 +1,47 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
[c1ed103c-258d-45b2-be73-d8c6d9580c7b]
description = "Zero is an Armstrong number"
[579e8f03-9659-4b85-a1a2-d64350f6b17a]
-description = "Single digit numbers are Armstrong numbers"
+description = "Single-digit numbers are Armstrong numbers"
[2d6db9dc-5bf8-4976-a90b-b2c2b9feba60]
-description = "There are no 2 digit Armstrong numbers"
+description = "There are no two-digit Armstrong numbers"
[509c087f-e327-4113-a7d2-26a4e9d18283]
-description = "Three digit number that is an Armstrong number"
+description = "Three-digit number that is an Armstrong number"
[7154547d-c2ce-468d-b214-4cb953b870cf]
-description = "Three digit number that is not an Armstrong number"
+description = "Three-digit number that is not an Armstrong number"
[6bac5b7b-42e9-4ecb-a8b0-4832229aa103]
-description = "Four digit number that is an Armstrong number"
+description = "Four-digit number that is an Armstrong number"
[eed4b331-af80-45b5-a80b-19c9ea444b2e]
-description = "Four digit number that is not an Armstrong number"
+description = "Four-digit number that is not an Armstrong number"
[f971ced7-8d68-4758-aea1-d4194900b864]
-description = "Seven digit number that is an Armstrong number"
+description = "Seven-digit number that is an Armstrong number"
[7ee45d52-5d35-4fbd-b6f1-5c8cd8a67f18]
-description = "Seven digit number that is not an Armstrong number"
+description = "Seven-digit number that is not an Armstrong number"
+
+[5ee2fdf8-334e-4a46-bb8d-e5c19c02c148]
+description = "Armstrong number containing seven zeroes"
+include = false
+comment = "Excluded because this exercise does not support numbers larger than 32 bits."
+
+[12ffbf10-307a-434e-b4ad-c925680e1dd4]
+description = "The largest and last Armstrong number"
+include = false
+comment = "Excluded because this exercise does not support numbers larger than 32 bits."
diff --git a/exercises/practice/armstrong-numbers/.meta/version b/exercises/practice/armstrong-numbers/.meta/version
deleted file mode 100644
index 9084fa2f7..000000000
--- a/exercises/practice/armstrong-numbers/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.1.0
diff --git a/exercises/practice/armstrong-numbers/build.gradle b/exercises/practice/armstrong-numbers/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/armstrong-numbers/build.gradle
+++ b/exercises/practice/armstrong-numbers/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/armstrong-numbers/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/armstrong-numbers/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/armstrong-numbers/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/armstrong-numbers/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/armstrong-numbers/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/armstrong-numbers/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/armstrong-numbers/gradlew b/exercises/practice/armstrong-numbers/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/armstrong-numbers/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/armstrong-numbers/gradlew.bat b/exercises/practice/armstrong-numbers/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/armstrong-numbers/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/armstrong-numbers/src/test/java/ArmstrongNumbersTest.java b/exercises/practice/armstrong-numbers/src/test/java/ArmstrongNumbersTest.java
index 9a4b69f0c..8977970dc 100644
--- a/exercises/practice/armstrong-numbers/src/test/java/ArmstrongNumbersTest.java
+++ b/exercises/practice/armstrong-numbers/src/test/java/ArmstrongNumbersTest.java
@@ -1,6 +1,7 @@
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -8,68 +9,77 @@ public class ArmstrongNumbersTest {
private ArmstrongNumbers armstrongNumbers;
- @Before
+ @BeforeEach
public void setup() {
armstrongNumbers = new ArmstrongNumbers();
}
@Test
+ @DisplayName("Zero is an Armstrong number")
public void zeroIsArmstrongNumber() {
assertThat(armstrongNumbers.isArmstrongNumber(0))
.isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Single-digit numbers are Armstrong numbers")
public void singleDigitsAreArmstrongNumbers() {
assertThat(armstrongNumbers.isArmstrongNumber(5))
.isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("There are no two-digit Armstrong numbers")
public void noTwoDigitArmstrongNumbers() {
assertThat(armstrongNumbers.isArmstrongNumber(10))
.isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Three-digit number that is an Armstrong number")
public void threeDigitNumberIsArmstrongNumber() {
assertThat(armstrongNumbers.isArmstrongNumber(153))
.isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Three-digit number that is not an Armstrong number")
public void threeDigitNumberIsNotArmstrongNumber() {
assertThat(armstrongNumbers.isArmstrongNumber(100))
.isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Four-digit number that is an Armstrong number")
public void fourDigitNumberIsArmstrongNumber() {
assertThat(armstrongNumbers.isArmstrongNumber(9474))
.isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Four-digit number that is not an Armstrong number")
public void fourDigitNumberIsNotArmstrongNumber() {
assertThat(armstrongNumbers.isArmstrongNumber(9475))
.isFalse();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Seven-digit number that is an Armstrong number")
public void sevenDigitNumberIsArmstrongNumber() {
assertThat(armstrongNumbers.isArmstrongNumber(9926315))
.isTrue();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Seven-digit number that is not an Armstrong number")
public void sevenDigitNumberIsNotArmstrongNumber() {
assertThat(armstrongNumbers.isArmstrongNumber(9926314))
.isFalse();
diff --git a/exercises/practice/atbash-cipher/.docs/instructions.append.md b/exercises/practice/atbash-cipher/.docs/instructions.append.md
deleted file mode 100644
index 1dac9bb20..000000000
--- a/exercises/practice/atbash-cipher/.docs/instructions.append.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Instructions append
-
-Since this exercise has difficulty 5 it doesn't come with any starter implementation.
-This is so that you get to practice creating classes and methods which is an important part of programming in Java.
-It does mean that when you first try to run the tests, they won't compile.
-They will give you an error similar to:
-```
- path-to-exercism-dir\exercism\java\name-of-exercise\src\test\java\ExerciseClassNameTest.java:14: error: cannot find symbol
- ExerciseClassName exerciseClassName = new ExerciseClassName();
- ^
- symbol: class ExerciseClassName
- location: class ExerciseClassNameTest
-```
-This error occurs because the test refers to a class that hasn't been created yet (`ExerciseClassName`).
-To resolve the error you need to add a file matching the class name in the error to the `src/main/java` directory.
-For example, for the error above you would add a file called `ExerciseClassName.java`.
-
-When you try to run the tests again you will get slightly different errors.
-You might get an error similar to:
-```
- constructor ExerciseClassName in class ExerciseClassName cannot be applied to given types;
- ExerciseClassName exerciseClassName = new ExerciseClassName("some argument");
- ^
- required: no arguments
- found: String
- reason: actual and formal argument lists differ in length
-```
-This error means that you need to add a [constructor](https://docs.oracle.com/javase/tutorial/java/javaOO/constructors.html) to your new class.
-If you don't add a constructor, Java will add a default one for you.
-This default constructor takes no arguments.
-So if the tests expect your class to have a constructor which takes arguments, then you need to create this constructor yourself.
-In the example above you could add:
-```
-ExerciseClassName(String input) {
-
-}
-```
-That should make the error go away, though you might need to add some more code to your constructor to make the test pass!
-
-You might also get an error similar to:
-```
- error: cannot find symbol
- assertThat(exerciseClassName.someMethod()).isEqualTo(expectedOutput);
- ^
- symbol: method someMethod()
- location: variable exerciseClassName of type ExerciseClassName
-```
-This error means that you need to add a method called `someMethod` to your new class.
-In the example above you would add:
-```
-String someMethod() {
- return "";
-}
-```
-Make sure the return type matches what the test is expecting.
-You can find out which return type it should have by looking at the type of object it's being compared to in the tests.
-Or you could set your method to return some random type (e.g. `void`), and run the tests again.
-The new error should tell you which type it's expecting.
-
-After having resolved these errors you should be ready to start making the tests pass!
diff --git a/exercises/practice/atbash-cipher/.docs/instructions.md b/exercises/practice/atbash-cipher/.docs/instructions.md
index 2f712b159..1e7627b1e 100644
--- a/exercises/practice/atbash-cipher/.docs/instructions.md
+++ b/exercises/practice/atbash-cipher/.docs/instructions.md
@@ -1,11 +1,9 @@
# Instructions
-Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.
+Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.
-The Atbash cipher is a simple substitution cipher that relies on
-transposing all the letters in the alphabet such that the resulting
-alphabet is backwards. The first letter is replaced with the last
-letter, the second with the second-last, and so on.
+The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards.
+The first letter is replaced with the last letter, the second with the second-last, and so on.
An Atbash cipher for the Latin alphabet would be as follows:
@@ -14,16 +12,16 @@ Plain: abcdefghijklmnopqrstuvwxyz
Cipher: zyxwvutsrqponmlkjihgfedcba
```
-It is a very weak cipher because it only has one possible key, and it is
-a simple monoalphabetic substitution cipher. However, this may not have
-been an issue in the cipher's time.
+It is a very weak cipher because it only has one possible key, and it is a simple mono-alphabetic substitution cipher.
+However, this may not have been an issue in the cipher's time.
-Ciphertext is written out in groups of fixed length, the traditional group size
-being 5 letters, and punctuation is excluded. This is to make it harder to guess
-things based on word boundaries.
+Ciphertext is written out in groups of fixed length, the traditional group size being 5 letters, leaving numbers unchanged, and punctuation is excluded.
+This is to make it harder to guess things based on word boundaries.
+All text will be encoded as lowercase letters.
## Examples
- Encoding `test` gives `gvhg`
+- Encoding `x123 yes` gives `c123b vh`
- Decoding `gvhg` gives `test`
- Decoding `gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt` gives `thequickbrownfoxjumpsoverthelazydog`
diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json
index d201af822..41761efcd 100644
--- a/exercises/practice/atbash-cipher/.meta/config.json
+++ b/exercises/practice/atbash-cipher/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.",
"authors": [],
"contributors": [
"c-thornton",
@@ -35,8 +34,12 @@
],
"example": [
".meta/src/reference/java/Atbash.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
+ "blurb": "Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.",
"source": "Wikipedia",
- "source_url": "http://en.wikipedia.org/wiki/Atbash"
+ "source_url": "https://en.wikipedia.org/wiki/Atbash"
}
diff --git a/exercises/practice/atbash-cipher/.meta/version b/exercises/practice/atbash-cipher/.meta/version
deleted file mode 100644
index 26aaba0e8..000000000
--- a/exercises/practice/atbash-cipher/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.2.0
diff --git a/exercises/practice/atbash-cipher/build.gradle b/exercises/practice/atbash-cipher/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/atbash-cipher/build.gradle
+++ b/exercises/practice/atbash-cipher/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/atbash-cipher/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/atbash-cipher/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/atbash-cipher/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/atbash-cipher/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/atbash-cipher/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/atbash-cipher/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/atbash-cipher/gradlew b/exercises/practice/atbash-cipher/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/atbash-cipher/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/atbash-cipher/gradlew.bat b/exercises/practice/atbash-cipher/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/atbash-cipher/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/atbash-cipher/src/main/java/Atbash.java b/exercises/practice/atbash-cipher/src/main/java/Atbash.java
index 6178f1beb..e5ea2147a 100644
--- a/exercises/practice/atbash-cipher/src/main/java/Atbash.java
+++ b/exercises/practice/atbash-cipher/src/main/java/Atbash.java
@@ -1,10 +1,11 @@
-/*
+class Atbash {
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+ String encode(String input) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-Please remove this comment when submitting your solution.
+ String decode(String input) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-*/
+}
diff --git a/exercises/practice/atbash-cipher/src/test/java/AtbashTest.java b/exercises/practice/atbash-cipher/src/test/java/AtbashTest.java
index ef818bc30..c914f8b28 100644
--- a/exercises/practice/atbash-cipher/src/test/java/AtbashTest.java
+++ b/exercises/practice/atbash-cipher/src/test/java/AtbashTest.java
@@ -1,6 +1,7 @@
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -8,96 +9,110 @@ public class AtbashTest {
private Atbash atbash;
- @Before
+ @BeforeEach
public void setup() {
atbash = new Atbash();
}
@Test
+ @DisplayName("encode yes")
public void testEncodeYes() {
assertThat(atbash.encode("yes")).isEqualTo("bvh");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode no")
public void testEncodeNo() {
assertThat(atbash.encode("no")).isEqualTo("ml");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode OMG")
public void testEncodeOmgInCapital() {
assertThat(atbash.encode("OMG")).isEqualTo("lnt");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode spaces")
public void testEncodeOmgWithSpaces() {
assertThat(atbash.encode("O M G")).isEqualTo("lnt");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode mindblowingly")
public void testEncodeMindBlowingly() {
assertThat(atbash.encode("mindblowingly")).isEqualTo("nrmwy oldrm tob");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode numbers")
public void testEncodeNumbers() {
assertThat(atbash.encode("Testing,1 2 3, testing."))
.isEqualTo("gvhgr mt123 gvhgr mt");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode deep thought")
public void testEncodeDeepThought() {
assertThat(atbash.encode("Truth is fiction."))
.isEqualTo("gifgs rhurx grlm");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("encode all the letters")
public void testEncodeAllTheLetters() {
assertThat(atbash.encode("The quick brown fox jumps over the lazy dog."))
.isEqualTo("gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode exercism")
public void testDecodeExercism() {
assertThat(atbash.decode("vcvix rhn")).isEqualTo("exercism");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode a sentence")
public void testDecodeASentence() {
assertThat(atbash.decode("zmlyh gzxov rhlug vmzhg vkkrm thglm v"))
.isEqualTo("anobstacleisoftenasteppingstone");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode numbers")
public void testDecodeNumbers() {
assertThat(atbash.decode("gvhgr mt123 gvhgr mt"))
.isEqualTo("testing123testing");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode all the letters")
public void testDecodeAllTheLetters() {
assertThat(atbash.decode("gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt"))
.isEqualTo("thequickbrownfoxjumpsoverthelazydog");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode with too many spaces")
public void testDecodeWithTooManySpaces() {
assertThat(atbash.decode("vc vix r hn")).isEqualTo("exercism");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("decode with no spaces")
public void testDecodeWithNoSpaces() {
assertThat(atbash.decode("zmlyhgzxovrhlugvmzhgvkkrmthglmv"))
.isEqualTo("anobstacleisoftenasteppingstone");
diff --git a/exercises/practice/baffling-birthdays/.docs/instructions.md b/exercises/practice/baffling-birthdays/.docs/instructions.md
new file mode 100644
index 000000000..a01ec8679
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/.docs/instructions.md
@@ -0,0 +1,23 @@
+# Instructions
+
+Your task is to estimate the birthday paradox's probabilities.
+
+To do this, you need to:
+
+- Generate random birthdates.
+- Check if a collection of randomly generated birthdates contains at least two with the same birthday.
+- Estimate the probability that at least two people in a group share the same birthday for different group sizes.
+
+~~~~exercism/note
+A birthdate includes the full date of birth (year, month, and day), whereas a birthday refers only to the month and day, which repeat each year.
+Two birthdates with the same month and day correspond to the same birthday.
+~~~~
+
+~~~~exercism/caution
+The birthday paradox assumes that:
+
+- There are 365 possible birthdays (no leap years).
+- Each birthday is equally likely (uniform distribution).
+
+Your implementation must follow these assumptions.
+~~~~
diff --git a/exercises/practice/baffling-birthdays/.docs/introduction.md b/exercises/practice/baffling-birthdays/.docs/introduction.md
new file mode 100644
index 000000000..97dabd1e6
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/.docs/introduction.md
@@ -0,0 +1,25 @@
+# Introduction
+
+Fresh out of college, you're throwing a huge party to celebrate with friends and family.
+Over 70 people have shown up, including your mildly eccentric Uncle Ted.
+
+In one of his usual antics, he bets you £100 that at least two people in the room share the same birthday.
+That sounds ridiculous — there are many more possible birthdays than there are guests, so you confidently accept.
+
+To your astonishment, after collecting the birthdays of just 32 guests, you've already found two guests that share the same birthday.
+Accepting your loss, you hand Uncle Ted his £100, but something feels off.
+
+The next day, curiosity gets the better of you.
+A quick web search leads you to the [birthday paradox][birthday-problem], which reveals that with just 23 people, the probability of a shared birthday exceeds 50%.
+
+Ah. So _that's_ why Uncle Ted was so confident.
+
+Determined to turn the tables, you start looking up other paradoxes; next time, _you'll_ be the one making the bets.
+
+~~~~exercism/note
+The birthday paradox is a [veridical paradox][veridical-paradox]: even though it feels wrong, it is actually true.
+
+[veridical-paradox]: https://en.wikipedia.org/wiki/Paradox#Quine's_classification
+~~~~
+
+[birthday-problem]: https://en.wikipedia.org/wiki/Birthday_problem
diff --git a/exercises/practice/baffling-birthdays/.meta/config.json b/exercises/practice/baffling-birthdays/.meta/config.json
new file mode 100644
index 000000000..6e401cb62
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/.meta/config.json
@@ -0,0 +1,22 @@
+{
+ "authors": [
+ "Baboushka"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/BafflingBirthdays.java"
+ ],
+ "test": [
+ "src/test/java/BafflingBirthdaysTest.java"
+ ],
+ "example": [
+ ".meta/src/reference/java/BafflingBirthdays.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "blurb": "Estimate the birthday paradox's probabilities.",
+ "source": "Erik Schierboom",
+ "source_url": "https://github.com/exercism/problem-specifications/pull/2539"
+}
diff --git a/exercises/practice/baffling-birthdays/.meta/src/reference/java/BafflingBirthdays.java b/exercises/practice/baffling-birthdays/.meta/src/reference/java/BafflingBirthdays.java
new file mode 100644
index 000000000..ef58bc2c0
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/.meta/src/reference/java/BafflingBirthdays.java
@@ -0,0 +1,48 @@
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class BafflingBirthdays {
+ private static final int nonLeapYear = 2001;
+ private static final int daysInYear = 365;
+
+ boolean sharedBirthday(List birthdates) {
+ Set seen = new HashSet<>();
+ for (LocalDate birthdate : birthdates) {
+ if (!seen.add(birthdate.getMonth().toString() + birthdate.getDayOfMonth())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ List randomBirthdates(int groupSize) {
+ if (groupSize <= 0) {
+ return List.of();
+ }
+ List birthdates = new ArrayList<>(groupSize);
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+ for (int i = 0; i < groupSize; i++) {
+ int dayOfYear = random.nextInt(1, daysInYear + 1);
+ birthdates.add(LocalDate.ofYearDay(nonLeapYear, dayOfYear));
+ }
+ return birthdates;
+ }
+
+ double estimatedProbabilityOfSharedBirthday(int groupSize) {
+ if (groupSize <= 1) {
+ return 0.0;
+ }
+ if (groupSize > daysInYear) {
+ return 100.0;
+ }
+ double probabilityNoSharedBirthday = 1.0;
+ for (int k = 0; k < groupSize; k++) {
+ probabilityNoSharedBirthday *= (daysInYear - k) / (double) daysInYear;
+ }
+ return (1 - probabilityNoSharedBirthday) * 100.0;
+ }
+}
diff --git a/exercises/practice/baffling-birthdays/.meta/tests.toml b/exercises/practice/baffling-birthdays/.meta/tests.toml
new file mode 100644
index 000000000..c76afb466
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/.meta/tests.toml
@@ -0,0 +1,61 @@
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[716dcc2b-8fe4-4fc9-8c48-cbe70d8e6b67]
+description = "shared birthday -> one birthdate"
+
+[f7b3eb26-bcfc-4c1e-a2de-af07afc33f45]
+description = "shared birthday -> two birthdates with same year, month, and day"
+
+[7193409a-6e16-4bcb-b4cc-9ffe55f79b25]
+description = "shared birthday -> two birthdates with same year and month, but different day"
+
+[d04db648-121b-4b72-93e8-d7d2dced4495]
+description = "shared birthday -> two birthdates with same month and day, but different year"
+
+[3c8bd0f0-14c6-4d4c-975a-4c636bfdc233]
+description = "shared birthday -> two birthdates with same year, but different month and day"
+
+[df5daba6-0879-4480-883c-e855c99cdaa3]
+description = "shared birthday -> two birthdates with different year, month, and day"
+
+[0c17b220-cbb9-4bd7-872f-373044c7b406]
+description = "shared birthday -> multiple birthdates without shared birthday"
+
+[966d6b0b-5c0a-4b8c-bc2d-64939ada49f8]
+description = "shared birthday -> multiple birthdates with one shared birthday"
+
+[b7937d28-403b-4500-acce-4d9fe3a9620d]
+description = "shared birthday -> multiple birthdates with more than one shared birthday"
+
+[70b38cea-d234-4697-b146-7d130cd4ee12]
+description = "random birthdates -> generate requested number of birthdates"
+
+[d9d5b7d3-5fea-4752-b9c1-3fcd176d1b03]
+description = "random birthdates -> years are not leap years"
+
+[d1074327-f68c-4c8a-b0ff-e3730d0f0521]
+description = "random birthdates -> months are random"
+
+[7df706b3-c3f5-471d-9563-23a4d0577940]
+description = "random birthdates -> days are random"
+
+[89a462a4-4265-4912-9506-fb027913f221]
+description = "estimated probability of at least one shared birthday -> for one person"
+
+[ec31c787-0ebb-4548-970c-5dcb4eadfb5f]
+description = "estimated probability of at least one shared birthday -> among ten people"
+
+[b548afac-a451-46a3-9bb0-cb1f60c48e2f]
+description = "estimated probability of at least one shared birthday -> among twenty-three people"
+
+[e43e6b9d-d77b-4f6c-a960-0fc0129a0bc5]
+description = "estimated probability of at least one shared birthday -> among seventy people"
diff --git a/exercises/practice/baffling-birthdays/build.gradle b/exercises/practice/baffling-birthdays/build.gradle
new file mode 100644
index 000000000..d28f35dee
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/practice/baffling-birthdays/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/baffling-birthdays/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/baffling-birthdays/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/baffling-birthdays/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/baffling-birthdays/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/baffling-birthdays/gradlew b/exercises/practice/baffling-birthdays/gradlew
new file mode 100644
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/baffling-birthdays/gradlew.bat b/exercises/practice/baffling-birthdays/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/baffling-birthdays/src/main/java/BafflingBirthdays.java b/exercises/practice/baffling-birthdays/src/main/java/BafflingBirthdays.java
new file mode 100644
index 000000000..5e1925081
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/src/main/java/BafflingBirthdays.java
@@ -0,0 +1,16 @@
+import java.time.LocalDate;
+import java.util.List;
+
+class BafflingBirthdays {
+ boolean sharedBirthday(List birthdates) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
+
+ List randomBirthdates(int groupSize) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
+
+ double estimatedProbabilityOfSharedBirthday(int groupSize) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
+}
\ No newline at end of file
diff --git a/exercises/practice/baffling-birthdays/src/test/java/BafflingBirthdaysTest.java b/exercises/practice/baffling-birthdays/src/test/java/BafflingBirthdaysTest.java
new file mode 100644
index 000000000..0726301dc
--- /dev/null
+++ b/exercises/practice/baffling-birthdays/src/test/java/BafflingBirthdaysTest.java
@@ -0,0 +1,176 @@
+import java.time.LocalDate;
+import java.util.List;
+
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static java.time.Month.APRIL;
+import static java.time.Month.AUGUST;
+import static java.time.Month.DECEMBER;
+import static java.time.Month.FEBRUARY;
+import static java.time.Month.JANUARY;
+import static java.time.Month.JULY;
+import static java.time.Month.MAY;
+import static java.time.Month.NOVEMBER;
+import static java.time.Month.OCTOBER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.Offset.offset;
+
+public class BafflingBirthdaysTest {
+ private BafflingBirthdays birthdays = new BafflingBirthdays();
+
+ @Test
+ @DisplayName("one birthdate")
+ public void oneBirthdateTest() {
+ assertThat(birthdays.sharedBirthday(List.of(
+ LocalDate.of(2000, JANUARY, 1)
+ ))).isFalse();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("two birthdates with same year, month, and day")
+ public void twoBirthdatesWithSameYearMonthAndDayTest() {
+ assertThat(birthdays.sharedBirthday(List.of(
+ LocalDate.of(2000, JANUARY, 1),
+ LocalDate.of(2000, JANUARY, 1)
+ ))).isTrue();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("two birthdates with same year and month, but different day")
+ public void twoBirthdatesWithSameYearAndMonthButDifferentDayTest() {
+ assertThat(birthdays.sharedBirthday(List.of(
+ LocalDate.of(2012, MAY, 9),
+ LocalDate.of(2012, MAY, 17)
+ ))).isFalse();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("two birthdates with same month and day, but different year")
+ public void twoBirthdatesWithSameMonthAndDayButDifferentYearTest() {
+ assertThat(birthdays.sharedBirthday(List.of(
+ LocalDate.of(1999, OCTOBER, 23),
+ LocalDate.of(1988, OCTOBER, 23)
+ ))).isTrue();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("two birthdates with same year, but different month and day")
+ public void twoBirthdatesWithSameYearButDifferentMonthAndDayTest() {
+ assertThat(birthdays.sharedBirthday(List.of(
+ LocalDate.of(2007, DECEMBER, 19),
+ LocalDate.of(2007, APRIL, 27)
+ ))).isFalse();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("two birthdates with different year, month, and day")
+ public void twoBirthdatesWithDifferentYearMonthAndDayTest() {
+ assertThat(birthdays.sharedBirthday(List.of(
+ LocalDate.of(1997, AUGUST, 4),
+ LocalDate.of(1963, NOVEMBER, 23)
+ ))).isFalse();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("multiple birthdates without shared birthday")
+ public void multipleBirthdatesWithoutSharedBirthdayTest() {
+ assertThat(birthdays.sharedBirthday(List.of(
+ LocalDate.of(1966, AUGUST, 29),
+ LocalDate.of(1977, FEBRUARY, 12),
+ LocalDate.of(2001, DECEMBER, 25),
+ LocalDate.of(1980, NOVEMBER, 10)
+ ))).isFalse();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("multiple birthdates with one shared birthday")
+ public void multipleBirthdatesWithOneSharedBirthdayTest() {
+ assertThat(birthdays.sharedBirthday(List.of(
+ LocalDate.of(1966, AUGUST, 29),
+ LocalDate.of(1977, FEBRUARY, 12),
+ LocalDate.of(2001, AUGUST, 29),
+ LocalDate.of(1980, NOVEMBER, 10)
+ ))).isTrue();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("multiple birthdates with more than one shared birthday")
+ public void multipleBirthdatesWithMoreThanOneSharedBirthdayTest() {
+ assertThat(birthdays.sharedBirthday(List.of(
+ LocalDate.of(1966, JULY, 29),
+ LocalDate.of(1977, FEBRUARY, 12),
+ LocalDate.of(2001, DECEMBER, 25),
+ LocalDate.of(1980, JULY, 29),
+ LocalDate.of(2019, FEBRUARY, 12)
+ ))).isTrue();
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("generate requested number of birthdates")
+ public void generateRequestedNumberOfBirthdatesTest() {
+ int groupSize = 50;
+ assertThat(birthdays.randomBirthdates(groupSize).size()).isEqualTo(groupSize);
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("years are not leap years")
+ public void yearsAreNotLeapYearsTest() {
+ assertThat(birthdays.randomBirthdates(100)).noneMatch(LocalDate::isLeapYear);
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("months are random")
+ public void monthsAreRandomTest() {
+ assertThat(birthdays.randomBirthdates(500).stream().map(LocalDate::getMonth).distinct())
+ .hasSizeGreaterThanOrEqualTo(7);
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("days are random")
+ public void daysAreRandomTest() {
+ assertThat(birthdays.randomBirthdates(500).stream().map(LocalDate::getDayOfMonth).distinct())
+ .hasSizeGreaterThanOrEqualTo(11);
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("estimated probability of at least one shared birthday case for one person")
+ public void estimatedProbabilityOfAtLeastOneSharedBirthdayForOnePersonTest() {
+ assertThat(birthdays.estimatedProbabilityOfSharedBirthday(1)).isEqualTo(0.0);
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("estimated probability of at least one shared birthday case among ten people")
+ public void estimatedProbabilityOfAtLeastOneSharedBirthdayAmongTenPeopleTest() {
+ assertThat(birthdays.estimatedProbabilityOfSharedBirthday(10)).isCloseTo(11.694818, offset(1.0));
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("estimated probability of at least one shared birthday case among twenty-three people")
+ public void estimatedProbabilityOfAtLeastOneSharedBirthdayAmongTwentyThreePeopleTest() {
+ assertThat(birthdays.estimatedProbabilityOfSharedBirthday(23)).isCloseTo(50.729723, offset(1.0));
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("estimated probability of at least one shared birthday case among seventy people")
+ public void estimatedProbabilityOfAtLeastOneSharedBirthdayAmongSeventyPeopleTest() {
+ assertThat(birthdays.estimatedProbabilityOfSharedBirthday(70)).isCloseTo(99.915958, offset(1.0));
+ }
+}
diff --git a/exercises/practice/bank-account/.approaches/config.json b/exercises/practice/bank-account/.approaches/config.json
new file mode 100644
index 000000000..1739dcd4a
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/config.json
@@ -0,0 +1,45 @@
+{
+ "introduction": {
+ "authors": [
+ "kahgoh"
+ ]
+ },
+ "approaches": [
+ {
+ "uuid": "78d753b0-aa58-43dc-83c0-9ea41496de84",
+ "slug": "synchronized-methods",
+ "title": "Synchronized methods",
+ "blurb": "Use synchronized methods to prevent methods from running simultaneously in different threads",
+ "authors": [
+ "kahgoh"
+ ]
+ },
+ {
+ "uuid": "5f3b0152-02eb-40d5-9104-5edc30b4447e",
+ "slug": "synchronized-statements",
+ "title": "Synchronized statements",
+ "blurb": "Use an object to prevent threads from running blocks of code at the same time",
+ "authors": [
+ "kahgoh"
+ ]
+ },
+ {
+ "uuid": "0acd6f2b-27d0-4ae6-9c22-22b0b2047039",
+ "slug": "reentrant-lock",
+ "title": "Reentrant lock",
+ "blurb": "Use an ReentrantLock to explicitly acquire and release locks",
+ "authors": [
+ "kahgoh"
+ ]
+ },
+ {
+ "uuid": "4ad42f88-f750-4af9-bbbd-d8b2dc5e8078",
+ "slug": "readwrite-lock",
+ "title": "Readwrite lock",
+ "blurb": "Use separate read and write locks to achieve greater concurrency",
+ "authors": [
+ "kahgoh"
+ ]
+ }
+ ]
+}
diff --git a/exercises/practice/bank-account/.approaches/introduction.md b/exercises/practice/bank-account/.approaches/introduction.md
new file mode 100644
index 000000000..ac8d3c506
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/introduction.md
@@ -0,0 +1,351 @@
+# Introduction
+
+In Bank Account, you are tasked with implementing a number of operations that can be performed on a bank account.
+However, these operations may be performed by multiple threads at the same time.
+
+## General guidance
+
+The key to solving Bank Account is to prevent an account from being updated from multiple threads at the same time.
+For example, consider an account that begins with $0.
+A $10 deposit is made twice.
+Each transaction starts a thread.
+If the threads happen to start simultaneously, they might both see the account starting $0.
+They each add $10 to this amount and update the account accordingly.
+In this case, each thread sets the amount to $10 even though the transaction was made twice.
+
+The problem here is that both threads saw that there was $0 in the account prior to adding the deposit.
+Instead, each thread needs to take turns to process the deposit for the account so that they can process the transaction one at a time.
+This way, the later thread can "see" the deposited amount from the first thread.
+
+## Approach: Synchronized methods
+
+```java
+class BankAccount {
+ private int balance = 0;
+ private boolean isClosed = true;
+
+ synchronized void open() throws BankAccountActionInvalidException {
+ if (!isClosed) {
+ throw new BankAccountActionInvalidException("Account already open");
+ }
+ isClosed = false;
+ balance = 0;
+ }
+
+ synchronized void close() throws BankAccountActionInvalidException {
+ if (isClosed) {
+ throw new BankAccountActionInvalidException("Account not open");
+ }
+ isClosed = true;
+ }
+
+ synchronized int getBalance() throws BankAccountActionInvalidException {
+ checkIfClosed();
+ return balance;
+ }
+
+ synchronized void deposit(int amount) throws BankAccountActionInvalidException {
+ checkIfClosed();
+ checkIfValidAmount(amount);
+
+ balance += amount;
+ }
+
+ synchronized void withdraw(int amount) throws BankAccountActionInvalidException {
+ checkIfClosed();
+ checkIfValidAmount(amount);
+ checkIfEnoughMoneyInAccount(amount);
+
+ balance -= amount;
+ }
+
+ private void checkIfValidAmount(int amount) throws BankAccountActionInvalidException {
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ }
+
+ private void checkIfEnoughMoneyInAccount(int amount) throws BankAccountActionInvalidException {
+ if (balance == 0) {
+ throw new BankAccountActionInvalidException("Cannot withdraw money from an empty account");
+ }
+ if (balance - amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account");
+ }
+ }
+
+ private void checkIfClosed() throws BankAccountActionInvalidException {
+ if (isClosed) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ }
+}
+```
+
+For more information, check the [Synchronized methods approach][approach-synchronized-methods].
+
+## Approach: Synchronized statements
+
+```java
+class BankAccount {
+ private final Object lock = new Object();
+ private int balance = 0;
+ private boolean isClosed = true;
+
+ void open() throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ if (!isClosed) {
+ throw new BankAccountActionInvalidException("Account already open");
+ }
+ isClosed = false;
+ balance = 0;
+ }
+ }
+
+ void close() throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ if (isClosed) {
+ throw new BankAccountActionInvalidException("Account not open");
+ }
+ isClosed = true;
+ }
+ }
+
+ int getBalance() throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ checkIfClosed();
+ return balance;
+ }
+ }
+
+ void deposit(int amount) throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ checkIfClosed();
+ checkIfValidAmount(amount);
+
+ balance += amount;
+ }
+ }
+
+ void withdraw(int amount) throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ checkIfClosed();
+ checkIfValidAmount(amount);
+ checkIfEnoughMoneyInAccount(amount);
+
+ balance -= amount;
+ }
+ }
+
+ private void checkIfValidAmount(int amount) throws BankAccountActionInvalidException {
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ }
+
+ private void checkIfEnoughMoneyInAccount(int amount) throws BankAccountActionInvalidException {
+ if (balance == 0) {
+ throw new BankAccountActionInvalidException("Cannot withdraw money from an empty account");
+ }
+ if (balance - amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account");
+ }
+ }
+
+ private void checkIfClosed() throws BankAccountActionInvalidException {
+ if (isClosed) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ }
+}
+```
+
+For more information, check the [Synchronized statements approach][approach-synchronized-statements].
+
+## Approach: Reentrant lock
+
+```java
+import java.util.concurrent.locks.ReentrantLock;
+
+class BankAccount {
+
+ private final ReentrantLock lock = new ReentrantLock();
+
+ private boolean isOpen = false;
+ private int balance = 0;
+
+ void open() throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (isOpen) {
+ throw new BankAccountActionInvalidException("Account already open");
+ }
+ isOpen = true;
+ balance = 0;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ void close() throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account not open");
+ }
+ isOpen = false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ int getBalance() throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ return balance;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ void deposit(int amount) throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ balance += amount;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ void withdraw(int amount) throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ if (amount > balance) {
+ throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account");
+ }
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ balance -= amount;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+}
+```
+
+For more information, check the [Reentrant lock approach][approach-reentrant-lock].
+
+## Approach: Read write lock
+
+```java
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+class BankAccount {
+
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+ private boolean isOpen = false;
+
+ private int balance = 0;
+
+ void open() throws BankAccountActionInvalidException {
+ lock.writeLock().lock();
+ try {
+ if (isOpen) {
+ throw new BankAccountActionInvalidException("Account already open");
+ }
+ isOpen = true;
+ balance = 0;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ void close() throws BankAccountActionInvalidException {
+ lock.writeLock().lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account not open");
+ }
+ isOpen = false;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ int getBalance() throws BankAccountActionInvalidException {
+ lock.readLock().lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ return balance;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ void deposit(int amount) throws BankAccountActionInvalidException {
+ lock.writeLock().lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ balance += amount;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ void withdraw(int amount) throws BankAccountActionInvalidException {
+ lock.writeLock().lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ if (amount > balance) {
+ throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account");
+ }
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ balance -= amount;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+}
+```
+
+For more information, check the [Read write lock approach][approach-read-write-lock].
+
+## Which approach to use?
+
+- The synchronized methods is the simplest, requiring no extra objects to be created.
+- Synchronized statements provide greater control over which code statements are performed with a lock and which object is to be used as the lock.
+- The read write lock allows greater concurrency by letting multiple read operations, such as `getBalance`, run in parallel.
+ However, it requires the lock to be explicitly released.
+
+[approach-read-write-lock]: https://exercism.org/tracks/java/exercises/bank-account/approaches/readwrite-lock
+[approach-reentrant-lock]: https://exercism.org/tracks/java/exercises/bank-acconuunt/approaches/reentrant-lock
+[approach-synchronized-methods]: https://exercism.org/tracks/java/exercises/bank-account/approaches/synchronized-methods
+[approach-synchronized-statements]: https://exercism.org/tracks/java/exercises/bank-account/approaches/synchronzied-statements
diff --git a/exercises/practice/bank-account/.approaches/readwrite-lock/content.md b/exercises/practice/bank-account/.approaches/readwrite-lock/content.md
new file mode 100644
index 000000000..5db33cc0a
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/readwrite-lock/content.md
@@ -0,0 +1,108 @@
+# Readwrite Lock
+
+```java
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+class BankAccount {
+
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+ private boolean isOpen = false;
+
+ private int balance = 0;
+
+ void open() throws BankAccountActionInvalidException {
+ lock.writeLock().lock();
+ try {
+ if (isOpen) {
+ throw new BankAccountActionInvalidException("Account already open");
+ }
+ isOpen = true;
+ balance = 0;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ void close() throws BankAccountActionInvalidException {
+ lock.writeLock().lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account not open");
+ }
+ isOpen = false;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ int getBalance() throws BankAccountActionInvalidException {
+ lock.readLock().lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ return balance;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ void deposit(int amount) throws BankAccountActionInvalidException {
+ lock.writeLock().lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ balance += amount;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ void withdraw(int amount) throws BankAccountActionInvalidException {
+ lock.writeLock().lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ if (amount > balance) {
+ throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account");
+ }
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ balance -= amount;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+}
+```
+
+A [ReadWriteLock][docs-readwritelock] provides two types of locks - one for reading, the other for writing.
+[ReentrantReadWriteLock][docs-reentrantreadwritelock] is an implementation of a [ReadWriteLock][docs-readwritelock].
+
+Read locks are intended for read-only type operations, such as `getBalance`, and are acquired by calling `readLock().lock()` on the [ReadWriteLock][docs-readwritelock].
+Multiple threads are allowed to acquire read locks at the same time because they are expected to only read data.
+This means multiple threads can run `getBalance` at the same time.
+
+Write locks are for write operations, such as `withdraw` and `deposit`.
+It is also used in `open` and `close` as they change the state of the `BankAccount` (they "write" to the `isOpen` field).
+Write locks are acquired by calling `writeLock().lock()` on the [ReadWriteLock][docs-readwritelock].
+
+Only one thread can hold a write lock at a time.
+Therefore, `withdraw`, `deposit`, `open` and `close` can not run at the same time.
+Additionally, a thread must _also_ wait for _all_ read locks to be released to obtain a write lock.
+Similarly, threads must wait for write locks to be released before they are granted a read lock.
+This means `getBalance` also can not run at the same time as any of the `withdraw`, `deposit`, `open` and `close` methods.
+
+The locks are released in the `finally` block to ensure they are released, even when an exception is thrown.
+
+[docs-readwritelock]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/locks/ReadWriteLock.html
+[docs-reentrantreadwritelock]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/locks/ReentrantReadWriteLock.html
diff --git a/exercises/practice/bank-account/.approaches/readwrite-lock/snippet.txt b/exercises/practice/bank-account/.approaches/readwrite-lock/snippet.txt
new file mode 100644
index 000000000..45fcdf98d
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/readwrite-lock/snippet.txt
@@ -0,0 +1,7 @@
+private final ReadWriteLock lock = new ReentrantReadWriteLock();
+lock.readLock().lock();
+try {
+ balance += amount;
+} finally {
+ lock.readLock().unlock();
+}
\ No newline at end of file
diff --git a/exercises/practice/bank-account/.approaches/reentrant-lock/content.md b/exercises/practice/bank-account/.approaches/reentrant-lock/content.md
new file mode 100644
index 000000000..efa9f9512
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/reentrant-lock/content.md
@@ -0,0 +1,96 @@
+# Reentrant Lock
+
+```java
+import java.util.concurrent.locks.ReentrantLock;
+
+class BankAccount {
+
+ private final ReentrantLock lock = new ReentrantLock();
+
+ private boolean isOpen = false;
+ private int balance = 0;
+
+ void open() throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (isOpen) {
+ throw new BankAccountActionInvalidException("Account already open");
+ }
+ isOpen = true;
+ balance = 0;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ void close() throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account not open");
+ }
+ isOpen = false;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ int getBalance() throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ return balance;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ void deposit(int amount) throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ balance += amount;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ void withdraw(int amount) throws BankAccountActionInvalidException {
+ lock.lock();
+ try {
+ if (!isOpen) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ if (amount > balance) {
+ throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account");
+ }
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ balance -= amount;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+}
+```
+
+A [ReentrantLock][docs-reentrantlock] object represents a lock that threads must acquire to perform certain operations.
+It is used here by the operation methods to ensure they are not trying to update the bank account at the same time.
+
+The lock is requested by calling [lock][docs-reentrantlock-lock].
+The lock is released at the end of the operation by calling [unlock][docs-reentrantlock-unlock] in a `finally` block.
+This is important to ensure that the lock is released when it is no longer needed, especially if an exception is thrown.
+The re-entrant nature of the lock means a thread will be granted a lock again if it already has the lock.
+
+[docs-reentrantlock]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html
+[docs-reentrantlock-lock]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html#lock()
+[docs-reentrantlock-unlock]: https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/concurrent/locks/ReentrantLock.html#unlock()
diff --git a/exercises/practice/bank-account/.approaches/reentrant-lock/snippet.txt b/exercises/practice/bank-account/.approaches/reentrant-lock/snippet.txt
new file mode 100644
index 000000000..2014610f8
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/reentrant-lock/snippet.txt
@@ -0,0 +1,7 @@
+private final ReentrantLock lock = new ReentrantLock();
+lock.lock();
+try {
+ balance += amount;
+} finally {
+ lock.unlock();
+}
\ No newline at end of file
diff --git a/exercises/practice/bank-account/.approaches/synchronized-methods/content.md b/exercises/practice/bank-account/.approaches/synchronized-methods/content.md
new file mode 100644
index 000000000..7c1730694
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/synchronized-methods/content.md
@@ -0,0 +1,77 @@
+# Synchronized methods
+
+```java
+class BankAccount {
+ private int balance = 0;
+ private boolean isClosed = true;
+
+ synchronized void open() throws BankAccountActionInvalidException {
+ if (!isClosed) {
+ throw new BankAccountActionInvalidException("Account already open");
+ }
+ isClosed = false;
+ balance = 0;
+ }
+
+ synchronized void close() throws BankAccountActionInvalidException {
+ if (isClosed) {
+ throw new BankAccountActionInvalidException("Account not open");
+ }
+ isClosed = true;
+ }
+
+ synchronized int getBalance() throws BankAccountActionInvalidException {
+ checkIfClosed();
+ return balance;
+ }
+
+ synchronized void deposit(int amount) throws BankAccountActionInvalidException {
+ checkIfClosed();
+ checkIfValidAmount(amount);
+
+ balance += amount;
+ }
+
+ synchronized void withdraw(int amount) throws BankAccountActionInvalidException {
+ checkIfClosed();
+ checkIfValidAmount(amount);
+ checkIfEnoughMoneyInAccount(amount);
+
+ balance -= amount;
+ }
+
+ private void checkIfValidAmount(int amount) throws BankAccountActionInvalidException {
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ }
+
+ private void checkIfEnoughMoneyInAccount(int amount) throws BankAccountActionInvalidException {
+ if (balance == 0) {
+ throw new BankAccountActionInvalidException("Cannot withdraw money from an empty account");
+ }
+ if (balance - amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account");
+ }
+ }
+
+ private void checkIfClosed() throws BankAccountActionInvalidException {
+ if (isClosed) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ }
+}
+```
+
+Each operation method is marked `synchronized`.
+This tells the thread to acquire a lock on the `BankAccount` object _before_ executing the method.
+If any other thread holds a lock on the `BankAccount` object, it must wait for the other thread to release the lock.
+
+~~~~exercism/note
+In Java, the is one other way to acquire a lock on the `BankAccount` object - [synchronized statements][approach-synchronized-statements].
+Since synchronized methods use a lock on the `BankAccount` object, it will also have to wait for locks on the `BankAccount` that are used by [synchronized statements][approach-synchronized-statements] to be reused.
+
+[approach-synchronized-statements]: https://exercism.org/tracks/java/exercises/bank-account/approaches/synchronzied-statements
+~~~~
+
+The lock is automatically released when the method finishes.
diff --git a/exercises/practice/bank-account/.approaches/synchronized-methods/snippet.txt b/exercises/practice/bank-account/.approaches/synchronized-methods/snippet.txt
new file mode 100644
index 000000000..61ca7d4ec
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/synchronized-methods/snippet.txt
@@ -0,0 +1,3 @@
+synchronized void deposit(int amount) throws BankAccountActionInvalidException {
+ balance += amount;
+}
\ No newline at end of file
diff --git a/exercises/practice/bank-account/.approaches/synchronized-statements/content.md b/exercises/practice/bank-account/.approaches/synchronized-statements/content.md
new file mode 100644
index 000000000..8d17806b1
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/synchronized-statements/content.md
@@ -0,0 +1,123 @@
+# Synchronized statements
+
+```java
+class BankAccount {
+ private final Object lock = new Object();
+ private int balance = 0;
+ private boolean isClosed = true;
+
+ void open() throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ if (!isClosed) {
+ throw new BankAccountActionInvalidException("Account already open");
+ }
+ isClosed = false;
+ balance = 0;
+ }
+ }
+
+ void close() throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ if (isClosed) {
+ throw new BankAccountActionInvalidException("Account not open");
+ }
+ isClosed = true;
+ }
+ }
+
+ int getBalance() throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ checkIfClosed();
+ return balance;
+ }
+ }
+
+ void deposit(int amount) throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ checkIfClosed();
+ checkIfValidAmount(amount);
+
+ balance += amount;
+ }
+ }
+
+ void withdraw(int amount) throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ checkIfClosed();
+ checkIfValidAmount(amount);
+ checkIfEnoughMoneyInAccount(amount);
+
+ balance -= amount;
+ }
+ }
+
+ private void checkIfValidAmount(int amount) throws BankAccountActionInvalidException {
+ if (amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot deposit or withdraw negative amount");
+ }
+ }
+
+ private void checkIfEnoughMoneyInAccount(int amount) throws BankAccountActionInvalidException {
+ if (balance == 0) {
+ throw new BankAccountActionInvalidException("Cannot withdraw money from an empty account");
+ }
+ if (balance - amount < 0) {
+ throw new BankAccountActionInvalidException("Cannot withdraw more money than is currently in the account");
+ }
+ }
+
+ private void checkIfClosed() throws BankAccountActionInvalidException {
+ if (isClosed) {
+ throw new BankAccountActionInvalidException("Account closed");
+ }
+ }
+}
+```
+
+In this approach, the operation methods, such as `open`, `close`, `deposit` and `withdraw`, perform their operations in a `synchronized` code block.
+A lock is acquired on the synchronized object (`lock`) before the statements inside the block are executed.
+If another thread has a lock on the object, it must wait for it to be released.
+The lock is released after the block is executed.
+
+## Using `this` as the synchronized object
+
+Any object can be used as the lock, including `this`.
+For example:
+
+```java
+int getBalance() throws BankAccountActionInvalidException {
+ synchronized(this) {
+ checkIfClosed();
+ return balance;
+ }
+}
+```
+
+This is the same as using a [synchronized method][approach-synchronized-methods], which requires a lock on the same `this` object to run the method.
+For example:
+
+```java
+synchronized int getBalance() throws BankAccountActionInvalidException {
+ checkIfClosed();
+ return balance;
+}
+```
+
+When using [synchronized methods][approach-synchronized-methods] and `synchronized(this)`, it is important to keep in mind that it may be trying to acquire a lock on the same instance.
+For example:
+
+```java
+BankAccount account = new BankAccount();
+
+Thread thread1 = new Thread(() -> {
+ account.withdraw(5);
+});
+
+Thread thread2 = new Thread(() -> {
+ synchronized (account) {
+ // Code in here can not run at same time as account.withdraw in thread1.
+ }
+});
+```
+
+[approach-synchronized-methods]: https://exercism.org/tracks/java/exercises/bank-account/approaches/synchronized-methods
diff --git a/exercises/practice/bank-account/.approaches/synchronized-statements/snippet.txt b/exercises/practice/bank-account/.approaches/synchronized-statements/snippet.txt
new file mode 100644
index 000000000..6b3a368fb
--- /dev/null
+++ b/exercises/practice/bank-account/.approaches/synchronized-statements/snippet.txt
@@ -0,0 +1,7 @@
+private final Object lock = new Object();
+
+void deposit(int amount) throws BankAccountActionInvalidException {
+ synchronized(lock) {
+ balance += amount;
+ }
+}
\ No newline at end of file
diff --git a/exercises/practice/bank-account/.docs/instructions.append.md b/exercises/practice/bank-account/.docs/instructions.append.md
index 85db44ba2..9d3307ee3 100644
--- a/exercises/practice/bank-account/.docs/instructions.append.md
+++ b/exercises/practice/bank-account/.docs/instructions.append.md
@@ -1,13 +1,15 @@
# Instructions append
-This exercise introduces [concurrency](https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html).
-To pass the last test you might find the
-[`synchronized` keyword or locks](https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html) useful.
+This exercise introduces [concurrency][oracle-docs-concurrency].
+To pass the last test you might find the [`synchronized` keyword or locks][oracle-docs-synchronized] useful.
-Problems arising from running code concurrently are often intermittent because they depend on the order the code is
-executed. Therefore the last test runs many [threads](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)
-several times to increase the chances of catching a bug. That means this test should fail if your implementation is not
-[thread safe](https://en.wikipedia.org/wiki/Thread_safety), but there is a chance it will pass just because there was
-no concurrent modification attempt. It is unlikely that this will occur several times
-in a row since the order the code is executed should vary every time you run the test. So if you run the last test a
-couple of times and it passes every time then you can be reasonably sure that your implementation is correct.
\ No newline at end of file
+Problems arising from running code concurrently are often intermittent because they depend on the order the code is executed.
+Therefore the last test runs many [threads][threads-api] several times to increase the chances of catching a bug.
+That means this test should fail if your implementation is not [thread safe][wiki-thread-safety], but there is a chance it will pass just because there was no concurrent modification attempt.
+It is unlikely that this will occur several times in a row since the order the code is executed should vary every time you run the test.
+So if you run the last test a couple of times and it passes every time then you can be reasonably sure that your implementation is correct.
+
+[oracle-docs-concurrency]: https://docs.oracle.com/javase/tutorial/essential/concurrency/index.html
+[oracle-docs-synchronized]: https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html
+[threads-api]: https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html
+[wiki-thread-safety]: https://en.wikipedia.org/wiki/Thread_safety
diff --git a/exercises/practice/bank-account/.docs/instructions.md b/exercises/practice/bank-account/.docs/instructions.md
index 1265ac8b3..7398fbea1 100644
--- a/exercises/practice/bank-account/.docs/instructions.md
+++ b/exercises/practice/bank-account/.docs/instructions.md
@@ -1,27 +1,10 @@
# Instructions
-Simulate a bank account supporting opening/closing, withdrawals, and deposits
-of money. Watch out for concurrent transactions!
+Your task is to implement bank accounts supporting opening/closing, withdrawals, and deposits of money.
-A bank account can be accessed in multiple ways. Clients can make
-deposits and withdrawals using the internet, mobile phones, etc. Shops
-can charge against the account.
+As bank accounts can be accessed in many different ways (internet, mobile phones, automatic charges), your bank software must allow accounts to be safely accessed from multiple threads/processes (terminology depends on your programming language) in parallel.
+For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there are no [race conditions][wikipedia] between when you read the account balance and set the new balance.
-Create an account that can be accessed from multiple threads/processes
-(terminology depends on your programming language).
+It should be possible to close an account; operations against a closed account must fail.
-It should be possible to close an account; operations against a closed
-account must fail.
-
-## Instructions
-
-Run the test file, and fix each of the errors in turn. When you get the
-first test to pass, go to the first pending or skipped test, and make
-that pass as well. When all of the tests are passing, feel free to
-submit.
-
-Remember that passing code is just the first step. The goal is to work
-towards a solution that is as readable and expressive as you can make
-it.
-
-Have fun!
+[wikipedia]: https://en.wikipedia.org/wiki/Race_condition#In_software
diff --git a/exercises/practice/bank-account/.docs/introduction.md b/exercises/practice/bank-account/.docs/introduction.md
new file mode 100644
index 000000000..650b5d9c4
--- /dev/null
+++ b/exercises/practice/bank-account/.docs/introduction.md
@@ -0,0 +1,20 @@
+# Introduction
+
+After years of filling out forms and waiting, you've finally acquired your banking license.
+This means you are now officially eligible to open your own bank, hurray!
+
+Your first priority is to get the IT systems up and running.
+After a day of hard work, you can already open and close accounts, as well as handle withdrawals and deposits.
+
+Since you couldn't be bothered writing tests, you invite some friends to help test the system.
+However, after just five minutes, one of your friends claims they've lost money!
+While you're confident your code is bug-free, you start looking through the logs to investigate.
+
+Ah yes, just as you suspected, your friend is at fault!
+They shared their test credentials with another friend, and together they conspired to make deposits and withdrawals from the same account _in parallel_.
+Who would do such a thing?
+
+While you argue that it's physically _impossible_ for someone to access their account in parallel, your friend smugly notifies you that the banking rules _require_ you to support this.
+Thus, no parallel banking support, no go-live signal.
+Sighing, you create a mental note to work on this tomorrow.
+This will set your launch date back at _least_ one more day, but well...
diff --git a/exercises/practice/bank-account/.meta/config.json b/exercises/practice/bank-account/.meta/config.json
index 55ea3d6fa..fe8e36e3b 100644
--- a/exercises/practice/bank-account/.meta/config.json
+++ b/exercises/practice/bank-account/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Simulate a bank account supporting opening/closing, withdraws, and deposits of money. Watch out for concurrent transactions!",
"authors": [
"FridaTveit"
],
@@ -18,14 +17,18 @@
"solution": [
"src/main/java/BankAccount.java"
],
- "editor": [
- "src/main/java/BankAccountActionInvalidException.java"
- ],
"test": [
"src/test/java/BankAccountTest.java"
],
"example": [
".meta/src/reference/java/BankAccount.java"
+ ],
+ "editor": [
+ "src/main/java/BankAccountActionInvalidException.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
- }
+ },
+ "blurb": "Simulate a bank account supporting opening/closing, withdraws, and deposits of money. Watch out for concurrent transactions!"
}
diff --git a/exercises/practice/bank-account/.meta/src/reference/java/BankAccount.java b/exercises/practice/bank-account/.meta/src/reference/java/BankAccount.java
index 8f762cf1d..9146d0a5c 100644
--- a/exercises/practice/bank-account/.meta/src/reference/java/BankAccount.java
+++ b/exercises/practice/bank-account/.meta/src/reference/java/BankAccount.java
@@ -2,11 +2,18 @@ class BankAccount {
private int balance = 0;
private boolean isClosed = true;
- void open() {
+ void open() throws BankAccountActionInvalidException {
+ if (!isClosed) {
+ throw new BankAccountActionInvalidException("Account already open");
+ }
isClosed = false;
+ balance = 0;
}
- void close() {
+ void close() throws BankAccountActionInvalidException {
+ if (isClosed) {
+ throw new BankAccountActionInvalidException("Account not open");
+ }
isClosed = true;
}
diff --git a/exercises/practice/bank-account/.meta/tests.toml b/exercises/practice/bank-account/.meta/tests.toml
new file mode 100644
index 000000000..4e42d4dcb
--- /dev/null
+++ b/exercises/practice/bank-account/.meta/tests.toml
@@ -0,0 +1,61 @@
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[983a1528-4ceb-45e5-8257-8ce01aceb5ed]
+description = "Newly opened account has zero balance"
+
+[e88d4ec3-c6bf-4752-8e59-5046c44e3ba7]
+description = "Single deposit"
+
+[3d9147d4-63f4-4844-8d2b-1fee2e9a2a0d]
+description = "Multiple deposits"
+
+[08f1af07-27ae-4b38-aa19-770bde558064]
+description = "Withdraw once"
+
+[6f6d242f-8c31-4ac6-8995-a90d42cad59f]
+description = "Withdraw twice"
+
+[45161c94-a094-4c77-9cec-998b70429bda]
+description = "Can do multiple operations sequentially"
+
+[f9facfaa-d824-486e-8381-48832c4bbffd]
+description = "Cannot check balance of closed account"
+
+[7a65ba52-e35c-4fd2-8159-bda2bde6e59c]
+description = "Cannot deposit into closed account"
+
+[a0a1835d-faae-4ad4-a6f3-1fcc2121380b]
+description = "Cannot deposit into unopened account"
+
+[570dfaa5-0532-4c1f-a7d3-0f65c3265608]
+description = "Cannot withdraw from closed account"
+
+[c396d233-1c49-4272-98dc-7f502dbb9470]
+description = "Cannot close an account that was not opened"
+
+[c06f534f-bdc2-4a02-a388-1063400684de]
+description = "Cannot open an already opened account"
+
+[0722d404-6116-4f92-ba3b-da7f88f1669c]
+description = "Reopened account does not retain balance"
+
+[ec42245f-9361-4341-8231-a22e8d19c52f]
+description = "Cannot withdraw more than deposited"
+
+[4f381ef8-10ef-4507-8e1d-0631ecc8ee72]
+description = "Cannot withdraw negative"
+
+[d45df9ea-1db0-47f3-b18c-d365db49d938]
+description = "Cannot deposit negative"
+
+[ba0c1e0b-0f00-416f-8097-a7dfc97871ff]
+description = "Can handle concurrent transactions"
diff --git a/exercises/practice/bank-account/build.gradle b/exercises/practice/bank-account/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/bank-account/build.gradle
+++ b/exercises/practice/bank-account/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/bank-account/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/bank-account/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/bank-account/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/bank-account/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/bank-account/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/bank-account/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/bank-account/gradlew b/exercises/practice/bank-account/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/bank-account/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/bank-account/gradlew.bat b/exercises/practice/bank-account/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/bank-account/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/bank-account/src/main/java/BankAccount.java b/exercises/practice/bank-account/src/main/java/BankAccount.java
index 6178f1beb..0538b3611 100644
--- a/exercises/practice/bank-account/src/main/java/BankAccount.java
+++ b/exercises/practice/bank-account/src/main/java/BankAccount.java
@@ -1,10 +1,23 @@
-/*
+class BankAccount {
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+ void open() throws BankAccountActionInvalidException {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-Please remove this comment when submitting your solution.
+ void close() throws BankAccountActionInvalidException {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-*/
+ synchronized int getBalance() throws BankAccountActionInvalidException {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
+
+ synchronized void deposit(int amount) throws BankAccountActionInvalidException {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
+
+ synchronized void withdraw(int amount) throws BankAccountActionInvalidException {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
+
+}
\ No newline at end of file
diff --git a/exercises/practice/bank-account/src/main/java/BankAccountActionInvalidException.java b/exercises/practice/bank-account/src/main/java/BankAccountActionInvalidException.java
index 4b06fa845..a4fa24c81 100644
--- a/exercises/practice/bank-account/src/main/java/BankAccountActionInvalidException.java
+++ b/exercises/practice/bank-account/src/main/java/BankAccountActionInvalidException.java
@@ -1,6 +1,6 @@
-class BankAccountActionInvalidException extends Exception {
+public class BankAccountActionInvalidException extends Exception {
- BankAccountActionInvalidException(String message) {
+ public BankAccountActionInvalidException(String message) {
super(message);
}
}
diff --git a/exercises/practice/bank-account/src/test/java/BankAccountTest.java b/exercises/practice/bank-account/src/test/java/BankAccountTest.java
index ba0982893..8c6d65de7 100644
--- a/exercises/practice/bank-account/src/test/java/BankAccountTest.java
+++ b/exercises/practice/bank-account/src/test/java/BankAccountTest.java
@@ -1,154 +1,202 @@
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.assertj.core.api.Assertions.fail;
-
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import java.util.Random;
+import static org.assertj.core.api.Assertions.*;
+
public class BankAccountTest {
- private BankAccount bankAccount = new BankAccount();
+ private BankAccount bankAccount;
+
+ @BeforeEach
+ public void setUp() {
+ bankAccount = new BankAccount();
+ }
@Test
+ @DisplayName("Newly opened account has zero balance")
public void newlyOpenedAccountHasEmptyBalance() throws BankAccountActionInvalidException {
bankAccount.open();
assertThat(bankAccount.getBalance()).isEqualTo(0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void canDepositMoney() throws BankAccountActionInvalidException {
+ @DisplayName("Single deposit")
+ public void singleDeposit() throws BankAccountActionInvalidException {
bankAccount.open();
+ bankAccount.deposit(100);
- bankAccount.deposit(10);
-
- assertThat(bankAccount.getBalance()).isEqualTo(10);
+ assertThat(bankAccount.getBalance()).isEqualTo(100);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void canDepositMoneySequentially() throws BankAccountActionInvalidException {
+ @DisplayName("Multiple deposits")
+ public void multipleDeposits() throws BankAccountActionInvalidException {
bankAccount.open();
+ bankAccount.deposit(100);
+ bankAccount.deposit(50);
- bankAccount.deposit(5);
- bankAccount.deposit(23);
-
- assertThat(bankAccount.getBalance()).isEqualTo(28);
+ assertThat(bankAccount.getBalance()).isEqualTo(150);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void canWithdrawMoney() throws BankAccountActionInvalidException {
+ @DisplayName("Withdraw once")
+ public void withdrawOnce() throws BankAccountActionInvalidException {
bankAccount.open();
- bankAccount.deposit(10);
-
- bankAccount.withdraw(5);
+ bankAccount.deposit(100);
+ bankAccount.withdraw(75);
- assertThat(bankAccount.getBalance()).isEqualTo(5);
+ assertThat(bankAccount.getBalance()).isEqualTo(25);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void canWithdrawMoneySequentially() throws BankAccountActionInvalidException {
+ @DisplayName("Withdraw twice")
+ public void withdrawTwice() throws BankAccountActionInvalidException {
bankAccount.open();
- bankAccount.deposit(23);
-
- bankAccount.withdraw(10);
- bankAccount.withdraw(13);
+ bankAccount.deposit(100);
+ bankAccount.withdraw(80);
+ bankAccount.withdraw(20);
assertThat(bankAccount.getBalance()).isEqualTo(0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void cannotWithdrawMoneyFromEmptyAccount() {
+ @DisplayName("Can do multiple operations sequentially")
+ public void canDoMultipleOperationsSequentially() throws BankAccountActionInvalidException {
bankAccount.open();
+ bankAccount.deposit(100);
+ bankAccount.deposit(110);
+ bankAccount.withdraw(200);
+ bankAccount.deposit(60);
+ bankAccount.withdraw(50);
- assertThatExceptionOfType(BankAccountActionInvalidException.class)
- .isThrownBy(() -> bankAccount.withdraw(5))
- .withMessage("Cannot withdraw money from an empty account");
+ assertThat(bankAccount.getBalance()).isEqualTo(20);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void cannotWithdrawMoreMoneyThanYouHave() throws BankAccountActionInvalidException {
+ @DisplayName("Cannot check balance of closed account")
+ public void cannotCheckBalanceOfClosedAccount() throws BankAccountActionInvalidException {
bankAccount.open();
- bankAccount.deposit(6);
+ bankAccount.close();
assertThatExceptionOfType(BankAccountActionInvalidException.class)
- .isThrownBy(() -> bankAccount.withdraw(7))
- .withMessage("Cannot withdraw more money than is currently in the account");
+ .isThrownBy(bankAccount::getBalance)
+ .withMessage("Account closed");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void cannotDepositNegativeAmount() {
+ @DisplayName("Cannot deposit into closed account")
+ public void cannotDepositIntoClosedAccount() throws BankAccountActionInvalidException {
bankAccount.open();
+ bankAccount.close();
assertThatExceptionOfType(BankAccountActionInvalidException.class)
- .isThrownBy(() -> bankAccount.deposit(-1))
- .withMessage("Cannot deposit or withdraw negative amount");
+ .isThrownBy(() -> bankAccount.deposit(50))
+ .withMessage("Account closed");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void cannotWithdrawNegativeAmount() throws BankAccountActionInvalidException {
+ @DisplayName("Cannot deposit into unopened account")
+ public void cannotDepositIntoUnopenedAccount() {
+ assertThatExceptionOfType(BankAccountActionInvalidException.class)
+ .isThrownBy(() -> bankAccount.deposit(50))
+ .withMessage("Account closed");
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("Cannot withdraw from closed account")
+ public void cannotWithdrawFromClosedAccount() throws BankAccountActionInvalidException {
bankAccount.open();
- bankAccount.deposit(105);
+ bankAccount.close();
assertThatExceptionOfType(BankAccountActionInvalidException.class)
- .isThrownBy(() -> bankAccount.withdraw(-5))
- .withMessage("Cannot deposit or withdraw negative amount");
+ .isThrownBy(() -> bankAccount.withdraw(50))
+ .withMessage("Account closed");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void cannotGetBalanceOfClosedAccount() throws BankAccountActionInvalidException {
+ @DisplayName("Cannot close an account that was not opened")
+ public void cannotCloseAnAccountThatWasNotOpened() {
+ assertThatExceptionOfType(BankAccountActionInvalidException.class)
+ .isThrownBy(bankAccount::close)
+ .withMessage("Account not open");
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("Cannot open an already opened account")
+ public void cannotOpenAnAlreadyOpenedAccount() throws BankAccountActionInvalidException {
bankAccount.open();
- bankAccount.deposit(10);
- bankAccount.close();
assertThatExceptionOfType(BankAccountActionInvalidException.class)
- .isThrownBy(bankAccount::getBalance)
- .withMessage("Account closed");
+ .isThrownBy(bankAccount::open)
+ .withMessage("Account already open");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void cannotDepositMoneyIntoClosedAccount() {
+ @DisplayName("Reopened account does not retain balance")
+ public void reopenedAccountDoesNotRetainBalance() throws BankAccountActionInvalidException {
bankAccount.open();
+ bankAccount.deposit(50);
bankAccount.close();
+ bankAccount.open();
+
+ assertThat(bankAccount.getBalance()).isEqualTo(0);
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("Cannot withdraw more than deposited")
+ public void cannotWithdrawMoreThanDeposited() throws BankAccountActionInvalidException {
+ bankAccount.open();
+ bankAccount.deposit(25);
assertThatExceptionOfType(BankAccountActionInvalidException.class)
- .isThrownBy(() -> bankAccount.deposit(5))
- .withMessage("Account closed");
+ .isThrownBy(() -> bankAccount.withdraw(50))
+ .withMessage("Cannot withdraw more money than is currently in the account");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void cannotWithdrawMoneyFromClosedAccount() throws BankAccountActionInvalidException {
+ @DisplayName("Cannot withdraw negative")
+ public void cannotWithdrawNegativeAmount() throws BankAccountActionInvalidException {
bankAccount.open();
- bankAccount.deposit(20);
- bankAccount.close();
+ bankAccount.deposit(100);
assertThatExceptionOfType(BankAccountActionInvalidException.class)
- .isThrownBy(() -> bankAccount.withdraw(5))
- .withMessage("Account closed");
+ .isThrownBy(() -> bankAccount.withdraw(-50))
+ .withMessage("Cannot deposit or withdraw negative amount");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void bankAccountIsClosedBeforeItIsOpened() {
+ @DisplayName("Cannot deposit negative")
+ public void cannotDepositNegativeAmount() throws BankAccountActionInvalidException {
+ bankAccount.open();
+
assertThatExceptionOfType(BankAccountActionInvalidException.class)
- .isThrownBy(bankAccount::getBalance)
- .withMessage("Account closed");
+ .isThrownBy(() -> bankAccount.deposit(-50))
+ .withMessage("Cannot deposit or withdraw negative amount");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void canAdjustBalanceConcurrently() throws BankAccountActionInvalidException, InterruptedException {
+ @DisplayName("Can handle concurrent transactions")
+ public void canHandleConcurrentTransactions() throws BankAccountActionInvalidException, InterruptedException {
bankAccount.open();
bankAccount.deposit(1000);
@@ -158,7 +206,7 @@ public void canAdjustBalanceConcurrently() throws BankAccountActionInvalidExcept
}
}
- private void adjustBalanceConcurrently() throws BankAccountActionInvalidException, InterruptedException {
+ private void adjustBalanceConcurrently() throws InterruptedException {
Random random = new Random();
Thread[] threads = new Thread[1000];
diff --git a/exercises/practice/beer-song/.meta/config.json b/exercises/practice/beer-song/.meta/config.json
index c78000f5a..adf537182 100644
--- a/exercises/practice/beer-song/.meta/config.json
+++ b/exercises/practice/beer-song/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Produce the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.",
"authors": [
"jeseekia"
],
@@ -33,8 +32,12 @@
],
"example": [
".meta/src/reference/java/BeerSong.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
+ "blurb": "Produce the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.",
"source": "Learn to Program by Chris Pine",
- "source_url": "http://pine.fm/LearnToProgram/?Chapter=06"
+ "source_url": "https://pine.fm/LearnToProgram/?Chapter=06"
}
diff --git a/exercises/practice/beer-song/.meta/version b/exercises/practice/beer-song/.meta/version
deleted file mode 100644
index 7ec1d6db4..000000000
--- a/exercises/practice/beer-song/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-2.1.0
diff --git a/exercises/practice/beer-song/src/main/java/BeerSong.java b/exercises/practice/beer-song/src/main/java/BeerSong.java
index 6178f1beb..9fe51b1f5 100644
--- a/exercises/practice/beer-song/src/main/java/BeerSong.java
+++ b/exercises/practice/beer-song/src/main/java/BeerSong.java
@@ -1,10 +1,11 @@
-/*
+class BeerSong {
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+ String sing(int startBottles, int takeDown) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-Please remove this comment when submitting your solution.
+ String singSong() {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-*/
+}
\ No newline at end of file
diff --git a/exercises/practice/binary-search-tree/.docs/instructions.md b/exercises/practice/binary-search-tree/.docs/instructions.md
index ba3c42eb6..7625220e9 100644
--- a/exercises/practice/binary-search-tree/.docs/instructions.md
+++ b/exercises/practice/binary-search-tree/.docs/instructions.md
@@ -2,53 +2,69 @@
Insert and search for numbers in a binary tree.
-When we need to represent sorted data, an array does not make a good
-data structure.
-
-Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes
-`[1, 3, 4, 5, 2]` now we must sort the entire array again! We can
-improve on this by realizing that we only need to make space for the new
-item `[1, nil, 3, 4, 5]`, and then adding the item in the space we
-added. But this still requires us to shift many elements down by one.
-
-Binary Search Trees, however, can operate on sorted data much more
-efficiently.
-
-A binary search tree consists of a series of connected nodes. Each node
-contains a piece of data (e.g. the number 3), a variable named `left`,
-and a variable named `right`. The `left` and `right` variables point at
-`nil`, or other nodes. Since these other nodes in turn have other nodes
-beneath them, we say that the left and right variables are pointing at
-subtrees. All data in the left subtree is less than or equal to the
-current node's data, and all data in the right subtree is greater than
-the current node's data.
-
-For example, if we had a node containing the data 4, and we added the
-data 2, our tree would look like this:
+When we need to represent sorted data, an array does not make a good data structure.
+Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes `[1, 3, 4, 5, 2]`.
+Now we must sort the entire array again!
+We can improve on this by realizing that we only need to make space for the new item `[1, nil, 3, 4, 5]`, and then adding the item in the space we added.
+But this still requires us to shift many elements down by one.
+
+Binary Search Trees, however, can operate on sorted data much more efficiently.
+
+A binary search tree consists of a series of connected nodes.
+Each node contains a piece of data (e.g. the number 3), a variable named `left`, and a variable named `right`.
+The `left` and `right` variables point at `nil`, or other nodes.
+Since these other nodes in turn have other nodes beneath them, we say that the left and right variables are pointing at subtrees.
+All data in the left subtree is less than or equal to the current node's data, and all data in the right subtree is greater than the current node's data.
+
+For example, if we had a node containing the data 4, and we added the data 2, our tree would look like this:
+
+
+
+```text
4
/
2
+```
If we then added 6, it would look like this:
+
+
+```text
4
/ \
2 6
+```
If we then added 3, it would look like this
+
+
+```text
4
/ \
2 6
\
3
+```
And if we then added 1, 5, and 7, it would look like this
+
+
+```text
4
/ \
/ \
2 6
/ \ / \
1 3 5 7
+```
+
+## Credit
+
+The images were created by [habere-et-dispertire][habere-et-dispertire] using [PGF/TikZ][pgf-tikz] by Till Tantau.
+
+[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire
+[pgf-tikz]: https://en.wikipedia.org/wiki/PGF/TikZ
diff --git a/exercises/practice/binary-search-tree/.meta/config.json b/exercises/practice/binary-search-tree/.meta/config.json
index 7871981df..0a454a4c7 100644
--- a/exercises/practice/binary-search-tree/.meta/config.json
+++ b/exercises/practice/binary-search-tree/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Insert and search for numbers in a binary tree.",
"authors": [
"javaeeeee"
],
@@ -35,8 +34,11 @@
],
"example": [
".meta/src/reference/java/BinarySearchTree.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
- "source": "Josh Cheek",
- "source_url": "https://twitter.com/josh_cheek"
+ "blurb": "Insert and search for numbers in a binary tree.",
+ "source": "Josh Cheek"
}
diff --git a/exercises/practice/binary-search-tree/.meta/version b/exercises/practice/binary-search-tree/.meta/version
deleted file mode 100644
index 3eefcb9dd..000000000
--- a/exercises/practice/binary-search-tree/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.0.0
diff --git a/exercises/practice/binary-search-tree/build.gradle b/exercises/practice/binary-search-tree/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/binary-search-tree/build.gradle
+++ b/exercises/practice/binary-search-tree/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/binary-search-tree/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/binary-search-tree/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/binary-search-tree/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/binary-search-tree/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/binary-search-tree/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/binary-search-tree/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/binary-search-tree/gradlew b/exercises/practice/binary-search-tree/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/binary-search-tree/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/binary-search-tree/gradlew.bat b/exercises/practice/binary-search-tree/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/binary-search-tree/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/binary-search-tree/src/test/java/BinarySearchTreeTest.java b/exercises/practice/binary-search-tree/src/test/java/BinarySearchTreeTest.java
index b59414ed5..f6d8f1478 100644
--- a/exercises/practice/binary-search-tree/src/test/java/BinarySearchTreeTest.java
+++ b/exercises/practice/binary-search-tree/src/test/java/BinarySearchTreeTest.java
@@ -3,12 +3,14 @@
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
public class BinarySearchTreeTest {
@Test
+ @DisplayName("data is retained")
public void dataIsRetained() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
@@ -22,8 +24,9 @@ public void dataIsRetained() {
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("insert data at proper node")
public void insertsLess() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
@@ -43,8 +46,9 @@ public void insertsLess() {
assertThat(left.getData()).isEqualTo(expectedLeft);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("same number at left node")
public void insertsSame() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
String expectedRoot = "4";
@@ -63,8 +67,9 @@ public void insertsSame() {
assertThat(left.getData()).isEqualTo(expectedLeft);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("greater number at right node")
public void insertsRight() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
int expectedRoot = 4;
@@ -83,8 +88,9 @@ public void insertsRight() {
assertThat(right.getData()).isEqualTo(expectedRight);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("can create complex tree")
public void createsComplexTree() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
List expected = List.of('4', '2', '6', '1', '3', '5', '7');
@@ -95,8 +101,9 @@ public void createsComplexTree() {
assertThat(binarySearchTree.getAsLevelOrderList()).isEqualTo(expected);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("can sort single number")
public void sortsSingleElement() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
List expected = Collections.singletonList("2");
@@ -106,8 +113,9 @@ public void sortsSingleElement() {
assertThat(binarySearchTree.getAsSortedList()).isEqualTo(expected);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("can sort if second number is smaller than first")
public void sortsCollectionOfTwoIfSecondInsertedIsSmallerThanFirst() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
List expected = List.of(1, 2);
@@ -118,8 +126,9 @@ public void sortsCollectionOfTwoIfSecondInsertedIsSmallerThanFirst() {
assertThat(binarySearchTree.getAsSortedList()).isEqualTo(expected);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("can sort if second number is same as first")
public void sortsCollectionOfTwoIfSecondNumberisSameAsFirst() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
List expected = List.of('2', '2');
@@ -130,8 +139,9 @@ public void sortsCollectionOfTwoIfSecondNumberisSameAsFirst() {
assertThat(binarySearchTree.getAsSortedList()).isEqualTo(expected);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("can sort if second number is greater than first")
public void sortsCollectionOfTwoIfSecondInsertedIsBiggerThanFirst() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
List expected = List.of('2', '3');
@@ -142,8 +152,9 @@ public void sortsCollectionOfTwoIfSecondInsertedIsBiggerThanFirst() {
assertThat(binarySearchTree.getAsSortedList()).isEqualTo(expected);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("can sort complex tree")
public void iteratesOverComplexTree() {
BinarySearchTree binarySearchTree = new BinarySearchTree<>();
List expected = List.of("1", "2", "3", "5", "6", "7");
diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md
index 4dcaba726..12f4358eb 100644
--- a/exercises/practice/binary-search/.docs/instructions.md
+++ b/exercises/practice/binary-search/.docs/instructions.md
@@ -1,35 +1,29 @@
# Instructions
-Implement a binary search algorithm.
+Your task is to implement a binary search algorithm.
-Searching a sorted collection is a common task. A dictionary is a sorted
-list of word definitions. Given a word, one can find its definition. A
-telephone book is a sorted list of people's names, addresses, and
-telephone numbers. Knowing someone's name allows one to quickly find
-their telephone number and address.
+A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for.
+It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations.
-If the list to be searched contains more than a few items (a dozen, say)
-a binary search will require far fewer comparisons than a linear search,
-but it imposes the requirement that the list be sorted.
+~~~~exercism/caution
+Binary search only works when a list has been sorted.
+~~~~
-In computer science, a binary search or half-interval search algorithm
-finds the position of a specified input value (the search "key") within
-an array sorted by key value.
+The algorithm looks like this:
-In each step, the algorithm compares the search key value with the key
-value of the middle element of the array.
+- Find the middle element of a _sorted_ list and compare it with the item we're looking for.
+- If the middle element is our item, then we're done!
+- If the middle element is greater than our item, we can eliminate that element and all the elements **after** it.
+- If the middle element is less than our item, we can eliminate that element and all the elements **before** it.
+- If every element of the list has been eliminated then the item is not in the list.
+- Otherwise, repeat the process on the part of the list that has not been eliminated.
-If the keys match, then a matching element has been found and its index,
-or position, is returned.
+Here's an example:
-Otherwise, if the search key is less than the middle element's key, then
-the algorithm repeats its action on the sub-array to the left of the
-middle element or, if the search key is greater, on the sub-array to the
-right.
+Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`.
-If the remaining array to be searched is empty, then the key cannot be
-found in the array and a special "not found" indication is returned.
-
-A binary search halves the number of items to check with each iteration,
-so locating an item (or determining its absence) takes logarithmic time.
-A binary search is a dichotomic divide and conquer search algorithm.
+- We start by comparing 23 with the middle element, 16.
+- Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`.
+- We then compare 23 with the new middle element, 28.
+- Since 23 is less than 28, we can eliminate the right half of the list: `[23]`.
+- We've found our item.
diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md
new file mode 100644
index 000000000..03496599e
--- /dev/null
+++ b/exercises/practice/binary-search/.docs/introduction.md
@@ -0,0 +1,13 @@
+# Introduction
+
+You have stumbled upon a group of mathematicians who are also singer-songwriters.
+They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers (like [0][zero] or [73][seventy-three] or [6174][kaprekars-constant]).
+
+You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while.
+Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about.
+
+You realize that you can use a binary search algorithm to quickly find a song given the title.
+
+[zero]: https://en.wikipedia.org/wiki/0
+[seventy-three]: https://en.wikipedia.org/wiki/73_(number)
+[kaprekars-constant]: https://en.wikipedia.org/wiki/6174_(number)
diff --git a/exercises/practice/binary-search/.meta/config.json b/exercises/practice/binary-search/.meta/config.json
index 41bb3927c..5dda31dc2 100644
--- a/exercises/practice/binary-search/.meta/config.json
+++ b/exercises/practice/binary-search/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Implement a binary search algorithm.",
"authors": [
"javaeeeee"
],
@@ -29,16 +28,20 @@
"solution": [
"src/main/java/BinarySearch.java"
],
- "editor": [
- "src/main/java/ValueNotFoundException.java"
- ],
"test": [
"src/test/java/BinarySearchTest.java"
],
"example": [
".meta/src/reference/java/BinarySearch.java"
+ ],
+ "editor": [
+ "src/main/java/ValueNotFoundException.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
+ "blurb": "Implement a binary search algorithm.",
"source": "Wikipedia",
- "source_url": "http://en.wikipedia.org/wiki/Binary_search_algorithm"
+ "source_url": "https://en.wikipedia.org/wiki/Binary_search_algorithm"
}
diff --git a/exercises/practice/binary-search/.meta/version b/exercises/practice/binary-search/.meta/version
deleted file mode 100644
index f0bb29e76..000000000
--- a/exercises/practice/binary-search/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.3.0
diff --git a/exercises/practice/binary-search/build.gradle b/exercises/practice/binary-search/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/binary-search/build.gradle
+++ b/exercises/practice/binary-search/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/binary-search/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/binary-search/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/binary-search/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/binary-search/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/binary-search/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/binary-search/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/binary-search/gradlew b/exercises/practice/binary-search/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/binary-search/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/binary-search/gradlew.bat b/exercises/practice/binary-search/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/binary-search/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/binary-search/src/main/java/BinarySearch.java b/exercises/practice/binary-search/src/main/java/BinarySearch.java
index 6178f1beb..270fd4e50 100644
--- a/exercises/practice/binary-search/src/main/java/BinarySearch.java
+++ b/exercises/practice/binary-search/src/main/java/BinarySearch.java
@@ -1,10 +1,11 @@
-/*
+import java.util.List;
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+class BinarySearch {
+ BinarySearch(List items) {
+ throw new UnsupportedOperationException("Please implement the BinarySearch constructor");
+ }
-Please remove this comment when submitting your solution.
-
-*/
+ int indexOf(int item) throws ValueNotFoundException {
+ throw new UnsupportedOperationException("Please implement the indexOf method");
+ }
+}
diff --git a/exercises/practice/binary-search/src/main/java/ValueNotFoundException.java b/exercises/practice/binary-search/src/main/java/ValueNotFoundException.java
index ace0d4243..740cb11f0 100644
--- a/exercises/practice/binary-search/src/main/java/ValueNotFoundException.java
+++ b/exercises/practice/binary-search/src/main/java/ValueNotFoundException.java
@@ -1,4 +1,4 @@
-class ValueNotFoundException extends Exception {
+public class ValueNotFoundException extends Exception {
ValueNotFoundException(String message) {
super(message);
diff --git a/exercises/practice/binary-search/src/test/java/BinarySearchTest.java b/exercises/practice/binary-search/src/test/java/BinarySearchTest.java
index 621d7379a..4846489c9 100644
--- a/exercises/practice/binary-search/src/test/java/BinarySearchTest.java
+++ b/exercises/practice/binary-search/src/test/java/BinarySearchTest.java
@@ -1,8 +1,9 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.List;
@@ -10,6 +11,7 @@
public class BinarySearchTest {
@Test
+ @DisplayName("finds a value in an array with one element")
public void findsAValueInAnArrayWithOneElement() throws ValueNotFoundException {
List listOfUnitLength = Collections.singletonList(6);
@@ -18,8 +20,9 @@ public void findsAValueInAnArrayWithOneElement() throws ValueNotFoundException {
assertThat(search.indexOf(6)).isEqualTo(0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("finds a value in the middle of an array")
public void findsAValueInTheMiddleOfAnArray() throws ValueNotFoundException {
List sortedList = List.of(1, 3, 4, 6, 8, 9, 11);
@@ -28,8 +31,9 @@ public void findsAValueInTheMiddleOfAnArray() throws ValueNotFoundException {
assertThat(search.indexOf(6)).isEqualTo(3);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("finds a value at the beginning of an array")
public void findsAValueAtTheBeginningOfAnArray() throws ValueNotFoundException {
List sortedList = List.of(1, 3, 4, 6, 8, 9, 11);
@@ -38,8 +42,9 @@ public void findsAValueAtTheBeginningOfAnArray() throws ValueNotFoundException {
assertThat(search.indexOf(1)).isEqualTo(0);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("finds a value at the end of an array")
public void findsAValueAtTheEndOfAnArray() throws ValueNotFoundException {
List sortedList = List.of(1, 3, 4, 6, 8, 9, 11);
@@ -48,8 +53,9 @@ public void findsAValueAtTheEndOfAnArray() throws ValueNotFoundException {
assertThat(search.indexOf(11)).isEqualTo(6);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("finds a value in an array of odd length")
public void findsAValueInAnArrayOfOddLength() throws ValueNotFoundException {
List sortedListOfOddLength = List.of(1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 634);
@@ -58,8 +64,9 @@ public void findsAValueInAnArrayOfOddLength() throws ValueNotFoundException {
assertThat(search.indexOf(144)).isEqualTo(9);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("finds a value in an array of even length")
public void findsAValueInAnArrayOfEvenLength() throws ValueNotFoundException {
List sortedListOfEvenLength = List.of(1, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377);
@@ -68,8 +75,9 @@ public void findsAValueInAnArrayOfEvenLength() throws ValueNotFoundException {
assertThat(search.indexOf(21)).isEqualTo(5);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("identifies that a value is not included in the array")
public void identifiesThatAValueIsNotFoundInTheArray() {
List sortedList = List.of(1, 3, 4, 6, 8, 9, 11);
@@ -80,8 +88,9 @@ public void identifiesThatAValueIsNotFoundInTheArray() {
.withMessage("Value not in array");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("a value smaller than the array's smallest value is not found")
public void aValueSmallerThanTheArraysSmallestValueIsNotFound() {
List sortedList = List.of(1, 3, 4, 6, 8, 9, 11);
@@ -92,9 +101,10 @@ public void aValueSmallerThanTheArraysSmallestValueIsNotFound() {
.withMessage("Value not in array");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void aValueLargerThanTheArraysSmallestValueIsNotFound() throws ValueNotFoundException {
+ @DisplayName("a value larger than the array's largest value is not found")
+ public void aValueLargerThanTheArraysLargestValueIsNotFound() throws ValueNotFoundException {
List sortedList = List.of(1, 3, 4, 6, 8, 9, 11);
BinarySearch search = new BinarySearch(sortedList);
@@ -104,8 +114,9 @@ public void aValueLargerThanTheArraysSmallestValueIsNotFound() throws ValueNotFo
.withMessage("Value not in array");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("nothing is found in an empty array")
public void nothingIsFoundInAnEmptyArray() throws ValueNotFoundException {
List emptyList = Collections.emptyList();
@@ -116,8 +127,9 @@ public void nothingIsFoundInAnEmptyArray() throws ValueNotFoundException {
.withMessage("Value not in array");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("nothing is found when the left and right bounds cross")
public void nothingIsFoundWhenTheLeftAndRightBoundCross() throws ValueNotFoundException {
List sortedList = List.of(1, 2);
diff --git a/exercises/practice/binary/.meta/config.json b/exercises/practice/binary/.meta/config.json
index 33f2c3afc..e50b3b3b7 100644
--- a/exercises/practice/binary/.meta/config.json
+++ b/exercises/practice/binary/.meta/config.json
@@ -31,6 +31,9 @@
],
"example": [
".meta/src/reference/java/Binary.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
"source": "All of Computer Science",
diff --git a/exercises/practice/binary/.meta/version b/exercises/practice/binary/.meta/version
deleted file mode 100644
index 9084fa2f7..000000000
--- a/exercises/practice/binary/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.1.0
diff --git a/exercises/practice/binary/src/main/java/Binary.java b/exercises/practice/binary/src/main/java/Binary.java
index e69de29bb..412e64ef2 100644
--- a/exercises/practice/binary/src/main/java/Binary.java
+++ b/exercises/practice/binary/src/main/java/Binary.java
@@ -0,0 +1,10 @@
+public class Binary {
+
+ public Binary(String input) {
+ throw new UnsupportedOperationException("Please implement the Binary constructor.");
+ }
+
+ public int getDecimal() {
+ throw new UnsupportedOperationException("Please implement the Binary.getDecimal() method.");
+ }
+}
diff --git a/exercises/practice/bob/.approaches/answer-array/content.md b/exercises/practice/bob/.approaches/answer-array/content.md
new file mode 100644
index 000000000..92cb6a542
--- /dev/null
+++ b/exercises/practice/bob/.approaches/answer-array/content.md
@@ -0,0 +1,91 @@
+# Answer array
+
+```java
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+class Bob {
+ final private static String[] answers = {
+ "Whatever.",
+ "Sure.",
+ "Whoa, chill out!",
+ "Calm down, I know what I'm doing!"
+ };
+ final private static Pattern isAlpha = Pattern.compile("[a-zA-Z]");
+ final private static Predicate < String > isShout = msg -> isAlpha.matcher(msg).find() && msg == msg.toUpperCase();
+
+ public String hey(String message) {
+ var speech = message.trim();
+ if (speech.isEmpty()) {
+ return "Fine. Be that way!";
+ }
+ var questioning = speech.endsWith("?") ? 1 : 0;
+ var shouting = isShout.test(speech) ? 2 : 0;
+ return answers[questioning + shouting];
+ }
+}
+```
+
+In this approach you define an array that contains Bob’s answers, and each condition is given a score.
+The correct answer is selected from the array by using the score as the array index.
+
+The `String` [`trim()`][trim] method is applied to the input to eliminate any whitespace at either end of the input.
+If the string has no characters left, it returns the response for saying nothing.
+
+~~~~exercism/caution
+Note that a `null` `string` would be different from a `String` of all whitespace.
+A `null` `String` would throw a `NullPointerException` if `trim()` were applied to it.
+~~~~
+
+A [Pattern][pattern] is defined to look for at least one English alphabetic character.
+
+The first half of the `isShout` [Predicate][predicate]
+
+```java
+isAlpha.matcher(msg).find() && msg == msg.toUpperCase();
+```
+
+is constructed from the `Pattern` [`matcher()`][matcher-method] method and the [`Matcher`][matcher] [`find()`][find] method
+to ensure there is at least one letter character in the `String`.
+This is because the second half of the condition tests that the uppercased input is the same as the input.
+If the input were only `"123"` it would equal itself uppercased, but without letters it would not be a shout.
+
+A question is determined by use of the [`endsWith()`][endswith] method to see if the input ends with a question mark.
+
+The conditions of being a question and being a shout are assigned scores through the use of the [ternary operator][ternary].
+For example, giving a question a score of `1` would use an index of `1` to get the element from the answers array, which is `"Sure."`.
+
+| isShout | isQuestion | Index | Answer |
+| ------- | ---------- | --------- | ------------------------------------- |
+| `false` | `false` | 0 + 0 = 0 | `"Whatever."` |
+| `false` | `true` | 0 + 1 = 1 | `"Sure."` |
+| `true` | `false` | 2 + 0 = 2 | `"Whoa, chill out!"` |
+| `true` | `true` | 2 + 1 = 3 | `"Calm down, I know what I'm doing!"` |
+
+## Shortening
+
+When the body of an `if` statement is a single line, both the test expression and the body _could_ be put on the same line, like so
+
+```java
+if (speech.isEmpty()) return "Fine. Be that way!";
+```
+
+or the body _could_ be put on a separate line without curly braces
+
+```java
+if (speech.isEmpty())
+ return "Fine. Be that way!";
+```
+
+However, the [Java Coding Conventions][coding-conventions] advise to always use curly braces for `if` statements, which helps to avoid errors.
+Your team may choose to overrule them at its own risk.
+
+[trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim()
+[pattern]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html
+[predicate]: https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html
+[matcher]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html
+[matcher-method]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#matcher-java.lang.CharSequence-
+[find]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html#find--
+[ternary]: https://www.geeksforgeeks.org/java-ternary-operator-with-examples/
+[endswith]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#endsWith(java.lang.String)
+[coding-conventions]: https://www.oracle.com/java/technologies/javase/codeconventions-statements.html#449
diff --git a/exercises/practice/bob/.approaches/answer-array/snippet.txt b/exercises/practice/bob/.approaches/answer-array/snippet.txt
new file mode 100644
index 000000000..8d29be255
--- /dev/null
+++ b/exercises/practice/bob/.approaches/answer-array/snippet.txt
@@ -0,0 +1,8 @@
+public String hey(String message) {
+ var speech = message.trim();
+ if (speech.isEmpty())
+ return "Fine. Be that way!";
+ var questioning = speech.endsWith("?") ? 1 : 0;
+ var shouting = isShout.test(speech) ? 2 : 0;
+ return answers[questioning + shouting];
+}
diff --git a/exercises/practice/bob/.approaches/config.json b/exercises/practice/bob/.approaches/config.json
new file mode 100644
index 000000000..14e2d8764
--- /dev/null
+++ b/exercises/practice/bob/.approaches/config.json
@@ -0,0 +1,44 @@
+{
+ "introduction": {
+ "authors": [
+ "bobahop"
+ ],
+ "contributors": [
+ "jagdish-15",
+ "kahgoh"
+ ]
+ },
+ "approaches": [
+ {
+ "uuid": "6ca5c7c0-f8f1-49b2-b137-951fa39f89eb",
+ "slug": "method-based-if-statements",
+ "title": "method-based if statements",
+ "blurb": "Use if statements to return the answer with the help of methods.",
+ "authors": [
+ "jagdish-15"
+ ],
+ "contributors": [
+ "BenjaminGale",
+ "kahgoh"
+ ]
+ },
+ {
+ "uuid": "323eb230-7f27-4301-88ea-19c39d3eb5b6",
+ "slug": "variable-based-if-statements",
+ "title": "variable-based if statements",
+ "blurb": "Use if statements to return the answer.",
+ "authors": [
+ "bobahop"
+ ]
+ },
+ {
+ "uuid": "11baf0c0-a596-4495-8c25-521c023c3103",
+ "slug": "answer-array",
+ "title": "Answer array",
+ "blurb": "Index into an array to return the answer.",
+ "authors": [
+ "bobahop"
+ ]
+ }
+ ]
+}
diff --git a/exercises/practice/bob/.approaches/introduction.md b/exercises/practice/bob/.approaches/introduction.md
new file mode 100644
index 000000000..96575876e
--- /dev/null
+++ b/exercises/practice/bob/.approaches/introduction.md
@@ -0,0 +1,143 @@
+# Introduction
+
+In this exercise, we’re working on a program to determine Bob’s responses based on the tone and style of given messages.
+Bob responds differently depending on whether a message is a question, a shout, both, or silence.
+Various approaches can be used to implement this logic efficiently and cleanly, ensuring the code remains readable and easy to maintain.
+
+## General guidance
+
+When implementing your solution, consider the following tips to keep your code optimized and idiomatic:
+
+- **Trim the Input Once**: Use [`trim()`][trim] only once at the start to remove any unnecessary whitespace.
+- **Use Built-in Methods**: For checking if a message is a question, prefer [`endsWith("?")`][endswith] instead of manually checking the last character.
+- **Single Determinations**: Use variables for `questioning` and `shouting` rather than calling these checks multiple times to improve efficiency.
+- **DRY Code**: Avoid duplicating code by combining the logic for determining a shout and a question when handling shouted questions. Following the [DRY][dry] principle helps maintain clear and maintainable code.
+- **Return Statements**: An early return in an `if` statement eliminates the need for additional `else` blocks, making the code more readable.
+- **Curly Braces**: While optional for single-line statements, some teams may require them for readability and consistency.
+
+## Approach: method-based `if` statements
+
+```java
+class Bob {
+ String hey(String input) {
+ var inputTrimmed = input.trim();
+
+ if (isSilent(inputTrimmed)) {
+ return "Fine. Be that way!";
+ }
+ if (isShouting(inputTrimmed) && isQuestioning(inputTrimmed)) {
+ return "Calm down, I know what I'm doing!";
+ }
+ if (isShouting(inputTrimmed)) {
+ return "Whoa, chill out!";
+ }
+ if (isQuestioning(inputTrimmed)) {
+ return "Sure.";
+ }
+
+ return "Whatever.";
+ }
+
+ private boolean isShouting(String input) {
+ return input.chars()
+ .anyMatch(Character::isLetter) &&
+ input.chars()
+ .filter(Character::isLetter)
+ .allMatch(Character::isUpperCase);
+ }
+
+ private boolean isQuestioning(String input) {
+ return input.endsWith("?");
+ }
+
+ private boolean isSilent(String input) {
+ return input.length() == 0;
+ }
+}
+```
+
+This approach defines helper methods for each type of message—silent, shouting, and questioning—to keep each condition clean and easily testable.
+For more details, refer to the [method-based `if` Statements Approach][approach-method-if].
+
+## Approach: variable-based `if` statements
+
+```java
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+class Bob {
+
+ final private static Pattern isAlpha = Pattern.compile("[a-zA-Z]");
+ final private static Predicate < String > isShout = msg -> isAlpha.matcher(msg).find() && msg == msg.toUpperCase();
+
+ public String hey(String message) {
+ var speech = message.trim();
+ if (speech.isEmpty()) {
+ return "Fine. Be that way!";
+ }
+ var questioning = speech.endsWith("?");
+ var shouting = isShout.test(speech);
+ if (questioning) {
+ if (shouting) {
+ return "Calm down, I know what I'm doing!";
+ }
+ return "Sure.";
+ }
+ if (shouting) {
+ return "Whoa, chill out!";
+ }
+ return "Whatever.";
+ }
+}
+```
+
+This approach uses variables to avoid rechecking whether Bob is silent, shouting or questioning.
+For more details, refer to the [variable-based `if` Statements Approach][approach-variable-if].
+
+## Approach: answer array
+
+```java
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+class Bob {
+ final private static String[] answers = {
+ "Whatever.",
+ "Sure.",
+ "Whoa, chill out!",
+ "Calm down, I know what I'm doing!"
+ };
+ final private static Pattern isAlpha = Pattern.compile("[a-zA-Z]");
+ final private static Predicate < String > isShout = msg -> isAlpha.matcher(msg).find() && msg == msg.toUpperCase();
+
+ public String hey(String message) {
+ var speech = message.trim();
+ if (speech.isEmpty()) {
+ return "Fine. Be that way!";
+ }
+ var questioning = speech.endsWith("?") ? 1 : 0;
+ var shouting = isShout.test(speech) ? 2 : 0;
+ return answers[questioning + shouting];
+ }
+}
+```
+
+This approach uses an array of answers and calculates the appropriate index based on flags for shouting and questioning.
+For more details, refer to the [Answer Array Approach][approach-answer-array].
+
+## Which Approach to Use?
+
+The choice between the **Method-Based `if` Statements Approach**, **Variable-Based `if` Statements Approach**, and the **Answer Array Approach** depends on readability, maintainability, and efficiency:
+
+- **Method-Based `if` Statements Approach**: This is clear and easy to follow but checks conditions multiple times, potentially affecting performance. Storing results in variables like `questioning` and `shouting` can improve efficiency but may reduce clarity slightly.
+- **Variable-Based `if` Statements Approach**: This approach can be more efficient by avoiding redundant checks, but its nested structure can reduce readability and maintainability.
+- **Answer Array Approach**: Efficient and compact, this method uses an array of responses based on flags for questioning and shouting. However, it may be less intuitive and harder to modify if more responses are needed.
+
+Each approach offers a balance between readability and performance, with trade-offs in flexibility and clarity.
+
+[trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim()
+[endswith]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#endsWith(java.lang.String)
+[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
+[approach-method-if]: https://exercism.org/tracks/java/exercises/bob/approaches/method-based-if-statements
+[approach-variable-if]: https://exercism.org/tracks/java/exercises/bob/approaches/variable-based-if-statements
+[approach-answer-array]: https://exercism.org/tracks/java/exercises/bob/approaches/answer-array
diff --git a/exercises/practice/bob/.approaches/method-based-if-statements/content.md b/exercises/practice/bob/.approaches/method-based-if-statements/content.md
new file mode 100644
index 000000000..93c95160b
--- /dev/null
+++ b/exercises/practice/bob/.approaches/method-based-if-statements/content.md
@@ -0,0 +1,99 @@
+# Method-Based `if` statements
+
+```java
+class Bob {
+ String hey(String input) {
+ var inputTrimmed = input.trim();
+
+ if (isSilent(inputTrimmed)) {
+ return "Fine. Be that way!";
+ }
+ if (isShouting(inputTrimmed) && isQuestioning(inputTrimmed)) {
+ return "Calm down, I know what I'm doing!";
+ }
+ if (isShouting(inputTrimmed)) {
+ return "Whoa, chill out!";
+ }
+ if (isQuestioning(inputTrimmed)) {
+ return "Sure.";
+ }
+
+ return "Whatever.";
+ }
+
+ private boolean isShouting(String input) {
+ return input.chars()
+ .anyMatch(Character::isLetter) &&
+ input.chars()
+ .filter(Character::isLetter)
+ .allMatch(Character::isUpperCase);
+ }
+
+ private boolean isQuestioning(String input) {
+ return input.endsWith("?");
+ }
+
+ private boolean isSilent(String input) {
+ return input.length() == 0;
+ }
+}
+```
+
+In this approach, the different conditions for Bob’s responses are separated into dedicated private methods within the `Bob` class.
+This method-based approach improves readability and modularity by organizing each condition check into its own method, making the main response method easier to understand and maintain.
+
+## Explanation
+
+This approach simplifies the main method `hey` by breaking down each response condition into helper methods:
+
+### Trimming the Input
+
+The `input` is trimmed using the `String` [`trim()`][trim] method to remove any leading or trailing whitespace.
+This helps to accurately detect if the input is empty and should prompt a `"Fine. Be that way!"` response.
+
+~~~~exercism/caution
+Note that a `null` `string` would be different from a `String` of all whitespace.
+A `null` `String` would throw a `NullPointerException` if `trim()` were applied to it.
+~~~~
+
+### Delegating to Helper Methods
+
+Each condition is evaluated using the following helper methods:
+
+1. **`isSilent`**: Checks if the trimmed input has no characters.
+2. **`isShouting`**: Checks if the input is all uppercase and contains at least one alphabetic character, indicating shouting.
+3. **`isQuestioning`**: Verifies if the trimmed input ends with a question mark.
+
+This modular approach keeps each condition encapsulated, enhancing code clarity.
+
+### Order of Checks
+
+The order of checks within `hey` is important:
+
+1. Silence is evaluated first, as it requires an immediate response.
+2. Shouted questions take precedence over individual checks for shouting and questioning.
+3. Shouting comes next, requiring its response if not combined with a question.
+4. Questioning (a non-shouted question) is checked afterward.
+
+This ordering ensures that Bob’s response matches the expected behavior without redundancy.
+
+## Shortening
+
+When the body of an `if` statement is a single line, both the test expression and the body _could_ be put on the same line, like so:
+
+```java
+if (isSilent(inputTrimmed)) return "Fine. Be that way!";
+```
+
+or the body _could_ be put on a separate line without curly braces:
+
+```java
+if (isSilent(inputTrimmed))
+ return "Fine. Be that way!";
+```
+
+However, the [Java Coding Conventions][coding-conventions] advise always using curly braces for `if` statements, which helps to avoid errors.
+Your team may choose to overrule them at its own risk.
+
+[trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim()
+[coding-conventions]: https://www.oracle.com/java/technologies/javase/codeconventions-statements.html#449
diff --git a/exercises/practice/bob/.approaches/method-based-if-statements/snippet.txt b/exercises/practice/bob/.approaches/method-based-if-statements/snippet.txt
new file mode 100644
index 000000000..4abcfa820
--- /dev/null
+++ b/exercises/practice/bob/.approaches/method-based-if-statements/snippet.txt
@@ -0,0 +1,8 @@
+if (isSilent(inputTrimmed))
+ return "Fine. Be that way!";
+if (isShouting(inputTrimmed) && isQuestioning(inputTrimmed))
+ return "Calm down, I know what I'm doing!";
+if (isShouting(inputTrimmed))
+ return "Whoa, chill out!";
+if (isQuestioning(inputTrimmed))
+ return "Sure.";
\ No newline at end of file
diff --git a/exercises/practice/bob/.approaches/variable-based-if-statements/content.md b/exercises/practice/bob/.approaches/variable-based-if-statements/content.md
new file mode 100644
index 000000000..d20648458
--- /dev/null
+++ b/exercises/practice/bob/.approaches/variable-based-if-statements/content.md
@@ -0,0 +1,88 @@
+# Variable-Based `if` statements
+
+```java
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+class Bob {
+
+ final private static Pattern isAlpha = Pattern.compile("[a-zA-Z]");
+ final private static Predicate < String > isShout = msg -> isAlpha.matcher(msg).find() && msg == msg.toUpperCase();
+
+ public String hey(String message) {
+ var speech = message.trim();
+ if (speech.isEmpty()) {
+ return "Fine. Be that way!";
+ }
+ var questioning = speech.endsWith("?");
+ var shouting = isShout.test(speech);
+ if (questioning) {
+ if (shouting) {
+ return "Calm down, I know what I'm doing!";
+ }
+ return "Sure.";
+ }
+ if (shouting) {
+ return "Whoa, chill out!";
+ }
+ return "Whatever.";
+ }
+}
+```
+
+In this approach you have a series of `if` statements using the private methods to evaluate the conditions.
+As soon as the right condition is found, the correct response is returned.
+
+Note that there are no `else if` or `else` statements.
+If an `if` statement can return, then an `else if` or `else` is not needed.
+Execution will either return or will continue to the next statement anyway.
+
+The `String` [`trim()`][trim] method is applied to the input to eliminate any whitespace at either end of the input.
+If the string has no characters left, it returns the response for saying nothing.
+
+~~~~exercism/caution
+Note that a `null` `string` would be different from a `String` of all whitespace.
+A `null` `String` would throw a `NullPointerException` if `trim()` were applied to it.
+~~~~
+
+A [Pattern][pattern] is defined to look for at least one English alphabetic character.
+
+The first half of the `isShout` [Predicate][predicate]
+
+```java
+isAlpha.matcher(msg).find() && msg == msg.toUpperCase();
+```
+
+is constructed from the `Pattern` [`matcher()`][matcher-method] method and the [`Matcher`][matcher] [`find()`][find] method
+to ensure there is at least one letter character in the `String`.
+This is because the second half of the condition tests that the uppercased input is the same as the input.
+If the input were only `"123"` it would equal itself uppercased, but without letters it would not be a shout.
+
+A question is determined by use of the [`endsWith()`][endswith] method to see if the input ends with a question mark.
+
+## Shortening
+
+When the body of an `if` statement is a single line, both the test expression and the body _could_ be put on the same line, like so
+
+```java
+if (speech.isEmpty()) return "Fine. Be that way!";
+```
+
+or the body _could_ be put on a separate line without curly braces
+
+```java
+if (speech.isEmpty())
+ return "Fine. Be that way!";
+```
+
+However, the [Java Coding Conventions][coding-conventions] advise to always use curly braces for `if` statements, which helps to avoid errors.
+Your team may choose to overrule them at its own risk.
+
+[trim]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#trim()
+[pattern]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html
+[predicate]: https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html
+[matcher]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html
+[matcher-method]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html#matcher-java.lang.CharSequence-
+[find]: https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html#find--
+[endswith]: https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#endsWith(java.lang.String)
+[coding-conventions]: https://www.oracle.com/java/technologies/javase/codeconventions-statements.html#449
diff --git a/exercises/practice/bob/.approaches/variable-based-if-statements/snippet.txt b/exercises/practice/bob/.approaches/variable-based-if-statements/snippet.txt
new file mode 100644
index 000000000..57b3f38a4
--- /dev/null
+++ b/exercises/practice/bob/.approaches/variable-based-if-statements/snippet.txt
@@ -0,0 +1,8 @@
+if (questioning) {
+ if (shouting)
+ return "Calm down, I know what I'm doing!";
+ return "Sure.";
+}
+if (shouting)
+ return "Whoa, chill out!";
+return "Whatever.";
diff --git a/exercises/practice/bob/.docs/instructions.append.md b/exercises/practice/bob/.docs/instructions.append.md
deleted file mode 100644
index 0909bb1c4..000000000
--- a/exercises/practice/bob/.docs/instructions.append.md
+++ /dev/null
@@ -1,60 +0,0 @@
-# Instructions append
-
-Since this exercise has difficulty 5, it doesn't come with any starter implementation.
-This is so that you get to practice creating classes and methods which is an important part of programming in Java.
-It does mean that when you first try to run the tests, they won't compile.
-They will give you an error similar to:
-```
- path-to-exercism-dir\exercism\java\name-of-exercise\src\test\java\ExerciseClassNameTest.java:14: error: cannot find symbol
- ExerciseClassName exerciseClassName = new ExerciseClassName();
- ^
- symbol: class ExerciseClassName
- location: class ExerciseClassNameTest
-```
-This error occurs because the test refers to a class that hasn't been created yet (`ExerciseClassName`).
-To resolve the error you need to add a file matching the class name in the error to the `src/main/java` directory.
-For example, for the error above, you would add a file called `ExerciseClassName.java`.
-
-When you try to run the tests again, you will get slightly different errors.
-You might get an error similar to:
-```
- constructor ExerciseClassName in class ExerciseClassName cannot be applied to given types;
- ExerciseClassName exerciseClassName = new ExerciseClassName("some argument");
- ^
- required: no arguments
- found: String
- reason: actual and formal argument lists differ in length
-```
-This error means that you need to add a [constructor](https://docs.oracle.com/javase/tutorial/java/javaOO/constructors.html) to your new class.
-If you don't add a constructor, Java will add a default one for you.
-This default constructor takes no arguments.
-So if the tests expect your class to have a constructor which takes arguments, then you need to create this constructor yourself.
-In the example above you could add:
-```
-ExerciseClassName(String input) {
-
-}
-```
-That should make the error go away, though you might need to add some more code to your constructor to make the test pass!
-
-You might also get an error similar to:
-```
- error: cannot find symbol
- assertEquals(expectedOutput, exerciseClassName.someMethod());
- ^
- symbol: method someMethod()
- location: variable exerciseClassName of type ExerciseClassName
-```
-This error means that you need to add a method called `someMethod` to your new class.
-In the example above, you would add:
-```
-String someMethod() {
- return "";
-}
-```
-Make sure the return type matches what the test is expecting.
-You can find out which return type it should have by looking at the type of object it's being compared to in the tests.
-Or you could set your method to return some random type (e.g. `void`), and run the tests again.
-The new error should tell you which type it's expecting.
-
-After having resolved these errors, you should be ready to start making the tests pass!
diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md
index edddb1413..bb702f7bb 100644
--- a/exercises/practice/bob/.docs/instructions.md
+++ b/exercises/practice/bob/.docs/instructions.md
@@ -1,16 +1,19 @@
# Instructions
-Bob is a lackadaisical teenager. In conversation, his responses are very limited.
+Your task is to determine what Bob will reply to someone when they say something to him or ask him a question.
-Bob answers 'Sure.' if you ask him a question, such as "How are you?".
+Bob only ever answers one of five things:
-He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals).
-
-He answers 'Calm down, I know what I'm doing!' if you yell a question at him.
-
-He says 'Fine. Be that way!' if you address him without actually saying
-anything.
-
-He answers 'Whatever.' to anything else.
-
-Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English.
+- **"Sure."**
+ This is his response if you ask him a question, such as "How are you?"
+ The convention used for questions is that it ends with a question mark.
+- **"Whoa, chill out!"**
+ This is his answer if you YELL AT HIM.
+ The convention used for yelling is ALL CAPITAL LETTERS.
+- **"Calm down, I know what I'm doing!"**
+ This is what he says if you yell a question at him.
+- **"Fine. Be that way!"**
+ This is how he responds to silence.
+ The convention used for silence is nothing, or various combinations of whitespace characters.
+- **"Whatever."**
+ This is what he answers to anything else.
diff --git a/exercises/practice/bob/.docs/introduction.md b/exercises/practice/bob/.docs/introduction.md
new file mode 100644
index 000000000..ea4a80776
--- /dev/null
+++ b/exercises/practice/bob/.docs/introduction.md
@@ -0,0 +1,10 @@
+# Introduction
+
+Bob is a [lackadaisical][] teenager.
+He likes to think that he's very cool.
+And he definitely doesn't get excited about things.
+That wouldn't be cool.
+
+When people talk to him, his responses are pretty limited.
+
+[lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical
diff --git a/exercises/practice/bob/.meta/config.json b/exercises/practice/bob/.meta/config.json
index c56ac2df3..a86461574 100644
--- a/exercises/practice/bob/.meta/config.json
+++ b/exercises/practice/bob/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.",
"authors": [
"sit"
],
@@ -8,6 +7,7 @@
"austinlyons",
"c-thornton",
"FridaTveit",
+ "jagdish-15",
"jmrunkle",
"jtigger",
"kytrinyx",
@@ -35,8 +35,12 @@
],
"example": [
".meta/src/reference/java/Bob.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
+ "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.",
"source": "Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial.",
- "source_url": "http://pine.fm/LearnToProgram/?Chapter=06"
+ "source_url": "https://pine.fm/LearnToProgram/?Chapter=06"
}
diff --git a/exercises/practice/bob/.meta/tests.toml b/exercises/practice/bob/.meta/tests.toml
index 630485579..5299e2895 100644
--- a/exercises/practice/bob/.meta/tests.toml
+++ b/exercises/practice/bob/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
[e162fead-606f-437a-a166-d051915cea8e]
description = "stating something"
@@ -64,6 +71,7 @@ description = "alternate silence"
[66953780-165b-4e7e-8ce3-4bcb80b6385a]
description = "multiple line question"
+include = false
[5371ef75-d9ea-4103-bcfa-2da973ddec1b]
description = "starting with whitespace"
@@ -76,3 +84,7 @@ description = "other whitespace"
[12983553-8601-46a8-92fa-fcaa3bc4a2a0]
description = "non-question ending with whitespace"
+
+[2c7278ac-f955-4eb4-bf8f-e33eb4116a15]
+description = "multiple line question"
+reimplements = "66953780-165b-4e7e-8ce3-4bcb80b6385a"
diff --git a/exercises/practice/bob/.meta/version b/exercises/practice/bob/.meta/version
deleted file mode 100644
index 88c5fb891..000000000
--- a/exercises/practice/bob/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.4.0
diff --git a/exercises/practice/bob/build.gradle b/exercises/practice/bob/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/bob/build.gradle
+++ b/exercises/practice/bob/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/bob/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/bob/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/bob/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/bob/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/bob/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/bob/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/bob/gradlew b/exercises/practice/bob/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/bob/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/bob/gradlew.bat b/exercises/practice/bob/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/bob/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/bob/src/main/java/Bob.java b/exercises/practice/bob/src/main/java/Bob.java
index 6178f1beb..75d18c343 100644
--- a/exercises/practice/bob/src/main/java/Bob.java
+++ b/exercises/practice/bob/src/main/java/Bob.java
@@ -1,10 +1,7 @@
-/*
+class Bob {
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+ String hey(String input) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-Please remove this comment when submitting your solution.
-
-*/
+}
\ No newline at end of file
diff --git a/exercises/practice/bob/src/test/java/BobTest.java b/exercises/practice/bob/src/test/java/BobTest.java
index 4aa2002eb..756fb93f8 100644
--- a/exercises/practice/bob/src/test/java/BobTest.java
+++ b/exercises/practice/bob/src/test/java/BobTest.java
@@ -1,6 +1,7 @@
-import org.junit.Test;
-import org.junit.Ignore;
-import org.junit.Before;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.BeforeEach;
import static org.assertj.core.api.Assertions.assertThat;
@@ -8,183 +9,208 @@
public class BobTest {
private Bob bob;
- @Before
+ @BeforeEach
public void setUp() {
bob = new Bob();
}
@Test
+ @DisplayName("stating something")
public void saySomething() {
assertThat(bob.hey("Tom-ay-to, tom-aaaah-to."))
.isEqualTo("Whatever.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("shouting")
public void shouting() {
assertThat(bob.hey("WATCH OUT!"))
.isEqualTo("Whoa, chill out!");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("shouting gibberish")
public void shoutingGibberish() {
assertThat(bob.hey("FCECDFCAAB"))
.isEqualTo("Whoa, chill out!");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("asking a question")
public void askingAQuestion() {
assertThat(bob.hey("Does this cryogenic chamber make me look fat?"))
.isEqualTo("Sure.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("asking a numeric question")
public void askingANumericQuestion() {
assertThat(bob.hey("You are, what, like 15?"))
.isEqualTo("Sure.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("asking gibberish")
public void askingGibberish() {
assertThat(bob.hey("fffbbcbeab?"))
.isEqualTo("Sure.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("talking forcefully")
public void talkingForcefully() {
assertThat(bob.hey("Hi there!"))
.isEqualTo("Whatever.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("using acronyms in regular speech")
public void usingAcronymsInRegularSpeech() {
assertThat(bob.hey("It's OK if you don't want to go work for NASA."))
.isEqualTo("Whatever.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("forceful question")
public void forcefulQuestions() {
assertThat(bob.hey("WHAT'S GOING ON?"))
.isEqualTo("Calm down, I know what I'm doing!");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("shouting numbers")
public void shoutingNumbers() {
assertThat(bob.hey("1, 2, 3 GO!"))
.isEqualTo("Whoa, chill out!");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("no letters")
public void onlyNumbers() {
assertThat(bob.hey("1, 2, 3"))
.isEqualTo("Whatever.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("question with no letters")
public void questionWithOnlyNumbers() {
assertThat(bob.hey("4?"))
.isEqualTo("Sure.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("shouting with special characters")
public void shoutingWithSpecialCharacters() {
assertThat(bob.hey("ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!"))
.isEqualTo("Whoa, chill out!");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("shouting with no exclamation mark")
public void shoutingWithNoExclamationMark() {
assertThat(bob.hey("I HATE THE DENTIST"))
.isEqualTo("Whoa, chill out!");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("statement containing question mark")
public void statementContainingQuestionMark() {
assertThat(bob.hey("Ending with ? means a question."))
.isEqualTo("Whatever.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("non-letters with question")
public void nonLettersWithQuestion() {
assertThat(bob.hey(":) ?"))
.isEqualTo("Sure.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("prattling on")
public void prattlingOn() {
assertThat(bob.hey("Wait! Hang on. Are you going to be OK?"))
.isEqualTo("Sure.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("silence")
public void silence() {
assertThat(bob.hey(""))
.isEqualTo("Fine. Be that way!");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("prolonged silence")
public void prolongedSilence() {
assertThat(bob.hey(" "))
.isEqualTo("Fine. Be that way!");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("alternate silence")
public void alternateSilence() {
assertThat(bob.hey("\t\t\t\t\t\t\t\t\t\t"))
.isEqualTo("Fine. Be that way!");
}
- @Ignore("Remove to run test")
- @Test
- public void multipleLineQuestion() {
- assertThat(bob.hey("\nDoes this cryogenic chamber make me look fat?\nNo."))
- .isEqualTo("Whatever.");
- }
-
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("starting with whitespace")
public void startingWithWhitespace() {
assertThat(bob.hey(" hmmmmmmm..."))
.isEqualTo("Whatever.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("ending with whitespace")
public void endingWithWhiteSpace() {
assertThat(bob.hey("Okay if like my spacebar quite a bit? "))
.isEqualTo("Sure.");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("other whitespace")
public void otherWhiteSpace() {
assertThat(bob.hey("\n\r \t"))
.isEqualTo("Fine. Be that way!");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("non-question ending with whitespace")
public void nonQuestionEndingWithWhiteSpace() {
assertThat(bob.hey("This is a statement ending with whitespace "))
.isEqualTo("Whatever.");
}
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("multiple line question")
+ public void multipleLineQuestion() {
+ assertThat(bob.hey("\nDoes this cryogenic chamber make\n me look fat?"))
+ .isEqualTo("Sure.");
+ }
+
}
diff --git a/exercises/practice/book-store/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md
index 8ec0a7ba2..54403f17b 100644
--- a/exercises/practice/book-store/.docs/instructions.md
+++ b/exercises/practice/book-store/.docs/instructions.md
@@ -1,12 +1,10 @@
# Instructions
-To try and encourage more sales of different books from a popular 5 book
-series, a bookshop has decided to offer discounts on multiple book purchases.
+To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts on multiple book purchases.
One copy of any of the five books costs $8.
-If, however, you buy two different books, you get a 5%
-discount on those two books.
+If, however, you buy two different books, you get a 5% discount on those two books.
If you buy 3 different books, you get a 10% discount.
@@ -14,14 +12,9 @@ If you buy 4 different books, you get a 20% discount.
If you buy all 5, you get a 25% discount.
-Note: that if you buy four books, of which 3 are
-different titles, you get a 10% discount on the 3 that
-form part of a set, but the fourth book still costs $8.
+Note that if you buy four books, of which 3 are different titles, you get a 10% discount on the 3 that form part of a set, but the fourth book still costs $8.
-Your mission is to write a piece of code to calculate the
-price of any conceivable shopping basket (containing only
-books of the same series), giving as big a discount as
-possible.
+Your mission is to write code to calculate the price of any conceivable shopping basket (containing only books of the same series), giving as big a discount as possible.
For example, how much does this basket of books cost?
@@ -33,36 +26,36 @@ For example, how much does this basket of books cost?
One way of grouping these 8 books is:
-- 1 group of 5 --> 25% discount (1st,2nd,3rd,4th,5th)
-- +1 group of 3 --> 10% discount (1st,2nd,3rd)
+- 1 group of 5 (1st, 2nd,3rd, 4th, 5th)
+- 1 group of 3 (1st, 2nd, 3rd)
This would give a total of:
- 5 books at a 25% discount
-- +3 books at a 10% discount
+- 3 books at a 10% discount
Resulting in:
-- 5 x (8 - 2.00) == 5 x 6.00 == $30.00
-- +3 x (8 - 0.80) == 3 x 7.20 == $21.60
+- 5 × (100% - 25%) × $8 = 5 × $6.00 = $30.00, plus
+- 3 × (100% - 10%) × $8 = 3 × $7.20 = $21.60
-For a total of $51.60
+Which equals $51.60.
However, a different way to group these 8 books is:
-- 1 group of 4 books --> 20% discount (1st,2nd,3rd,4th)
-- +1 group of 4 books --> 20% discount (1st,2nd,3rd,5th)
+- 1 group of 4 books (1st, 2nd, 3rd, 4th)
+- 1 group of 4 books (1st, 2nd, 3rd, 5th)
This would give a total of:
- 4 books at a 20% discount
-- +4 books at a 20% discount
+- 4 books at a 20% discount
Resulting in:
-- 4 x (8 - 1.60) == 4 x 6.40 == $25.60
-- +4 x (8 - 1.60) == 4 x 6.40 == $25.60
+- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60, plus
+- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60
-For a total of $51.20
+Which equals $51.20.
And $51.20 is the price with the biggest discount.
diff --git a/exercises/practice/book-store/.meta/config.json b/exercises/practice/book-store/.meta/config.json
index e6e06fc23..229853057 100644
--- a/exercises/practice/book-store/.meta/config.json
+++ b/exercises/practice/book-store/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts of multiple-book purchases.",
"authors": [
"Twisti"
],
@@ -31,8 +30,12 @@
],
"example": [
".meta/src/reference/java/BookStore.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
+ "blurb": "To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts of multiple-book purchases.",
"source": "Inspired by the harry potter kata from Cyber-Dojo.",
- "source_url": "http://cyber-dojo.org"
+ "source_url": "https://cyber-dojo.org"
}
diff --git a/exercises/practice/book-store/.meta/tests.toml b/exercises/practice/book-store/.meta/tests.toml
index 9c7bc8d08..4b7ce98be 100644
--- a/exercises/practice/book-store/.meta/tests.toml
+++ b/exercises/practice/book-store/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
[17146bd5-2e80-4557-ab4c-05632b6b0d01]
description = "Only a single book"
@@ -33,16 +40,25 @@ description = "Two groups of four is cheaper than groups of five and three"
description = "Group of four plus group of two is cheaper than two groups of three"
[68ea9b78-10ad-420e-a766-836a501d3633]
-description = "Two each of first 4 books and 1 copy each of rest"
+description = "Two each of first four books and one copy each of rest"
[c0a779d5-a40c-47ae-9828-a340e936b866]
description = "Two copies of each book"
[18fd86fe-08f1-4b68-969b-392b8af20513]
-description = "Three copies of first book and 2 each of remaining"
+description = "Three copies of first book and two each of remaining"
[0b19a24d-e4cf-4ec8-9db2-8899a41af0da]
-description = "Three each of first 2 books and 2 each of remaining books"
+description = "Three each of first two books and two each of remaining books"
[bb376344-4fb2-49ab-ab85-e38d8354a58d]
description = "Four groups of four are cheaper than two groups each of five and three"
+
+[5260ddde-2703-4915-b45a-e54dbbac4303]
+description = "Check that groups of four are created properly even when there are more groups of three than groups of five"
+
+[b0478278-c551-4747-b0fc-7e0be3158b1f]
+description = "One group of one and four is cheaper than one group of two and three"
+
+[cf868453-6484-4ae1-9dfc-f8ee85bbde01]
+description = "One group of one and two plus three groups of four is cheaper than one group of each size"
diff --git a/exercises/practice/book-store/.meta/version b/exercises/practice/book-store/.meta/version
deleted file mode 100644
index 88c5fb891..000000000
--- a/exercises/practice/book-store/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.4.0
diff --git a/exercises/practice/book-store/build.gradle b/exercises/practice/book-store/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/book-store/build.gradle
+++ b/exercises/practice/book-store/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/book-store/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/book-store/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/book-store/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/book-store/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/book-store/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/book-store/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/book-store/gradlew b/exercises/practice/book-store/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/book-store/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/book-store/gradlew.bat b/exercises/practice/book-store/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/book-store/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/book-store/src/main/java/BookStore.java b/exercises/practice/book-store/src/main/java/BookStore.java
index 6178f1beb..f0f2e09fc 100644
--- a/exercises/practice/book-store/src/main/java/BookStore.java
+++ b/exercises/practice/book-store/src/main/java/BookStore.java
@@ -1,10 +1,9 @@
-/*
+import java.util.List;
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+class BookStore {
-Please remove this comment when submitting your solution.
+ double calculateBasketCost(List books) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-*/
+}
\ No newline at end of file
diff --git a/exercises/practice/book-store/src/test/java/BookStoreTest.java b/exercises/practice/book-store/src/test/java/BookStoreTest.java
index bf298a207..bbcbbcfb9 100644
--- a/exercises/practice/book-store/src/test/java/BookStoreTest.java
+++ b/exercises/practice/book-store/src/test/java/BookStoreTest.java
@@ -1,13 +1,14 @@
-import static org.assertj.core.api.Assertions.assertThat;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Arrays;
-import org.assertj.core.api.Assertions;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
+import static org.assertj.core.api.Assertions.assertThat;
public class BookStoreTest {
@@ -17,127 +18,171 @@ public class BookStoreTest {
private BookStore bookStore;
- @Before
+ @BeforeEach
public void setUp() {
bookStore = new BookStore();
}
@Test
+ @DisplayName("Only a single book")
public void onlyASingleBook() {
List books = Collections.singletonList(1);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(8.00, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(8.00, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Two of the same book")
public void twoOfSameBook() {
List books = Arrays.asList(2, 2);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(16.00, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(16.00, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Empty basket")
public void emptyBasket() {
List books = Collections.emptyList();
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(0.00, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(0.00, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Two different books")
public void twoDifferentBooks() {
List books = Arrays.asList(1, 2);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(15.20, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(15.20, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Three different books")
public void threeDifferentBooks() {
List books = Arrays.asList(1, 2, 3);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(21.60, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(21.60, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Four different books")
public void fourDifferentBooks() {
List books = Arrays.asList(1, 2, 3, 4);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(25.60, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(25.60, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Five different books")
public void fiveDifferentBooks() {
List books = Arrays.asList(1, 2, 3, 4, 5);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(30.00, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(30.00, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Two groups of four is cheaper than group of five plus group of three")
public void twoGroupsOfFourIsCheaperThanGroupOfFivePlusGroupOfThree() {
List books = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 5);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(51.20, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(51.20, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Two groups of four is cheaper than groups of five and three")
public void twoGroupsOfFourIsCheaperThanGroupsOfFiveAndThree() {
List books = Arrays.asList(1, 1, 2, 3, 4, 4, 5, 5);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(51.20, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(51.20, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Group of four plus group of two is cheaper than two groups of three")
public void groupOfFourPlusGroupOfTwoIsCheaperThanTwoGroupsOfThree() {
List books = Arrays.asList(1, 1, 2, 2, 3, 4);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(40.80, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(40.80, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Two each of first four books and one copy each of rest")
public void twoEachOfFirst4BooksAnd1CopyEachOfRest() {
List books = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(55.60, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(55.60, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Two copies of each book")
public void twoCopiesOfEachBook() {
List books = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(60.00, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(60.00, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void threeCopiesOfFirstBookAnd2EachOfRemaining() {
+ @DisplayName("Three copies of first book and two each of remaining")
+ public void threeCopiesOfFirstBookAndTwoEachOfRemaining() {
List books = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(68.00, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(68.00, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
- public void threeEachOFirst2BooksAnd2EachOfRemainingBooks() {
+ @DisplayName("Three each of first two books and two each of remaining books")
+ public void threeEachOfFirstTwoBooksAndTwoEachOfRemainingBooks() {
List books = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 2);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(75.20, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(75.20, Assertions.offset(EQUALITY_TOLERANCE));
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("Four groups of four are cheaper than two groups each of five and three")
public void fourGroupsOfFourAreCheaperThanTwoGroupsEachOfFiveAndThree() {
List books = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 5, 1, 1, 2, 2, 3, 3, 4, 5);
assertThat(bookStore.calculateBasketCost(books))
- .isCloseTo(102.4, Assertions.offset(EQUALITY_TOLERANCE));
+ .isCloseTo(102.4, Assertions.offset(EQUALITY_TOLERANCE));
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName(
+ "Check that groups of four are created properly even when there are more groups of three than groups of five"
+ )
+ public void groupsOfFourAreCreatedEvenWhenThereAreMoreGroupsOfThreeThanGroupsOfFive() {
+ List books = Arrays.asList(1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 5, 5);
+ assertThat(bookStore.calculateBasketCost(books))
+ .isCloseTo(145.6, Assertions.offset(EQUALITY_TOLERANCE));
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("One group of one and four is cheaper than one group of two and three")
+ public void oneGroupOfOneAndFourIsCheaperThanOneGroupOfTwoAndThree() {
+ List books = Arrays.asList(1, 1, 2, 3, 4);
+ assertThat(bookStore.calculateBasketCost(books))
+ .isCloseTo(33.6, Assertions.offset(EQUALITY_TOLERANCE));
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("One group of one and two plus three groups of four is cheaper than one group of each size")
+ public void oneGroupOfOneAndTwoPlusThreeGroupsOfFourIsCheaperThanOneGroupOfEachSize() {
+ List books = Arrays.asList(1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5);
+ assertThat(bookStore.calculateBasketCost(books))
+ .isCloseTo(100.0, Assertions.offset(EQUALITY_TOLERANCE));
}
}
diff --git a/exercises/practice/bottle-song/.docs/instructions.md b/exercises/practice/bottle-song/.docs/instructions.md
new file mode 100644
index 000000000..febdfc863
--- /dev/null
+++ b/exercises/practice/bottle-song/.docs/instructions.md
@@ -0,0 +1,57 @@
+# Instructions
+
+Recite the lyrics to that popular children's repetitive song: Ten Green Bottles.
+
+Note that not all verses are identical.
+
+```text
+Ten green bottles hanging on the wall,
+Ten green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be nine green bottles hanging on the wall.
+
+Nine green bottles hanging on the wall,
+Nine green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be eight green bottles hanging on the wall.
+
+Eight green bottles hanging on the wall,
+Eight green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be seven green bottles hanging on the wall.
+
+Seven green bottles hanging on the wall,
+Seven green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be six green bottles hanging on the wall.
+
+Six green bottles hanging on the wall,
+Six green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be five green bottles hanging on the wall.
+
+Five green bottles hanging on the wall,
+Five green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be four green bottles hanging on the wall.
+
+Four green bottles hanging on the wall,
+Four green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be three green bottles hanging on the wall.
+
+Three green bottles hanging on the wall,
+Three green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be two green bottles hanging on the wall.
+
+Two green bottles hanging on the wall,
+Two green bottles hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be one green bottle hanging on the wall.
+
+One green bottle hanging on the wall,
+One green bottle hanging on the wall,
+And if one green bottle should accidentally fall,
+There'll be no green bottles hanging on the wall.
+```
diff --git a/exercises/practice/bottle-song/.meta/config.json b/exercises/practice/bottle-song/.meta/config.json
new file mode 100644
index 000000000..d153fa587
--- /dev/null
+++ b/exercises/practice/bottle-song/.meta/config.json
@@ -0,0 +1,22 @@
+{
+ "authors": [
+ "kahgoh"
+ ],
+ "files": {
+ "solution": [
+ "src/main/java/BottleSong.java"
+ ],
+ "test": [
+ "src/test/java/BottleSongTest.java"
+ ],
+ "example": [
+ ".meta/src/reference/java/BottleSong.java"
+ ],
+ "invalidator": [
+ "build.gradle"
+ ]
+ },
+ "blurb": "Produce the lyrics to the popular children's repetitive song: Ten Green Bottles.",
+ "source": "Wikipedia",
+ "source_url": "https://en.wikipedia.org/wiki/Ten_Green_Bottles"
+}
diff --git a/exercises/practice/bottle-song/.meta/src/reference/java/BottleSong.java b/exercises/practice/bottle-song/.meta/src/reference/java/BottleSong.java
new file mode 100644
index 000000000..d143fd627
--- /dev/null
+++ b/exercises/practice/bottle-song/.meta/src/reference/java/BottleSong.java
@@ -0,0 +1,59 @@
+class BottleSong {
+ private String word(int number) {
+ return switch (number) {
+ case 2 -> "two";
+ case 3 -> "three";
+ case 4 -> "four";
+ case 5 -> "five";
+ case 6 -> "six";
+ case 7 -> "seven";
+ case 8 -> "eight";
+ case 9 -> "nine";
+ case 10 -> "ten";
+ default -> throw new IllegalArgumentException("Unrecognised number: " + Integer.toString(number));
+ };
+ }
+
+ private String verse(int number) {
+ return switch (number) {
+ case 1 ->
+ "One green bottle hanging on the wall,\n" +
+ "One green bottle hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be no green bottles hanging on the wall.\n";
+ case 2 ->
+ "Two green bottles hanging on the wall,\n" +
+ "Two green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be one green bottle hanging on the wall.\n";
+ default -> {
+ var codePoints = word(number).codePoints().toArray();
+ codePoints[0] = Character.toUpperCase(codePoints[0]);
+ var numberText = new String(codePoints, 0, codePoints.length);
+
+ var nextNumberText = word(number - 1);
+
+ yield String.format(
+ "%s green bottles hanging on the wall,\n" +
+ "%s green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be %s green bottles hanging on the wall.\n",
+ numberText, numberText, nextNumberText);
+ }
+ };
+ }
+
+ String recite(int startBottles, int takeDown) {
+ var stop = startBottles - takeDown + 1;
+ var output = new StringBuilder();
+
+ for (var i = startBottles; i >= stop; i--) {
+ if (i != startBottles) {
+ output.append("\n");
+ }
+ output.append(verse(i));
+ }
+
+ return output.toString();
+ }
+}
diff --git a/exercises/practice/bottle-song/.meta/tests.toml b/exercises/practice/bottle-song/.meta/tests.toml
new file mode 100644
index 000000000..1f6e40a37
--- /dev/null
+++ b/exercises/practice/bottle-song/.meta/tests.toml
@@ -0,0 +1,31 @@
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[d4ccf8fc-01dc-48c0-a201-4fbeb30f2d03]
+description = "verse -> single verse -> first generic verse"
+
+[0f0aded3-472a-4c64-b842-18d4f1f5f030]
+description = "verse -> single verse -> last generic verse"
+
+[f61f3c97-131f-459e-b40a-7428f3ed99d9]
+description = "verse -> single verse -> verse with 2 bottles"
+
+[05eadba9-5dbd-401e-a7e8-d17cc9baa8e0]
+description = "verse -> single verse -> verse with 1 bottle"
+
+[a4a28170-83d6-4dc1-bd8b-319b6abb6a80]
+description = "lyrics -> multiple verses -> first two verses"
+
+[3185d438-c5ac-4ce6-bcd3-02c9ff1ed8db]
+description = "lyrics -> multiple verses -> last three verses"
+
+[28c1584a-0e51-4b65-9ae2-fbc0bf4bbb28]
+description = "lyrics -> multiple verses -> all verses"
diff --git a/exercises/practice/bottle-song/build.gradle b/exercises/practice/bottle-song/build.gradle
new file mode 100644
index 000000000..d28f35dee
--- /dev/null
+++ b/exercises/practice/bottle-song/build.gradle
@@ -0,0 +1,25 @@
+plugins {
+ id "java"
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
+}
diff --git a/exercises/practice/bottle-song/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/bottle-song/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/bottle-song/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/bottle-song/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/bottle-song/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/bottle-song/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/bottle-song/gradlew b/exercises/practice/bottle-song/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/bottle-song/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/bottle-song/gradlew.bat b/exercises/practice/bottle-song/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/bottle-song/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/bottle-song/src/main/java/BottleSong.java b/exercises/practice/bottle-song/src/main/java/BottleSong.java
new file mode 100644
index 000000000..77397ee6e
--- /dev/null
+++ b/exercises/practice/bottle-song/src/main/java/BottleSong.java
@@ -0,0 +1,7 @@
+class BottleSong {
+
+ String recite(int startBottles, int takeDown) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
+
+}
\ No newline at end of file
diff --git a/exercises/practice/bottle-song/src/test/java/BottleSongTest.java b/exercises/practice/bottle-song/src/test/java/BottleSongTest.java
new file mode 100644
index 000000000..7d5ce0a97
--- /dev/null
+++ b/exercises/practice/bottle-song/src/test/java/BottleSongTest.java
@@ -0,0 +1,160 @@
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BottleSongTest {
+
+ private BottleSong bottleSong;
+
+ @BeforeEach
+ public void setup() {
+ bottleSong = new BottleSong();
+ }
+
+ @Test
+ @DisplayName("first generic verse")
+ public void firstGenericVerse() {
+ assertThat(bottleSong.recite(10, 1)).isEqualTo(
+ "Ten green bottles hanging on the wall,\n" +
+ "Ten green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be nine green bottles hanging on the wall.\n"
+ );
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("last generic verse")
+ public void lastGenericVerse() {
+ assertThat(bottleSong.recite(3, 1)).isEqualTo(
+ "Three green bottles hanging on the wall,\n" +
+ "Three green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be two green bottles hanging on the wall.\n"
+ );
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("verse with 2 bottles")
+ public void verseWithTwoBottles() {
+ assertThat(bottleSong.recite(2, 1)).isEqualTo(
+ "Two green bottles hanging on the wall,\n" +
+ "Two green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be one green bottle hanging on the wall.\n"
+ );
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("verse with 1 bottle")
+ public void verseWithOneBottle() {
+ assertThat(bottleSong.recite(1, 1)).isEqualTo(
+ "One green bottle hanging on the wall,\n" +
+ "One green bottle hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be no green bottles hanging on the wall.\n"
+ );
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("first two verses")
+ public void firstTwoVerses() {
+ assertThat(bottleSong.recite(10, 2)).isEqualTo(
+ "Ten green bottles hanging on the wall,\n" +
+ "Ten green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be nine green bottles hanging on the wall.\n" +
+ "\n" +
+ "Nine green bottles hanging on the wall,\n" +
+ "Nine green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be eight green bottles hanging on the wall.\n"
+ );
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("last three verses")
+ public void lastThreeVerses() {
+ assertThat(bottleSong.recite(3, 3)).isEqualTo(
+ "Three green bottles hanging on the wall,\n" +
+ "Three green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be two green bottles hanging on the wall.\n" +
+ "\n" +
+ "Two green bottles hanging on the wall,\n" +
+ "Two green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be one green bottle hanging on the wall.\n" +
+ "\n" +
+ "One green bottle hanging on the wall,\n" +
+ "One green bottle hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be no green bottles hanging on the wall.\n"
+ );
+ }
+
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("all verses")
+ public void allVerses() {
+ assertThat(bottleSong.recite(10, 10))
+ .isEqualTo(
+ "Ten green bottles hanging on the wall,\n" +
+ "Ten green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be nine green bottles hanging on the wall.\n" +
+ "\n" +
+ "Nine green bottles hanging on the wall,\n" +
+ "Nine green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be eight green bottles hanging on the wall.\n" +
+ "\n" +
+ "Eight green bottles hanging on the wall,\n" +
+ "Eight green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be seven green bottles hanging on the wall.\n" +
+ "\n" +
+ "Seven green bottles hanging on the wall,\n" +
+ "Seven green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be six green bottles hanging on the wall.\n" +
+ "\n" +
+ "Six green bottles hanging on the wall,\n" +
+ "Six green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be five green bottles hanging on the wall.\n" +
+ "\n" +
+ "Five green bottles hanging on the wall,\n" +
+ "Five green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be four green bottles hanging on the wall.\n" +
+ "\n" +
+ "Four green bottles hanging on the wall,\n" +
+ "Four green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be three green bottles hanging on the wall.\n" +
+ "\n" +
+ "Three green bottles hanging on the wall,\n" +
+ "Three green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be two green bottles hanging on the wall.\n" +
+ "\n" +
+ "Two green bottles hanging on the wall,\n" +
+ "Two green bottles hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be one green bottle hanging on the wall.\n" +
+ "\n" +
+ "One green bottle hanging on the wall,\n" +
+ "One green bottle hanging on the wall,\n" +
+ "And if one green bottle should accidentally fall,\n" +
+ "There'll be no green bottles hanging on the wall.\n"
+ );
+ }
+}
diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md
index be9b27faf..60ccad1b6 100644
--- a/exercises/practice/bowling/.docs/instructions.md
+++ b/exercises/practice/bowling/.docs/instructions.md
@@ -2,35 +2,30 @@
Score a bowling game.
-Bowling is a game where players roll a heavy ball to knock down pins
-arranged in a triangle. Write code to keep track of the score
-of a game of bowling.
+Bowling is a game where players roll a heavy ball to knock down pins arranged in a triangle.
+Write code to keep track of the score of a game of bowling.
## Scoring Bowling
-The game consists of 10 frames. A frame is composed of one or two ball
-throws with 10 pins standing at frame initialization. There are three
-cases for the tabulation of a frame.
+The game consists of 10 frames.
+A frame is composed of one or two ball throws with 10 pins standing at frame initialization.
+There are three cases for the tabulation of a frame.
-* An open frame is where a score of less than 10 is recorded for the
- frame. In this case the score for the frame is the number of pins
- knocked down.
+- An open frame is where a score of less than 10 is recorded for the frame.
+ In this case the score for the frame is the number of pins knocked down.
-* A spare is where all ten pins are knocked down by the second
- throw. The total value of a spare is 10 plus the number of pins
- knocked down in their next throw.
+- A spare is where all ten pins are knocked down by the second throw.
+ The total value of a spare is 10 plus the number of pins knocked down in their next throw.
-* A strike is where all ten pins are knocked down by the first
- throw. The total value of a strike is 10 plus the number of pins
- knocked down in the next two throws. If a strike is immediately
- followed by a second strike, then the value of the first strike
- cannot be determined until the ball is thrown one more time.
+- A strike is where all ten pins are knocked down by the first throw.
+ The total value of a strike is 10 plus the number of pins knocked down in the next two throws.
+ If a strike is immediately followed by a second strike, then the value of the first strike cannot be determined until the ball is thrown one more time.
Here is a three frame example:
-| Frame 1 | Frame 2 | Frame 3 |
-| :-------------: |:-------------:| :---------------------:|
-| X (strike) | 5/ (spare) | 9 0 (open frame) |
+| Frame 1 | Frame 2 | Frame 3 |
+| :--------: | :--------: | :--------------: |
+| X (strike) | 5/ (spare) | 9 0 (open frame) |
Frame 1 is (10 + 5 + 5) = 20
@@ -40,11 +35,11 @@ Frame 3 is (9 + 0) = 9
This means the current running total is 48.
-The tenth frame in the game is a special case. If someone throws a
-strike or a spare then they get a fill ball. Fill balls exist to
-calculate the total of the 10th frame. Scoring a strike or spare on
-the fill ball does not give the player more fill balls. The total
-value of the 10th frame is the total number of pins knocked down.
+The tenth frame in the game is a special case.
+If someone throws a spare or a strike then they get one or two fill balls respectively.
+Fill balls exist to calculate the total of the 10th frame.
+Scoring a strike or spare on the fill ball does not give the player more fill balls.
+The total value of the 10th frame is the total number of pins knocked down.
For a tenth frame of X1/ (strike and a spare), the total value is 20.
@@ -52,10 +47,10 @@ For a tenth frame of XXX (three strikes), the total value is 30.
## Requirements
-Write code to keep track of the score of a game of bowling. It should
-support two operations:
+Write code to keep track of the score of a game of bowling.
+It should support two operations:
-* `roll(pins : int)` is called each time the player rolls a ball. The
- argument is the number of pins knocked down.
-* `score() : int` is called only at the very end of the game. It
- returns the total score for that game.
+- `roll(pins : int)` is called each time the player rolls a ball.
+ The argument is the number of pins knocked down.
+- `score() : int` is called only at the very end of the game.
+ It returns the total score for that game.
diff --git a/exercises/practice/bowling/.meta/config.json b/exercises/practice/bowling/.meta/config.json
index f79bead41..e54a5afc7 100644
--- a/exercises/practice/bowling/.meta/config.json
+++ b/exercises/practice/bowling/.meta/config.json
@@ -1,5 +1,4 @@
{
- "blurb": "Score a bowling game.",
"authors": [],
"contributors": [
"aadityakulkarni",
@@ -27,9 +26,14 @@
"src/test/java/BowlingGameTest.java"
],
"example": [
- ".meta/src/reference/java/BowlingGame.java"
+ ".meta/src/reference/java/BowlingGame.java",
+ ".meta/src/reference/java/Frame.java"
+ ],
+ "invalidator": [
+ "build.gradle"
]
},
- "source": "The Bowling Game Kata at but UncleBob",
- "source_url": "http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata"
+ "blurb": "Score a bowling game.",
+ "source": "The Bowling Game Kata from UncleBob",
+ "source_url": "https://web.archive.org/web/20221001111000/http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata"
}
diff --git a/exercises/practice/bowling/.meta/tests.toml b/exercises/practice/bowling/.meta/tests.toml
index 963df175a..19042607d 100644
--- a/exercises/practice/bowling/.meta/tests.toml
+++ b/exercises/practice/bowling/.meta/tests.toml
@@ -1,6 +1,13 @@
-# This is an auto-generated file. Regular comments will be removed when this
-# file is regenerated. Regenerating will not touch any manually added keys,
-# so comments can be added in a "comment" key.
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
[656ae006-25c2-438c-a549-f338e7ec7441]
description = "should be able to score a game with all zeros"
@@ -38,6 +45,9 @@ description = "rolling a spare with the two roll bonus does not get a bonus roll
[576faac1-7cff-4029-ad72-c16bcada79b5]
description = "strikes with the two roll bonus do not get bonus rolls"
+[efb426ec-7e15-42e6-9b96-b4fca3ec2359]
+description = "last two strikes followed by only last bonus with non strike points"
+
[72e24404-b6c6-46af-b188-875514c0377b]
description = "a strike with the one roll bonus after a spare in the last frame does not get a bonus"
diff --git a/exercises/practice/bowling/.meta/version b/exercises/practice/bowling/.meta/version
deleted file mode 100644
index 26aaba0e8..000000000
--- a/exercises/practice/bowling/.meta/version
+++ /dev/null
@@ -1 +0,0 @@
-1.2.0
diff --git a/exercises/practice/bowling/build.gradle b/exercises/practice/bowling/build.gradle
index 8bd005d42..d28f35dee 100644
--- a/exercises/practice/bowling/build.gradle
+++ b/exercises/practice/bowling/build.gradle
@@ -1,24 +1,25 @@
-apply plugin: "java"
-apply plugin: "eclipse"
-apply plugin: "idea"
-
-// set default encoding to UTF-8
-compileJava.options.encoding = "UTF-8"
-compileTestJava.options.encoding = "UTF-8"
+plugins {
+ id "java"
+}
repositories {
- mavenCentral()
+ mavenCentral()
}
dependencies {
- testImplementation "junit:junit:4.13"
- testImplementation "org.assertj:assertj-core:3.15.0"
+ testImplementation platform("org.junit:junit-bom:5.10.0")
+ testImplementation "org.junit.jupiter:junit-jupiter"
+ testImplementation "org.assertj:assertj-core:3.25.1"
+
+ testRuntimeOnly "org.junit.platform:junit-platform-launcher"
}
test {
- testLogging {
- exceptionFormat = 'full'
- showStandardStreams = true
- events = ["passed", "failed", "skipped"]
- }
+ useJUnitPlatform()
+
+ testLogging {
+ exceptionFormat = "full"
+ showStandardStreams = true
+ events = ["passed", "failed", "skipped"]
+ }
}
diff --git a/exercises/practice/bowling/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/bowling/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..f8e1ee312
Binary files /dev/null and b/exercises/practice/bowling/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/exercises/practice/bowling/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/bowling/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..4d97ea344
--- /dev/null
+++ b/exercises/practice/bowling/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/exercises/practice/bowling/gradlew b/exercises/practice/bowling/gradlew
new file mode 100755
index 000000000..adff685a0
--- /dev/null
+++ b/exercises/practice/bowling/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/exercises/practice/bowling/gradlew.bat b/exercises/practice/bowling/gradlew.bat
new file mode 100644
index 000000000..c4bdd3ab8
--- /dev/null
+++ b/exercises/practice/bowling/gradlew.bat
@@ -0,0 +1,93 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/exercises/practice/bowling/src/main/java/BowlingGame.java b/exercises/practice/bowling/src/main/java/BowlingGame.java
index 6178f1beb..0a84e90da 100644
--- a/exercises/practice/bowling/src/main/java/BowlingGame.java
+++ b/exercises/practice/bowling/src/main/java/BowlingGame.java
@@ -1,10 +1,11 @@
-/*
+class BowlingGame {
-Since this exercise has a difficulty of > 4 it doesn't come
-with any starter implementation.
-This is so that you get to practice creating classes and methods
-which is an important part of programming in Java.
+ void roll(int pins) {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-Please remove this comment when submitting your solution.
+ int score() {
+ throw new UnsupportedOperationException("Delete this statement and write your own implementation.");
+ }
-*/
+}
\ No newline at end of file
diff --git a/exercises/practice/bowling/src/test/java/BowlingGameTest.java b/exercises/practice/bowling/src/test/java/BowlingGameTest.java
index 59b2852cc..a8fbcad96 100644
--- a/exercises/practice/bowling/src/test/java/BowlingGameTest.java
+++ b/exercises/practice/bowling/src/test/java/BowlingGameTest.java
@@ -1,9 +1,10 @@
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import org.junit.Ignore;
-import org.junit.Test;
-
public class BowlingGameTest {
private BowlingGame game = new BowlingGame();
@@ -14,6 +15,7 @@ private void playGame(int[] rolls) {
}
@Test
+ @DisplayName("should be able to score a game with all zeros")
public void shouldBeAbleToScoreAGameWithAllZeros() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -21,8 +23,9 @@ public void shouldBeAbleToScoreAGameWithAllZeros() {
assertThat(game.score()).isZero();
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("should be able to score a game with no strikes or spares")
public void shouldBeAbleToScoreAGameWithNoStrikesOrSpares() {
int[] rolls = {3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6};
@@ -30,8 +33,9 @@ public void shouldBeAbleToScoreAGameWithNoStrikesOrSpares() {
assertThat(game.score()).isEqualTo(90);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("a spare followed by zeros is worth ten points")
public void aSpareFollowedByZerosIsWorthTenPoints() {
int[] rolls = {6, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -39,8 +43,9 @@ public void aSpareFollowedByZerosIsWorthTenPoints() {
assertThat(game.score()).isEqualTo(10);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("points scored in the roll after a spare are counted twice")
public void pointsScoredInTheRollAfterASpareAreCountedTwice() {
int[] rolls = {6, 4, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -48,8 +53,9 @@ public void pointsScoredInTheRollAfterASpareAreCountedTwice() {
assertThat(game.score()).isEqualTo(16);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("consecutive spares each get a one roll bonus")
public void consecutiveSparesEachGetAOneRollBonus() {
int[] rolls = {5, 5, 3, 7, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -57,8 +63,9 @@ public void consecutiveSparesEachGetAOneRollBonus() {
assertThat(game.score()).isEqualTo(31);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("a spare in the last frame gets a one roll bonus that is counted once")
public void aSpareInTheLastFrameGetsAOneRollBonusThatIsCountedOnce() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 7};
@@ -66,8 +73,9 @@ public void aSpareInTheLastFrameGetsAOneRollBonusThatIsCountedOnce() {
assertThat(game.score()).isEqualTo(17);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("a strike earns ten points in a frame with a single roll")
public void aStrikeEarnsTenPointsInFrameWithASingleRoll() {
int[] rolls = {10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -75,8 +83,9 @@ public void aStrikeEarnsTenPointsInFrameWithASingleRoll() {
assertThat(game.score()).isEqualTo(10);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("points scored in the two rolls after a strike are counted twice as a bonus")
public void pointsScoredInTheTwoRollsAfterAStrikeAreCountedTwiceAsABonus() {
int[] rolls = {10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -84,8 +93,9 @@ public void pointsScoredInTheTwoRollsAfterAStrikeAreCountedTwiceAsABonus() {
assertThat(game.score()).isEqualTo(26);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("consecutive strikes each get the two roll bonus")
public void consecutiveStrikesEachGetTheTwoRollBonus() {
int[] rolls = {10, 10, 10, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -93,8 +103,9 @@ public void consecutiveStrikesEachGetTheTwoRollBonus() {
assertThat(game.score()).isEqualTo(81);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("a strike in the last frame gets a two roll bonus that is counted once")
public void aStrikeInTheLastFrameGetsATwoRollBonusThatIsCountedOnce() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 1};
@@ -102,8 +113,9 @@ public void aStrikeInTheLastFrameGetsATwoRollBonusThatIsCountedOnce() {
assertThat(game.score()).isEqualTo(18);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("rolling a spare with the two roll bonus does not get a bonus roll")
public void rollingASpareWithTheTwoRollBonusDoesNotGetABonusRoll() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 7, 3};
@@ -111,8 +123,9 @@ public void rollingASpareWithTheTwoRollBonusDoesNotGetABonusRoll() {
assertThat(game.score()).isEqualTo(20);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("strikes with the two roll bonus do not get bonus rolls")
public void strikesWithTheTwoRollBonusDoNotGetBonusRolls() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10};
@@ -120,8 +133,19 @@ public void strikesWithTheTwoRollBonusDoNotGetBonusRolls() {
assertThat(game.score()).isEqualTo(30);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
+ @Test
+ @DisplayName("last two strikes followed by only last bonus with non strike points")
+ public void lastTwoStrikesFollowedByOnlyLastBonusWithNonStrikePoints() {
+ int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1};
+
+ playGame(rolls);
+ assertThat(game.score()).isEqualTo(31);
+ }
+
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("a strike with the one roll bonus after a spare in the last frame does not get a bonus")
public void aStrikeWithTheOneRollBonusAfterASpareInTheLastFrameDoesNotGetABonus() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10};
@@ -129,8 +153,9 @@ public void aStrikeWithTheOneRollBonusAfterASpareInTheLastFrameDoesNotGetABonus(
assertThat(game.score()).isEqualTo(20);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("all strikes is a perfect game")
public void allStrikesIsAPerfectGame() {
int[] rolls = {10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10};
@@ -138,8 +163,9 @@ public void allStrikesIsAPerfectGame() {
assertThat(game.score()).isEqualTo(300);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("rolls cannot score negative points")
public void rollsCanNotScoreNegativePoints() {
int[] rolls = {-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -148,8 +174,9 @@ public void rollsCanNotScoreNegativePoints() {
.withMessage("Negative roll is invalid");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("a roll cannot score more than 10 points")
public void aRollCanNotScoreMoreThan10Points() {
int[] rolls = {11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -158,8 +185,9 @@ public void aRollCanNotScoreMoreThan10Points() {
.withMessage("Pin count exceeds pins on the lane");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("two rolls in a frame cannot score more than 10 points")
public void twoRollsInAFrameCanNotScoreMoreThan10Points() {
int[] rolls = {5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -168,8 +196,9 @@ public void twoRollsInAFrameCanNotScoreMoreThan10Points() {
.withMessage("Pin count exceeds pins on the lane");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("bonus roll after a strike in the last frame cannot score more than 10 points")
public void bonusRollAfterAStrikeInTheLastFrameCanNotScoreMoreThan10Points() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 0};
@@ -178,8 +207,9 @@ public void bonusRollAfterAStrikeInTheLastFrameCanNotScoreMoreThan10Points() {
.withMessage("Pin count exceeds pins on the lane");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("two bonus rolls after a strike in the last frame cannot score more than 10 points")
public void twoBonusRollsAfterAStrikeInTheLastFrameCanNotScoreMoreThan10Points() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 5, 6};
@@ -188,8 +218,9 @@ public void twoBonusRollsAfterAStrikeInTheLastFrameCanNotScoreMoreThan10Points()
.withMessage("Pin count exceeds pins on the lane");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("two bonus rolls after a strike in the last frame can score more than 10 points if one is a strike")
public void twoBonusRollsAfterAStrikeInTheLastFrameCanScoreMoreThan10PointsIfOneIsAStrike() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 6};
@@ -198,8 +229,11 @@ public void twoBonusRollsAfterAStrikeInTheLastFrameCanScoreMoreThan10PointsIfOne
assertThat(game.score()).isEqualTo(26);
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName(
+ "the second bonus rolls after a strike in the last frame cannot be a strike if the first one is not a strike"
+ )
public void theSecondBonusRollsAfterAStrikeInTheLastFrameCanNotBeAStrikeIfTheFirstOneIsNotAStrike() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 6, 10};
@@ -208,8 +242,9 @@ public void theSecondBonusRollsAfterAStrikeInTheLastFrameCanNotBeAStrikeIfTheFir
.withMessage("Pin count exceeds pins on the lane");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("second bonus roll after a strike in the last frame cannot score more than 10 points")
public void secondBonusRollAfterAStrikeInTheLastFrameCanNotScoreMoreThan10Points() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 11};
@@ -218,8 +253,9 @@ public void secondBonusRollAfterAStrikeInTheLastFrameCanNotScoreMoreThan10Points
.withMessage("Pin count exceeds pins on the lane");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("an unstarted game cannot be scored")
public void anUnstartedGameCanNotBeScored() {
int[] rolls = new int[0];
@@ -230,8 +266,9 @@ public void anUnstartedGameCanNotBeScored() {
.withMessage("Score cannot be taken until the end of the game");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("an incomplete game cannot be scored")
public void anIncompleteGameCanNotBeScored() {
int[] rolls = {0, 0};
@@ -242,8 +279,9 @@ public void anIncompleteGameCanNotBeScored() {
.withMessage("Score cannot be taken until the end of the game");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("cannot roll if game already has ten frames")
public void canNotRollIfGameAlreadyHasTenFrames() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -252,8 +290,9 @@ public void canNotRollIfGameAlreadyHasTenFrames() {
.withMessage("Cannot roll after game is over");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("bonus rolls for a strike in the last frame must be rolled before score can be calculated")
public void bonusRollsForAStrikeInTheLastFrameMustBeRolledBeforeScoreCanBeCalculated() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10};
@@ -264,8 +303,9 @@ public void bonusRollsForAStrikeInTheLastFrameMustBeRolledBeforeScoreCanBeCalcul
.withMessage("Score cannot be taken until the end of the game");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("both bonus rolls for a strike in the last frame must be rolled before score can be calculated")
public void bothBonusRollsForAStrikeInTheLastFrameMustBeRolledBeforeScoreCanBeCalculated() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10};
@@ -276,8 +316,9 @@ public void bothBonusRollsForAStrikeInTheLastFrameMustBeRolledBeforeScoreCanBeCa
.withMessage("Score cannot be taken until the end of the game");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("bonus roll for a spare in the last frame must be rolled before score can be calculated")
public void bonusRollForASpareInTheLastFrameMustBeRolledBeforeScoreCanBeCalculated() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3};
@@ -288,8 +329,9 @@ public void bonusRollForASpareInTheLastFrameMustBeRolledBeforeScoreCanBeCalculat
.withMessage("Score cannot be taken until the end of the game");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("cannot roll after bonus roll for spare")
public void canNotRollAfterBonusRollForSpare() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 2, 2};
@@ -298,8 +340,9 @@ public void canNotRollAfterBonusRollForSpare() {
.withMessage("Cannot roll after game is over");
}
- @Ignore("Remove to run test")
+ @Disabled("Remove to run test")
@Test
+ @DisplayName("cannot roll after bonus rolls for strike")
public void canNotRollAfterBonusRollForStrike() {
int[] rolls = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 3, 2, 2};
diff --git a/exercises/practice/change/.approaches/config.json b/exercises/practice/change/.approaches/config.json
new file mode 100644
index 000000000..c24280ab1
--- /dev/null
+++ b/exercises/practice/change/.approaches/config.json
@@ -0,0 +1,42 @@
+{
+ "introduction": {
+ "authors": [
+ "jagdish-15"
+ ],
+ "contributors": [
+ "kahgoh"
+ ]
+ },
+ "approaches": [
+ {
+ "uuid": "d0b615ca-3a02-4d66-ad10-e0c513062189",
+ "slug": "dynamic-programming-top-down",
+ "title": "Dynamic Programming: Top Down",
+ "blurb": "Break the required amount into smaller amounts and reuse saved results to quickly find the final result.",
+ "authors": [
+ "jagdish-15"
+ ],
+ "contributors": [
+ "kahgoh"
+ ]
+ },
+ {
+ "uuid": "daf47878-1607-4f22-b2df-1049f3d6802c",
+ "slug": "dynamic-programming-bottom-up",
+ "title": "Dynamic Programming: Bottom Up",
+ "blurb": "Start from the available coins and calculate the amounts that can be made from them.",
+ "authors": [
+ "kahgoh"
+ ]
+ },
+ {
+ "uuid": "06ae63ec-5bf3-41a0-89e3-2772e4cdbf5d",
+ "slug": "recursive",
+ "title": "Recursive",
+ "blurb": "Use recursion to recursively find the most efficient change for a given amount.",
+ "authors": [
+ "kahgoh"
+ ]
+ }
+ ]
+}
diff --git a/exercises/practice/change/.approaches/dynamic-programming-bottom-up/content.md b/exercises/practice/change/.approaches/dynamic-programming-bottom-up/content.md
new file mode 100644
index 000000000..84983df5e
--- /dev/null
+++ b/exercises/practice/change/.approaches/dynamic-programming-bottom-up/content.md
@@ -0,0 +1,84 @@
+# Dynamic Programming - Bottom up
+
+```java
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class ChangeCalculator {
+
+ private final List currencyCoins;
+
+ ChangeCalculator(List currencyCoins) {
+ this.currencyCoins = List.copyOf(currencyCoins);
+ }
+
+ List computeMostEfficientChange(int grandTotal) {
+ if (grandTotal < 0) {
+ throw new IllegalArgumentException("Negative totals are not allowed.");
+ }
+ if (grandTotal == 0) {
+ return Collections.emptyList();
+ }
+ Set reachableTotals = new HashSet<>();
+ ArrayDeque> queue = new ArrayDeque<>(currencyCoins.stream().map(List::of).toList());
+
+ while (!queue.isEmpty()) {
+ List next = queue.poll();
+ int total = next.stream().mapToInt(Integer::intValue).sum();
+ if (total == grandTotal) {
+ return next;
+ }
+ if (total < grandTotal && reachableTotals.add(total)) {
+ for (Integer coin : currencyCoins) {
+ List toCheck = new ArrayList<>(next);
+ toCheck.add(coin);
+ queue.offer(toCheck);
+ }
+ }
+ }
+
+ throw new IllegalArgumentException("The total " + grandTotal + " cannot be represented in the given currency.");
+ }
+}
+```
+
+This approach starts from the coins and calculates which amounts can be made up by the coins.
+
+The `grandTotal` is first validated to ensure that it is a positive number greater than 0.
+Two data structures are then created:
+
+- a queue to maintain a combination of coins to check
+- a set to keep track of the totals from the combinations that have been seen
+
+The queue is initialized with a number of combinations that consist just each of the coins.
+For example, if the available coins are 5, 10 and 20, then the queue begins with three combinations:
+
+- the first combination has just 5
+- the second has just 10
+- the third has just 20
+
+Thus, the queue contains `[[5], [10], [20]]`.
+
+For each combination in the queue, the loop calculates the sum of the combination.
+If the sum equals the desired total, it has found the combination.
+Otherwise new combinations are added to the queue by adding each of the coins to the end of the combination:
+
+- less than the desired total, and:
+- the total has _not_ yet been "seen" (the Set's [add][set-add] method returns `true` if a new item is being added and `false` if it is already in the Set)
+
+~~~~exercism/note
+If the total has been "seen", there is no need to recheck the amounts because shorter combinations are always checked before longer combinations.
+So, if the total is encountered again, we must have found a shorter combination to reach the same amount earlier.
+~~~~
+
+Continuing with the above example, the first combination contains just `5`.
+When this is processed, the combinations `[5, 5]`, `[5, 10]` and `[5, 20]` would be added to the end of the queue and the queue becomes `[[10], [20],[5 ,5], [5, 10], [5, 20]]` for the next iteration.
+Adding to the end of the queue ensures that the shorter combinations are checked first and allows the combination to simply be returned when the total is reached.
+
+The total can not be reached when there are no combinations in the queue.
+
+[set-add]: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Set.html#add(E)
diff --git a/exercises/practice/change/.approaches/dynamic-programming-bottom-up/snippet.txt b/exercises/practice/change/.approaches/dynamic-programming-bottom-up/snippet.txt
new file mode 100644
index 000000000..354ea1739
--- /dev/null
+++ b/exercises/practice/change/.approaches/dynamic-programming-bottom-up/snippet.txt
@@ -0,0 +1,8 @@
+while (!queue.isEmpty()) {
+ int total = next.stream().mapToInt(Integer::intValue).sum();
+ if (total < grandTotal && reachableTotals.add(total)) {
+ for (Integer coin : currencyCoins) {
+ queue.add(append(next, coin));
+ }
+ }
+}
\ No newline at end of file
diff --git a/exercises/practice/change/.approaches/dynamic-programming-top-down/content.md b/exercises/practice/change/.approaches/dynamic-programming-top-down/content.md
new file mode 100644
index 000000000..06dbf9ba8
--- /dev/null
+++ b/exercises/practice/change/.approaches/dynamic-programming-top-down/content.md
@@ -0,0 +1,68 @@
+# Dynamic Programming - Top Down
+
+```java
+import java.util.List;
+import java.util.ArrayList;
+
+class ChangeCalculator {
+ private final List currencyCoins;
+
+ ChangeCalculator(List currencyCoins) {
+ this.currencyCoins = currencyCoins;
+ }
+
+ List