Repository: bobocode-projects/java-fundamentals-exercises Branch: main Commit: 707802e9af36 Files: 201 Total size: 503.6 KB Directory structure: gitextract_r1nqldzq/ ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── maven.yml │ └── projectActions.yml ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── 0-0-intro/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── intro/ │ │ └── ExerciseIntroduction.java │ └── test/ │ └── java/ │ └── com/ │ └── bobocode/ │ └── intro/ │ └── ExerciseIntroductionTest.java ├── 1-0-java-basics/ │ ├── 1-3-0-hello-generics/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── basics/ │ │ │ ├── Box.java │ │ │ └── BoxDemoApp.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── basics/ │ │ └── BoxTest.java │ ├── 1-3-1-crazy-generics/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── basics/ │ │ │ ├── CrazyGenerics.java │ │ │ └── util/ │ │ │ └── BaseEntity.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── basics/ │ │ └── CrazyGenericsTest.java │ ├── 1-3-2-heterogeneous-max-holder/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── basics/ │ │ │ └── HeterogeneousMaxHolder.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── basics/ │ │ └── HeterogeneousMaxHolderTest.java │ ├── 1-5-0-hello-annotations/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── basics/ │ │ │ ├── HelloAnnotationsExercise.java │ │ │ └── Level.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── basics/ │ │ └── HelloAnnotationsExerciseTest.java │ ├── README.md │ └── pom.xml ├── 2-0-data-structures-and-algorithms/ │ ├── 2-2-1-node/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobobode/ │ │ │ └── cs/ │ │ │ ├── Node.java │ │ │ └── Nodes.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── cs/ │ │ └── NodesTest.java │ ├── 2-2-2-stack/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── cs/ │ │ │ ├── LinkedStack.java │ │ │ ├── Stack.java │ │ │ └── exception/ │ │ │ └── EmptyStackException.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── cs/ │ │ └── LinkedStackTest.java │ ├── 2-2-3-linked-queue/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── cs/ │ │ │ ├── LinkedQueue.java │ │ │ └── Queue.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── cs/ │ │ └── LinkedQueueTest.java │ ├── 2-2-4-linked-list/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── cs/ │ │ │ └── LinkedList.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── cs/ │ │ └── LinkedListTest.java │ ├── 2-2-5-array-list/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── cs/ │ │ │ └── ArrayList.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── cs/ │ │ └── ArrayListTest.java │ ├── 2-2-6-binary-search-tree/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── cs/ │ │ │ ├── BinarySearchTree.java │ │ │ └── RecursiveBinarySearchTree.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── cs/ │ │ └── RecursiveBinarySearchTreeTest.java │ ├── 2-2-9-hash-table/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── cs/ │ │ │ ├── HashTable.java │ │ │ └── Map.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── cs/ │ │ └── HashTableTest.java │ ├── README.md │ ├── data-structures-and-algorithms-util/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── cs/ │ │ └── List.java │ └── pom.xml ├── 3-0-java-core/ │ ├── 3-6-1-file-reader/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── se/ │ │ │ └── FileReaders.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── se/ │ │ │ └── FileReadersTest.java │ │ └── resources/ │ │ ├── empty.txt │ │ ├── lines.txt │ │ └── simple.txt │ ├── 3-6-2-file-stats/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── se/ │ │ │ ├── FileStats.java │ │ │ └── FileStatsException.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── se/ │ │ │ └── FileStatsTest.java │ │ └── resources/ │ │ ├── scosb.txt │ │ └── sotl.txt │ ├── 3-6-3-crazy-regex/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── se/ │ │ │ └── CrazyRegex.java │ │ └── test/ │ │ ├── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── se/ │ │ │ └── CrazyRegexTest.java │ │ └── resources/ │ │ ├── nasa.json │ │ └── note.txt │ ├── 3-6-4-random-field-comparator/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── se/ │ │ │ └── RandomFieldComparator.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── se/ │ │ └── RandomFieldComparatorTest.java │ ├── README.md │ └── pom.xml ├── 4-0-object-oriented-programming/ │ ├── 4-3-1-flight-search/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── oop/ │ │ │ ├── data/ │ │ │ │ └── FlightDao.java │ │ │ ├── factory/ │ │ │ │ └── FlightServiceFactory.java │ │ │ └── service/ │ │ │ └── FlightService.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── oop/ │ │ └── FlightServiceTest.java │ ├── README.md │ └── pom.xml ├── 5-0-functional-programming/ │ ├── 5-0-1-lambda-functions-map/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── fp/ │ │ │ ├── FunctionMap.java │ │ │ ├── Functions.java │ │ │ └── exception/ │ │ │ └── InvalidFunctionNameException.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── fp/ │ │ └── FunctionsTest.java │ ├── 5-0-2-stream-sum-of-squares/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── fp/ │ │ │ ├── SumOfSquares.java │ │ │ └── exception/ │ │ │ └── InvalidRangeException.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── fp/ │ │ └── SumOfSquaresTest.java │ ├── 5-1-1-crazy-lambdas/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── fp/ │ │ │ └── CrazyLambdas.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── fp/ │ │ └── CrazyLambdasTest.java │ ├── 5-2-1-crazy-streams/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── fp/ │ │ │ ├── CrazyStreams.java │ │ │ └── exception/ │ │ │ └── EntityNotFoundException.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── fp/ │ │ └── CrazyStreamsTest.java │ ├── 5-3-1-crazy-optionals/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── fp/ │ │ │ ├── CrazyOptionals.java │ │ │ ├── exception/ │ │ │ │ └── AccountNotFoundException.java │ │ │ └── function/ │ │ │ ├── AccountProvider.java │ │ │ ├── AccountService.java │ │ │ └── CreditAccountProvider.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── fp/ │ │ └── CrazyOptionalsTest.java │ ├── 5-4-1-fun-prime-numbers/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── fp/ │ │ │ ├── OOSumOfPrimes.java │ │ │ └── PrimeNumbers.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── fp/ │ │ └── PrimeNumbersTest.java │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── bobocode/ │ ├── lambdas/ │ │ └── tutorial/ │ │ ├── CrazyLambdaExample.java │ │ ├── FunctionComposition.java │ │ ├── LambdaAndMethodReference.java │ │ ├── LambdaComparatorExample.java │ │ ├── LambdaRunnableExample.java │ │ ├── MethodRefToUpperCaseExample.java │ │ ├── MethodReferenceExamples.java │ │ ├── PredefinedInterfacePrimitives.java │ │ └── PredefinedInterfacesExamples.java │ ├── optionals/ │ │ └── tutorial/ │ │ ├── OptionalCreation.java │ │ ├── OptionalExamples.java │ │ ├── OptionalImperativeVsDeclarativeCheck.java │ │ ├── OptionalMapping.java │ │ ├── OptionalPrimitives.java │ │ ├── OptionalReturningMethod.java │ │ ├── OptionalSearchByEmailExample.java │ │ ├── OptionalStream.java │ │ └── README.MD │ └── streams/ │ └── tutorial/ │ ├── ImperativeVsDeclarativeFiltering.java │ ├── ImperativeVsDeclarativeMax.java │ ├── README.MD │ ├── StreamAdditionalFeatures.java │ ├── StreamBasics.java │ ├── StreamCollecting.java │ ├── StreamFiltering.java │ ├── StreamMapping.java │ ├── StreamParallelProcessing.java │ ├── StreamPrimitives.java │ ├── StreamReducing.java │ ├── StreamSideEffectFilteringExample.java │ └── StreamWhileExample.java ├── 6-0-test-driven-development/ │ ├── 6-1-1-stack/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── tdd/ │ │ │ ├── LinkedStack.java │ │ │ └── Stack.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── tdd/ │ │ └── StackTest.java │ ├── 6-1-2-linked-list/ │ │ ├── README.MD │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── tdd/ │ │ │ ├── LinkedList.java │ │ │ └── List.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── tdd/ │ │ └── LinkedListTest.java │ ├── 6-1-3-binary-search-tree/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── com/ │ │ │ └── bobocode/ │ │ │ └── tdd/ │ │ │ ├── BinarySearchTree.java │ │ │ └── RecursiveBinarySearchTree.java │ │ └── test/ │ │ └── java/ │ │ └── com/ │ │ └── bobocode/ │ │ └── tdd/ │ │ └── BinarySearchTreeTest.java │ ├── README.md │ └── pom.xml ├── CONTRIBUTING.MD ├── LICENSE ├── README.md ├── java-fundamentals-util/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── bobocode/ │ ├── data/ │ │ └── Accounts.java │ ├── model/ │ │ ├── Account.java │ │ ├── CreditAccount.java │ │ └── Sex.java │ └── util/ │ └── ExerciseNotCompletedException.java ├── lesson-demo/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── com/ │ └── bobocode/ │ └── DemoApp.java ├── mvnw ├── mvnw.cmd └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms patreon: bobocode ================================================ FILE: .github/workflows/maven.yml ================================================ # This workflow will build a Java project with Maven # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven name: Build completed branch with tests on: push: branches: [ completed ] pull_request: branches: [ completed ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up JDK 17 uses: actions/setup-java@v1 with: java-version: 17 - name: Build with Maven run: mvn -B package --file pom.xml ================================================ FILE: .github/workflows/projectActions.yml ================================================ name: Auto Assign to Project(s) on: issues: types: [opened, labeled] pull_request: types: [opened, labeled] env: GITHUB_TOKEN: ${{ secrets.MY_GITHUB_TOKEN }} jobs: assign_one_project: runs-on: ubuntu-latest name: Assign to One Project steps: - name: Assign NEW issues project 4 uses: srggrs/assign-one-project-github-action@1.2.1 if: github.event.action == 'opened' || contains(github.event.issue.labels.*.name, 'java-fundamentals') with: project: 'https://github.com/bobocode-projects/java-fundamentals-exercises/projects/4' ================================================ FILE: .gitignore ================================================ .idea **/*.iml **/target ================================================ FILE: .mvn/wrapper/MavenWrapperDownloader.java ================================================ /* * Copyright 2007-present the original author or 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 * * http://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. */ import java.net.*; import java.io.*; import java.nio.channels.*; import java.util.Properties; public class MavenWrapperDownloader { private static final String WRAPPER_VERSION = "0.5.6"; /** * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; /** * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to * use instead of the default one. */ private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties"; /** * Path where the maven-wrapper.jar will be saved to. */ private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; /** * Name of the property which should be used to override the default download url for the wrapper. */ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; public static void main(String args[]) { System.out.println("- Downloader started"); File baseDirectory = new File(args[0]); System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); // If the maven-wrapper.properties exists, read it and check if it contains a custom // wrapperUrl parameter. File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); String url = DEFAULT_DOWNLOAD_URL; if(mavenWrapperPropertyFile.exists()) { FileInputStream mavenWrapperPropertyFileInputStream = null; try { mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); Properties mavenWrapperProperties = new Properties(); mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); } catch (IOException e) { System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); } finally { try { if(mavenWrapperPropertyFileInputStream != null) { mavenWrapperPropertyFileInputStream.close(); } } catch (IOException e) { // Ignore ... } } } System.out.println("- Downloading from: " + url); File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); if(!outputFile.getParentFile().exists()) { if(!outputFile.getParentFile().mkdirs()) { System.out.println( "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); } } System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); try { downloadFileFromURL(url, outputFile); System.out.println("Done"); System.exit(0); } catch (Throwable e) { System.out.println("- Error downloading"); e.printStackTrace(); System.exit(1); } } private static void downloadFileFromURL(String urlString, File destination) throws Exception { if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { String username = System.getenv("MVNW_USERNAME"); char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, password); } }); } URL website = new URL(urlString); ReadableByteChannel rbc; rbc = Channels.newChannel(website.openStream()); FileOutputStream fos = new FileOutputStream(destination); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); rbc.close(); } } ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar ================================================ FILE: 0-0-intro/README.md ================================================ # Introduction Learn how to use this course to build strong skill needed for enterprise Java development ## The only way to learn effectively is to **learn by doing!** 💪 Therefore, this whole repository consists of **various exercises grouped by topics**. By doing exercises you will **build strong skills and neven get stuck.** Introduction itself is an exercise, so you can understand the idea on the simplest example. Each exercise has three major parts: * **a task** – some logic that you implement in order build a certain skill 👨🏻‍💻 * **a test** – a corresponding test that verifies if you implement the task correctly ▶️ * **a completed solution** - a branch `completed` that holds implemented task ✅ Go ahead and do this exercise by implementing a method in `Introduction` class. 💪 _(we know it's silly, but we wanted to give you a simple example 😀)_ ## You should have installed on your local machine ❗️ * [JDK 11+](https://jdk.java.net/15/) * [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) ## How to start ❓ * [clone](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) this [repository](https://github.com/bobocode-projects/java-fundamentals-exercises) to your computer * **open** the project via IDE ## Have questions? 🧐 * take a look at **README** of the exercise * switch to branch `completed` and **see the correct implementation** of the exercise * [join the discussion](https://github.com/bobocode-projects/java-fundamentals-exercises/discussions) on the GitHub * contact us via info@bobocode.com ================================================ FILE: 0-0-intro/pom.xml ================================================ 4.0.0 com.bobocode java-fundamentals-exercises 1.0-SNAPSHOT 0-0-intro com.bobocode java-fundamentals-util 1.0-SNAPSHOT ================================================ FILE: 0-0-intro/src/main/java/com/bobocode/intro/ExerciseIntroduction.java ================================================ package com.bobocode.intro; import com.bobocode.util.ExerciseNotCompletedException; /** * Welcome! This is an introduction exercise that will show you a simple example of Bobocode exercises. *

* JavaDoc is a way of communication with other devs. We use Java Docs in every exercise to define the task. * So PLEASE MAKE SURE you read the Java Docs carefully. *

* Every exercise is covered with tests, see {@link ExerciseIntroductionTest}. *

* In this repo you'll find dozens of exercises covering various fundamental topics. * They all have the same structure helping you to focus on practice and build strong skills! * * @author Taras Boychuk */ public class ExerciseIntroduction { /** * This method returns a very important message. If understood well, it can save you years of inefficient learning, * and unlock your potential! * * @return "The key to efficient learning is practice!" */ public String getWelcomeMessage() { // todo: implement a method and return a message according to javadoc throw new ExerciseNotCompletedException(); } /** * Method encodeMessage accepts one {@link String} parameter and returns encoded {@link String}. *

* PLEASE NOTE THAT YOU WILL GET STUCK ON THIS METHOD INTENTIONALLY! ;) *

* Every exercise has a completed solution that is stored in the branch "completed". So in case you got stuck * and don't know what to do, go check out completed solution. * * @param message input message * @return encoded message */ public String encodeMessage(String message) { // todo: switch to branch "completed" in order to see how it should be implemented throw new ExerciseNotCompletedException(); } } ================================================ FILE: 0-0-intro/src/test/java/com/bobocode/intro/ExerciseIntroductionTest.java ================================================ package com.bobocode.intro; import lombok.SneakyThrows; import org.junit.jupiter.api.*; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; /** * This is a {@link ExerciseIntroductionTest} that is meant to verify if you properly implement {@link ExerciseIntroduction}. * It is a simple example that shows how each exercise is organized: todo section + tests. *

* A typical Java test uses JUnit framework to run the test, and may also use some other frameworks for assertions. * In our exercises we use JUnit 5 + AssertJ *

* PLEASE NOTE: * - annotation @{@link Order} is used to help you to understand which method should be implemented first. * - annotation @{@link DisplayName} is used to provide you more detailed instructions. * * @author Taras Boychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class ExerciseIntroductionTest { private ExerciseIntroduction exerciseIntroduction = new ExerciseIntroduction(); private String EXPECTED_MESSAGE = "The key to efficient learning is practice!"; @Test @Order(1) @DisplayName("getWelcomeMessage method returns correct phrase") void getWelcomeMessage() { String message = exerciseIntroduction.getWelcomeMessage(); assertThat(message).isEqualTo(EXPECTED_MESSAGE); } @Test @Order(2) @DisplayName("encodeMessage returns correct encoded message") @SneakyThrows void encodeMessageReturnsCorrectPhrase() { var encodeMessageMethod = Arrays.stream(ExerciseIntroduction.class.getDeclaredMethods()) .filter(method -> method.getName().equals("encodeMessage")) .findAny() .orElseThrow(); var encodedMessage = encodeMessageMethod.invoke(new ExerciseIntroduction(), EXPECTED_MESSAGE); assertThat(encodedMessage).isEqualTo("VGhlIGtleSB0byBlZmZpY2llbnQgbGVhcm5pbmcgaXMgcHJhY3RpY2Uh"); } } ================================================ FILE: 1-0-java-basics/1-3-0-hello-generics/README.MD ================================================ # Hello Generics :muscle: Learn how to do **safe type parametrization** using generic types 💪 ### Pre-conditions ❗ You're supposed to be familiar **Java classes** and **casting** ### Objectives * refactor class `Box` to introduce a generic type ✅ * change the value field type to parametrized (generic) type ✅ * change the **constructor parameter type** to parametrized type ✅ * change the **setter parameter type** to parametrized type ✅ * change the **getter return type** to parametrized type ✅ * refactor `BoxDemoApp` to specify generic type as `Integer` and receive **compile time error** ✅ ## In many cases the logic we implement does not depend on the data type. In other words, **many algorithms are the same regardless if you apply them to Integer, String or any other types.** So in order to leverage the same logic for different types we need some form of type parametrization. Of course, we can use `Object` and store anything we want, but it is not safe. Using `Object` requires runtime casting which can cause runtime exceptions. --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 1-0-java-basics/1-3-0-hello-generics/pom.xml ================================================ 1-0-java-basics com.bobocode 1.0-SNAPSHOT 4.0.0 1-3-0-hello-generics ================================================ FILE: 1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/Box.java ================================================ package com.bobocode.basics; /** * {@link Box} is a container class that can store a value of any given type. Using Object as a field type * is flexible, because we can store anything we want there. But it is not safe, because it requires runtime casting * and there is no guarantee that we know the type of the stored value. *

* todo: refactor this class so it uses generic type "T" and run {@link com.bobocode.basics.BoxTest} to verify it */ public class Box { private Object value; public Box(Object value) { this.value = value; } public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } } ================================================ FILE: 1-0-java-basics/1-3-0-hello-generics/src/main/java/com/bobocode/basics/BoxDemoApp.java ================================================ package com.bobocode.basics; /** * This demo demonstrates why using Object is not safe. It's not safe because runtime casting can cause runtime * exceptions. We should always fail as soon as possible. So in this code we should not allow setting String * value at compile time, if we expect to work with integers. *

* todo: refactor class {@link Box} to make type parameterization safe and make this demo fail at compile time */ public class BoxDemoApp { public static void main(String[] args) { Box intBox = new Box(123); Box intBox2 = new Box(321); System.out.println((int) intBox.getValue() + (int) intBox2.getValue()); intBox.setValue(222); intBox.setValue("abc"); // this should not be allowed // the following code will compile, but will throw runtime exception System.out.println((int) intBox.getValue() + (int) intBox2.getValue()); } } ================================================ FILE: 1-0-java-basics/1-3-0-hello-generics/src/test/java/com/bobocode/basics/BoxTest.java ================================================ package com.bobocode.basics; import lombok.SneakyThrows; import org.junit.jupiter.api.*; import static org.assertj.core.api.Assertions.assertThat; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class BoxTest { private final String TYPE_PARAMETER_NAME = "T"; @Test @Order(1) @DisplayName("Box class has one type parameter") void boxClassHasOneTypeParameter() { var typeParameters = Box.class.getTypeParameters(); assertThat(typeParameters.length).isEqualTo(1); } @Test @Order(2) @DisplayName("Type parameter is called \"T\"") void typeParameterIsCalledT() { var typeParameter = Box.class.getTypeParameters()[0]; assertThat(typeParameter.getName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(3) @DisplayName("Type parameter \"T\" is not bounded") void typeParameterIsNotBounded() { var typeParameter = Box.class.getTypeParameters()[0]; assertThat(typeParameter.getBounds()).hasSize(1); assertThat(typeParameter.getBounds()[0].getTypeName()).isEqualTo(Object.class.getTypeName()); } @Test @SneakyThrows @Order(4) @DisplayName("Field \"value\" is \"T\"") void valueFieldIsGeneric() { var valueField = Box.class.getDeclaredField("value"); var genericType = valueField.getGenericType(); assertThat(genericType.getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @SneakyThrows @Order(5) @DisplayName("Constructor parameter type is \"T\"") void constructorParameterIsGeneric() { var constructor = Box.class.getDeclaredConstructors()[0]; assert (constructor.getParameters().length == 1); var parameter = constructor.getParameters()[0]; assertThat(parameter.getParameterizedType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @SneakyThrows @Order(6) @DisplayName("Getter return type is \"T\"") void getterReturnTypeIsGeneric() { var getter = Box.class.getDeclaredMethod("getValue"); assertThat(getter.getGenericReturnType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @SneakyThrows @Order(7) @DisplayName("Setter parameter type is \"T\"") void setterParameterIsGeneric() { var setter = Box.class.getDeclaredMethod("setValue", Object.class); assert (setter.getParameters().length == 1); var parameter = setter.getParameters()[0]; assertThat(parameter.getParameterizedType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } } ================================================ FILE: 1-0-java-basics/1-3-1-crazy-generics/README.MD ================================================ # Crazy Generics #### Learn tricky nuances and become the master of type parametrization in Java 💪 ### WHY ❓ Generics in Java provide **safe type parametrization.** Sou can write the logic of a method or a whole class and **reuse it for different types**. Before Java 5 (when generics were introduced), common logic could be reused as well. However, it required **using `Object` everywhere and casting it everytime you want to get an actual type**. It **could easily cause `ClassCastException` at runtime which is not safe.** When using generics, it's still the same `Object`, but **now compiler creates casts for you implicitly, and makes sure you didn't mess up with types**, which **moves errors from runtime to compile time and makes type parametrization safer.** ### Objectives * create a generic class with **simple type parameter** ✅ * create a generic class with **bounded type parameter** ✅ * create a generic class with **multiples type parameter** ✅ * create a generic class with **recursively bounded type parameter** ✅ * create a **generic method** ✅ * create a method that accepts a generic class on any type using **wildcard** ✅ * create method parameter using **bounded wildcard** ✅ ### Related materials ℹ️ * [Effective Java, Chapter 5 – Generics](https://read.amazon.com/kp/embed?asin=B078H61SCH&preview=newtab&linkCode=kpe&ref_=cm_sw_r_kb_dp_SADNB2C41TWARGY4QGKZ) 📘 ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/EaL5KsSlEQM/0.jpg)](https://www.youtube.com/watch?v=EaL5KsSlEQM) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 1-0-java-basics/1-3-1-crazy-generics/pom.xml ================================================ 1-0-java-basics com.bobocode 1.0-SNAPSHOT 4.0.0 1-3-1-crazy-generics ================================================ FILE: 1-0-java-basics/1-3-1-crazy-generics/src/main/java/com/bobocode/basics/CrazyGenerics.java ================================================ package com.bobocode.basics; import com.bobocode.basics.util.BaseEntity; import com.bobocode.util.ExerciseNotCompletedException; import lombok.Data; import java.io.Serializable; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Objects; /** * {@link CrazyGenerics} is an exercise class. It consists of classes, interfaces and methods that should be updated * using generics. *

* TODO: go step by step from top to bottom. Read the java doc, write code and run CrazyGenericsTest to verify your impl *

* Hint: in some cases you will need to refactor the code, like replace {@link Object} with a generic type. In order * cases you will need to add new fields, create new classes, or add new methods. Always try to read java doc and update * the code according to it. *

* TODO: to get the most out of your learning, visit our website *

* * @author Taras Boychuk */ public class CrazyGenerics { /** * {@link Sourced} is a container class that allows storing any object along with the source of that data. * The value type can be specified by a type parameter "T". * * @param – value type */ @Data public static class Sourced { // todo: refactor class to introduce type parameter and make value generic private Object value; private String source; } /** * {@link Limited} is a container class that allows storing an actual value along with possible min and max values. * It is special form of triple. All three values have a generic type that should be a subclass of {@link Number}. * * @param – actual, min and max type */ @Data public static class Limited { // todo: refactor class to introduce type param bounded by number and make fields generic numbers private final Object actual; private final Object min; private final Object max; } /** * {@link Converter} interface declares a typical contract of a converter. It works with two independent generic types. * It defines a convert method which accepts one parameter of one type and returns a converted result of another type. * * @param – source object type * @param - converted result type */ public interface Converter { // todo: introduce type parameters // todo: add convert method } /** * {@link MaxHolder} is a container class that keeps track of the maximum value only. It works with comparable objects * and allows you to put new values. Every time you put a value, it is stored only if the new value is greater * than the current max. * * @param – value type */ public static class MaxHolder { // todo: refactor class to make it generic private Object max; public MaxHolder(Object max) { this.max = max; } /** * Puts a new value to the holder. A new value is stored to the max, only if it is greater than current max value. * * @param val a new value */ public void put(Object val) { throw new ExerciseNotCompletedException(); // todo: update parameter and implement the method } public Object getMax() { return max; } } /** * {@link StrictProcessor} defines a contract of a processor that can process only objects that are {@link Serializable} * and {@link Comparable}. * * @param – the type of objects that can be processed */ interface StrictProcessor { // todo: make it generic void process(Object obj); } /** * {@link CollectionRepository} defines a contract of a runtime store for entities based on any {@link Collection}. * It has methods that allow to save new entity, and get whole collection. * * @param – a type of the entity that should be a subclass of {@link BaseEntity} * @param – a type of any collection */ interface CollectionRepository { // todo: update interface according to the javadoc void save(Object entity); Collection getEntityCollection(); } /** * {@link ListRepository} extends {@link CollectionRepository} but specifies the underlying collection as * {@link java.util.List}. * * @param – a type of the entity that should be a subclass of {@link BaseEntity} */ interface ListRepository { // todo: update interface according to the javadoc } /** * {@link ComparableCollection} is a {@link Collection} that can be compared by size. It extends a {@link Collection} * interface and {@link Comparable} interface, and provides a default implementation of a compareTo method that * compares collections sizes. *

* Please note that size does not depend on the elements type, so it is allowed to compare collections of different * element types. * * @param a type of collection elements */ interface ComparableCollection { // todo: refactor it to make generic and provide a default impl of compareTo } /** * {@link CollectionUtil} is an util class that provides various generic helper methods. */ static class CollectionUtil { static final Comparator CREATED_ON_COMPARATOR = Comparator.comparing(BaseEntity::getCreatedOn); /** * An util method that allows to print a dashed list of elements * * @param list */ public static void print(List list) { // todo: refactor it so the list of any type can be printed, not only integers list.forEach(element -> System.out.println(" – " + element)); } /** * Util method that check if provided collection has new entities. An entity is any object * that extends {@link BaseEntity}. A new entity is an entity that does not have an id assigned. * (In other word, which id value equals null). * * @param entities provided collection of entities * @return true if at least one of the elements has null id */ public static boolean hasNewEntities(Collection entities) { throw new ExerciseNotCompletedException(); // todo: refactor parameter and implement method } /** * Util method that checks if a provided collection of entities is valid. An entity is any subclass of * a {@link BaseEntity} A validation criteria can be different for different cases, so it is passed * as second parameter. * * @param entities provided collection of entities * @param validationPredicate criteria for validation * @return true if all entities fit validation criteria */ public static boolean isValidCollection() { throw new ExerciseNotCompletedException(); // todo: add method parameters and implement the logic } /** * hasDuplicates is a generic util method checks if a list of entities contains target entity more than once. * In other words, it checks if target entity has duplicates in the provided list. A duplicate is an entity that * has the same UUID. * * @param entities given list of entities * @param targetEntity a target entity * @param entity type * @return true if entities list contains target entity more than once */ public static boolean hasDuplicates() { throw new ExerciseNotCompletedException(); // todo: update method signature and implement it } /** * findMax is a generic util method that accepts an {@link Iterable} and {@link Comparator} and returns an * optional object, that has maximum "value" based on the given comparator. * * @param elements provided iterable of elements * @param comparator an object that will be used to compare elements * @param type of elements * @return optional max value */ // todo: create a method and implement its logic manually without using util method from JDK /** * findMostRecentlyCreatedEntity is a generic util method that accepts a collection of entities and returns the * one that is the most recently created. If collection is empty, * it throws {@link java.util.NoSuchElementException}. *

* This method reuses findMax method and passes entities along with prepare comparator instance, * that is stored as constant CREATED_ON_COMPARATOR. * * @param entities provided collection of entities * @param entity type * @return an entity from the given collection that has the max createdOn value */ // todo: create a method according to JavaDoc and implement it using previous method /** * An util method that allows to swap two elements of any list. It changes the list so the element with the index * i will be located on index j, and the element with index j, will be located on the index i. * Please note that in order to make it convenient and simple, it DOES NOT declare any type parameter. * * @param elements a list of any given type * @param i index of the element to swap * @param j index of the other element to swap */ public static void swap(List elements, int i, int j) { Objects.checkIndex(i, elements.size()); Objects.checkIndex(j, elements.size()); throw new ExerciseNotCompletedException(); // todo: complete method implementation } } } ================================================ FILE: 1-0-java-basics/1-3-1-crazy-generics/src/main/java/com/bobocode/basics/util/BaseEntity.java ================================================ package com.bobocode.basics.util; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalDateTime; import java.util.UUID; @Getter @NoArgsConstructor @AllArgsConstructor public abstract class BaseEntity { protected UUID uuid; protected LocalDateTime createdOn; public BaseEntity(UUID uuid) { this.uuid = uuid; this.createdOn = LocalDateTime.now(); } } ================================================ FILE: 1-0-java-basics/1-3-1-crazy-generics/src/test/java/com/bobocode/basics/CrazyGenericsTest.java ================================================ package com.bobocode.basics; import com.bobocode.basics.CrazyGenerics.*; import com.bobocode.basics.util.BaseEntity; import lombok.SneakyThrows; import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mockito; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.time.LocalDateTime; import java.util.*; import java.util.function.Predicate; import java.util.stream.Stream; import static java.time.temporal.ChronoUnit.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.when; /** * A reflection-based test class for {@link CrazyGenerics}. *

* PLEASE NOTE: we use Reflection API only for learning purposes. It should NOT be used for production tests. * * @author Taras Boychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CrazyGenericsTest { final String TYPE_PARAMETER_NAME = "T"; final String SECOND_TYPE_PARAMETER_NAME = "R"; final String COLLECTION_ELEMENT_TYPE_PARAMETER_NAME = "E"; @Test @Order(1) @DisplayName("Sourced class has one type parameter") void sourcedClassHasOneTypeParameter() { var typeParameters = Sourced.class.getTypeParameters(); assertThat(typeParameters).hasSize(1); } @Test @Order(2) @DisplayName("Sourced class type parameter is called \"T\"") void sourcedClassTypeParameterIsCalledT() { var typeParameter = Sourced.class.getTypeParameters()[0]; assertThat(typeParameter.getName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(3) @DisplayName("Sourced class type parameter \"T\" is not bounded") void sourcedClassTypeParameterIsNotBounded() { var typeParameter = Sourced.class.getTypeParameters()[0]; assertThat(typeParameter.getBounds()).hasSize(1); assertThat(typeParameter.getBounds()[0].getTypeName()).isEqualTo(Object.class.getTypeName()); } @Test @SneakyThrows @Order(4) @DisplayName("Sourced class field \"value\" has generic type \"T\"") void valueFieldIsGeneric() { var valueField = Sourced.class.getDeclaredField("value"); var genericType = valueField.getGenericType(); assertThat(genericType.getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(5) @DisplayName("Limited class has one type parameter") void limitedClassHasOneTypeParameter() { var typeParameters = Limited.class.getTypeParameters(); assertThat(typeParameters).hasSize(1); } @Test @Order(6) @DisplayName("Limited class type parameter is bounded by Number") void limitedClassTypeParameterIsBoundedByNumber() { var typeParameters = Limited.class.getTypeParameters(); var typeParam = typeParameters[0]; assert (typeParam.getBounds().length == 1); var boundType = typeParam.getBounds()[0]; assertThat(boundType.getTypeName()).isEqualTo(Number.class.getTypeName()); } @Test @SneakyThrows @Order(7) @DisplayName("Limited class fields have generic type \"T\"") void limitedClassFieldsAreGeneric() { var fields = Limited.class.getDeclaredFields(); for (var f : fields) { assertThat(f.getGenericType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } } @Test @Order(8) @DisplayName("Converter interface has two type parameters") void converterInterfaceHasTwoTypeParameters() { var typeParameters = Converter.class.getTypeParameters(); assertThat(typeParameters).hasSize(2); } @Test @Order(9) @DisplayName("Converter interface first type parameter is called \"T\"") void converterInterfaceFirstTypeParameterIsCalledT() { var typeParameters = Converter.class.getTypeParameters(); var firstTypeParam = typeParameters[0]; assertThat(firstTypeParam.getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(10) @DisplayName("Converter interface second type parameter is called \"R\"") void converterInterfaceSecondTypeParameterIsCalledR() { var typeParameters = Converter.class.getTypeParameters(); var firstTypeParam = typeParameters[1]; assertThat(firstTypeParam.getTypeName()).isEqualTo(SECOND_TYPE_PARAMETER_NAME); } @Test @SneakyThrows @Order(11) @DisplayName("Convert method parameter \"obj\" has type \"T\"") void converterMethodParameterHasTypeT() { var convertMethod = Converter.class.getDeclaredMethod("convert", Object.class); var objParam = convertMethod.getParameters()[0]; assertThat(objParam.getParameterizedType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @SneakyThrows @Order(12) @DisplayName("Convert method return type is \"R\"") void converterMethodReturnTypeIsR() { var convertMethod = Converter.class.getDeclaredMethod("convert", Object.class); assertThat(convertMethod.getGenericReturnType().getTypeName()).isEqualTo(SECOND_TYPE_PARAMETER_NAME); } @Test @Order(13) @DisplayName("MaxHolder class has one type parameter") void maxHolderClassHasOneTypeParameter() { var typeParameters = MaxHolder.class.getTypeParameters(); assertThat(typeParameters).hasSize(1); } @Test @Order(14) @DisplayName("MaxHolder class type parameter is called \"T\"") void maxHolderClassTypeParameterIsCalledT() { var typeParameter = MaxHolder.class.getTypeParameters()[0]; assertThat(typeParameter.getName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(15) @DisplayName("MaxHolder type parameter is bound by Comparable") void maxHolderClassTypeParameterShouldBeBoundByComparableT() { var typeParameters = MaxHolder.class.getTypeParameters(); var typeParam = typeParameters[0]; var boundType = typeParam.getBounds()[0]; var expectedBoundTypeName = String.format("%s", Comparable.class.getTypeName(), TYPE_PARAMETER_NAME); assertThat(boundType.getTypeName()).isEqualTo(expectedBoundTypeName); } @Test @Order(16) @DisplayName("MaxHolder field has type \"T\"") void maxHolderFieldHasTypeT() { var typeParameter = MaxHolder.class.getTypeParameters()[0]; assertThat(typeParameter.getName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(17) @SneakyThrows @DisplayName("MaxHolder constructor param has type \"T\"") void maxHolderConstructorParamHasTypeT() { var constructor = MaxHolder.class.getConstructors()[0]; assert (constructor.getParameters().length == 1); var param = constructor.getParameters()[0]; assertThat(param.getParameterizedType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(18) @SneakyThrows @DisplayName("MaxHolder put method param has type \"T\"") void maxHolderPutMethodParamHasTypeT() { var putMethod = getMethodByName(MaxHolder.class, "put"); var param = putMethod.getParameters()[0]; assertThat(param.getParameterizedType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(19) @SneakyThrows @DisplayName("MaxHolder getMax return type is \"T\"") void maxHolderGetMaxReturnTypeIsT() { var getMaxMethod = MaxHolder.class.getDeclaredMethod("getMax"); assertThat(getMaxMethod.getGenericReturnType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(20) @SneakyThrows @DisplayName("MaxHolder put stores argument value when it's greater than max") void maxHolderPut() { Constructor constructor = MaxHolder.class.getConstructors()[0]; var maxHolder = constructor.newInstance(10); var putMethod = getMethodByName(MaxHolder.class, "put"); var getMaxMethod = MaxHolder.class.getDeclaredMethod("getMax"); putMethod.invoke(maxHolder, 11); assertThat(getMaxMethod.invoke(maxHolder)).isEqualTo(11); } @Test @Order(21) @SneakyThrows @DisplayName("MaxHolder put keeps old value when argument is less than max") void maxHolderPutWhenArgumentIsLessThanMax() { Constructor constructor = MaxHolder.class.getConstructors()[0]; var maxHolder = constructor.newInstance(10); var putMethod = getMethodByName(MaxHolder.class, "put"); var getMaxMethod = MaxHolder.class.getDeclaredMethod("getMax"); putMethod.invoke(maxHolder, 9); assertThat(getMaxMethod.invoke(maxHolder)).isEqualTo(10); } @Test @Order(22) @DisplayName("StrictProcessor class has one generic type") void strictProcessorHasOneGenericType() { var typeParameters = StrictProcessor.class.getTypeParameters(); assertThat(typeParameters).hasSize(1); } @Test @Order(23) @DisplayName("StrictProcessor type parameter is called \"T\"") void strictProcessorTypeParameterIsCalledT() { var typeParameters = StrictProcessor.class.getTypeParameters(); var typePram = typeParameters[0]; assertThat(typePram.getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(24) @DisplayName("StrictProcessor type parameter is bound by both Serializable and Comparable") void strictProcessorTypeParameterIsBoundBySerializableAndComparable() { var typeParameters = StrictProcessor.class.getTypeParameters(); var typeParam = typeParameters[0]; assertThat(typeParam.getBounds()) .hasSize(2) .extracting(Type::getTypeName) .containsExactlyInAnyOrder(Serializable.class.getTypeName(), String.format("%s", Comparable.class.getTypeName(), TYPE_PARAMETER_NAME)); } @Test @Order(25) @DisplayName("StrictProcessor process method parameter has type \"T\"") void strictProcessorProcessMethodParameterHasTypeT() { var processMethod = getMethodByName(StrictProcessor.class, "process"); assert (processMethod.getParameters().length == 1); var processMethodParam = processMethod.getParameters()[0]; assertThat(processMethodParam.getParameterizedType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(26) @DisplayName("CollectionRepository has two type parameters") void collectionRepositoryHasTwoTypeParameters() { var typeParameters = CollectionRepository.class.getTypeParameters(); assertThat(typeParameters).hasSize(2); } @Test @Order(27) @DisplayName("CollectionRepository first type parameter is called \"T\"") void collectionRepositoryFirstTypeParameterIsCalledT() { var typeParam = CollectionRepository.class.getTypeParameters()[0]; assertThat(typeParam.getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(28) @DisplayName("CollectionRepository first type parameter is bounded by BaseEntity") void collectionRepositoryFirstTypeParameterIsBoundedByBaseEntity() { var typeParam = CollectionRepository.class.getTypeParameters()[0]; var boundType = typeParam.getBounds()[0]; assertThat(boundType.getTypeName()).isEqualTo(BaseEntity.class.getTypeName()); } @Test @Order(29) @DisplayName("CollectionRepository second type parameter is called \"C\"") void collectionRepositorySecondTypeParameterIsCalledT() { var typeParam = CollectionRepository.class.getTypeParameters()[1]; assertThat(typeParam.getTypeName()).isEqualTo("C"); } @Test @Order(30) @DisplayName("CollectionRepository second type parameter is bounded by Collection") void collectionRepositorySecondTypeParameterIsBoundedByCollection() { var typeParam = CollectionRepository.class.getTypeParameters()[1]; var boundType = typeParam.getBounds()[0]; assertThat(boundType.getTypeName()) .isEqualTo(String.format("%s", Collection.class.getTypeName())); } @Test @Order(31) @DisplayName("CollectionRepository save method param has type \"T\"") @SneakyThrows void collectionRepositorySaveMethodParameterHasTypeT() { var saveMethod = getMethodByName(CollectionRepository.class, "save"); var methodParam = saveMethod.getParameters()[0]; assertThat(methodParam.getParameterizedType().getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(32) @DisplayName("CollectionRepository getEntityCollection method has return type \"C\"") @SneakyThrows void collectionRepositoryGetCollectionMethodHasReturnTypeC() { var getEntityCollectionMethod = CollectionRepository.class.getMethod("getEntityCollection"); assertThat(getEntityCollectionMethod.getGenericReturnType().getTypeName()).isEqualTo("C"); } @Test @Order(33) @DisplayName("ListRepository extends CollectionRepository") void listRepositoryExtendsCollectionRepository() { var superInterface = ListRepository.class.getInterfaces()[0]; assertThat(superInterface.getTypeName()).isEqualTo(CollectionRepository.class.getTypeName()); } @Test @Order(34) @DisplayName("ListRepository has one type parameter") void listRepositoryHasOneTypeParameter() { var typeParams = ListRepository.class.getTypeParameters(); assertThat(typeParams).hasSize(1); } @Test @Order(35) @DisplayName("ListRepository type parameter is called \"T\"") void listRepositoryTypeParameterIsCalledT() { var typeParam = ListRepository.class.getTypeParameters()[0]; assertThat(typeParam.getTypeName()).isEqualTo(TYPE_PARAMETER_NAME); } @Test @Order(36) @DisplayName("ListRepository type parameter is bound by BaseEntity") void listRepositoryTypeParameterIsBoundByBaseEntity() { var typeParam = ListRepository.class.getTypeParameters()[0]; var boundType = typeParam.getBounds()[0]; assertThat(boundType.getTypeName()).isEqualTo(BaseEntity.class.getTypeName()); } @Test @Order(37) @DisplayName("ListRepository parent interface has specified two types") void listRepositoryParentInterfaceHasSpecifiedTwoTypes() { var collectionInterface = ListRepository.class.getGenericInterfaces()[0]; assertThat(collectionInterface.getTypeName()) .isEqualTo(String.format("%s>", CollectionRepository.class.getTypeName(), List.class.getTypeName())); } @Test @Order(38) @DisplayName("ComparableCollection has one type parameter \"E\"") void comparableCollectionIsGeneric() { var typeParameters = ComparableCollection.class.getTypeParameters(); assertThat(typeParameters).hasSize(1); assertThat(typeParameters[0].getName()).isEqualTo(COLLECTION_ELEMENT_TYPE_PARAMETER_NAME); } @Test @Order(39) @DisplayName("ComparableCollection extends Collection") void comparableCollectionExtendsCollection() { var collectionInterface = Arrays.stream(ComparableCollection.class.getInterfaces()) .filter(it -> it.getTypeName().equals(Collection.class.getTypeName())) .findAny() .orElseThrow(); assertThat(collectionInterface).isNotNull(); } @Test @Order(40) @DisplayName("Type parameter is specified for a super interface Collection") void comparableCollectionExtendsCollectionOfTheSameElementsType() { var collectionInterface = Arrays.stream(ComparableCollection.class.getGenericInterfaces()) .filter(it -> it.getTypeName().equals( String.format("%s<%s>", Collection.class.getTypeName(), COLLECTION_ELEMENT_TYPE_PARAMETER_NAME) )) .findAny() .orElseThrow(); assertThat(collectionInterface).isNotNull(); } @Test @Order(41) @DisplayName("ComparableCollection extends Comparable") void comparableCollectionExtendsComparable() { var comparableInterface = Arrays.stream(ComparableCollection.class.getInterfaces()) .filter(it -> it.getTypeName().equals(Comparable.class.getTypeName())) .findAny() .orElseThrow(); assertThat(comparableInterface).isNotNull(); } @Test @Order(42) @DisplayName("Collection of any type is specified as type parameter for a super interface Comparable") void comparableCollectionExtendsComparableOfCollectionOfAnyType() { var comparableInterface = Arrays.stream(ComparableCollection.class.getGenericInterfaces()) .filter(it -> it.getTypeName().equals( String.format("%s<%s>", Comparable.class.getTypeName(), Collection.class.getTypeName()) )) .findAny() .orElseThrow(); assertThat(comparableInterface).isNotNull(); } @Test @Order(43) @DisplayName("Method compareTo is overridden") void comparableCollectionOverridesCompareToMethod() { var compareToMethod = Arrays.stream(ComparableCollection.class.getDeclaredMethods()) .filter(m -> m.getName().equals("compareTo")) .findAny(); assertThat(compareToMethod).isPresent(); } @Test @Order(44) @DisplayName("ComparableCollection provides a default impl of compareTo method") void compareToProvidesDefaultImpl() { var compareToMethod = getMethodByName(ComparableCollection.class, "compareTo"); assertThat(compareToMethod.isDefault()).isTrue(); } @Test @Order(45) @DisplayName("A parameter of method compareTo is a collection of elements of any type") void compareToParamIsACollectionOfAnyType() { var compareToMethod = getMethodByName(ComparableCollection.class, "compareTo"); var collectionParam = compareToMethod.getParameters()[0]; assertThat(collectionParam.getParameterizedType().getTypeName()) .isEqualTo(String.format("%s", Collection.class.getTypeName())); } @Order(46) @DisplayName("Method compareTo compares collection size") @SneakyThrows @ParameterizedTest @ValueSource(ints = {0, 5, 10}) void compareToComparesSize(int size) { var compareToMethod = getMethodByName(ComparableCollection.class, "compareTo"); var compCollectionMock = Mockito.spy(ComparableCollection.class); var sizeMethod = getMethodByName(ComparableCollection.class, "size"); when(sizeMethod.invoke(compCollectionMock)).thenReturn(size); var list = List.of(1, 2, 3, 4, 5); assertThat(compareToMethod.invoke(compCollectionMock, list)) .isEqualTo(Integer.compare(size, list.size())); } @Test @Order(47) @DisplayName("CollectionUtil is not a generic class") void collectionUtilIsNotAGenericClass() { var typeParams = CollectionUtil.class.getTypeParameters(); assertThat(typeParams).isEmpty(); } @Test @Order(48) @DisplayName("Method print param is a list of any type declared as unbounded wildcard") void printParamIsAListOfAnyType() { var printMethod = getMethodByName(CollectionUtil.class, "print"); var listParam = printMethod.getParameters()[0]; var typeName = listParam.getParameterizedType().getTypeName(); assertThat(typeName).isEqualTo(String.format("%s", List.class.getTypeName())); } @Test @Order(49) @DisplayName("Method hasNewEntities accepts a collection of any entities (BaseEntity subclasses)") void hasNewEntitiesMethodParamIsAGenericCollectionOfEntities() { var hasNewEntitiesMethod = getMethodByName(CollectionUtil.class, "hasNewEntities"); var entitiesParam = hasNewEntitiesMethod.getParameters()[0]; assertThat(entitiesParam.getParameterizedType().getTypeName()) .isEqualTo(String.format("%s", Collection.class.getTypeName(), BaseEntity.class.getTypeName())); } @ParameterizedTest @Order(50) @MethodSource("hasNewEntitiesArgs") @DisplayName("Method hasNewEntities checks id values") @SneakyThrows void hasNewEntitiesChecksIds(Collection entities, boolean result) { var hasNewEntitiesMethod = getMethodByName(CollectionUtil.class, "hasNewEntities"); boolean hasNewEntities = (boolean) hasNewEntitiesMethod.invoke(null, entities); assertThat(hasNewEntities).isEqualTo(result); } private Method getMethodByName(Class clazz, String methodName) { return Arrays.stream(clazz.getMethods()) .filter(m -> m.getName().equals(methodName)) .findAny().orElseThrow(); } static Stream hasNewEntitiesArgs() { var newEntity = Mockito.mock(BaseEntity.class); when(newEntity.getUuid()).thenReturn(null); var oldEntity = Mockito.mock(BaseEntity.class); when(oldEntity.getUuid()).thenReturn(UUID.randomUUID()); return Stream.of( arguments(List.of(newEntity, newEntity), true), arguments(List.of(newEntity, oldEntity), true), arguments(List.of(oldEntity, oldEntity), false) ); } @Test @Order(51) @DisplayName("Method isValidCollection accepts a collection of any entities as a first param") void isValidCollectionMethodFirstParamIsAGenericCollectionOfEntities() { var isValidCollectionMethod = getMethodByName(CollectionUtil.class, "isValidCollection"); var entitiesParam = isValidCollectionMethod.getParameters()[0]; assertThat(entitiesParam.getParameterizedType().getTypeName()) .isEqualTo(String.format("%s", Collection.class.getTypeName(), BaseEntity.class.getTypeName())); } @Test @Order(52) @DisplayName("Method isValidCollection accepts a predicate of any BaseEntity superclasses as a second param") void isValidCollectionMethodSecondParamIsAnyBaseEntitySuperClass() { var isValidCollectionMethod = getMethodByName(CollectionUtil.class, "isValidCollection"); var validationPredicate = isValidCollectionMethod.getParameters()[1]; assertThat(validationPredicate.getParameterizedType().getTypeName()) .isEqualTo(String.format("%s", Predicate.class.getTypeName(), BaseEntity.class.getTypeName())); } @ParameterizedTest @MethodSource("isValidCollectionArgs") @Order(53) @DisplayName("Method isValidCollection returns true when all entities pass validation predicate") @SneakyThrows void isValidCollectionReturnsTrueWhenAllEntitiesPassValidationPredicate(List entities, Predicate validationPredicate, boolean isValid) { var isValidCollectionMethod = getMethodByName(CollectionUtil.class, "isValidCollection"); boolean result = (boolean) isValidCollectionMethod.invoke(null, entities, validationPredicate); assertThat(result).isEqualTo(isValid); } private static Stream isValidCollectionArgs() { var now = LocalDateTime.now(); Predicate notNullIdValidationPredicate = e -> e.getUuid() != null; Predicate pastCreatedDateValidationPredicate = e -> e.getCreatedOn().compareTo(now) < 0; return Stream.of( arguments(List.of(new TestEntity(), new TestEntity()), notNullIdValidationPredicate, true), arguments(List.of(new TestEntity(), new TestEntity((UUID) null)), notNullIdValidationPredicate, false), arguments( List.of( new TestEntity(now.minus(1, DAYS)), new TestEntity((now.minus(1, MONTHS))) ), pastCreatedDateValidationPredicate, true ), arguments( List.of( new TestEntity(now.minus(1, DAYS)), new TestEntity((now.plus(2, DAYS))) ), pastCreatedDateValidationPredicate, false ) ); } @Test @Order(54) @DisplayName("hasDuplicates is a generic method") void hasDuplicatesIsAGenericMethod() { var hasDuplicatesMethod = getMethodByName(CollectionUtil.class, "hasDuplicates"); assertThat(hasDuplicatesMethod.getTypeParameters()).hasSize(1); } @Test @Order(55) @DisplayName("hasDuplicates type parameter is called \"T\"") void hasDuplicatesTypeParameterIsCalledT() { var hasDuplicatesMethod = getMethodByName(CollectionUtil.class, "hasDuplicates"); var typeParameter = hasDuplicatesMethod.getTypeParameters()[0]; assertThat(typeParameter.getName()).isEqualTo("T"); } @Test @Order(56) @DisplayName("hasDuplicates type parameter is bounded by BaseEntity") void hasDuplicatesTypeParameterIsBoundedByBaseEntity() { var hasDuplicatesMethod = getMethodByName(CollectionUtil.class, "hasDuplicates"); var typeParameter = hasDuplicatesMethod.getTypeParameters()[0]; Type bound = typeParameter.getBounds()[0]; assertThat(bound.getTypeName()).isEqualTo(BaseEntity.class.getTypeName()); } @ParameterizedTest @Order(57) @MethodSource("hasDuplicatesArgs") @DisplayName("hasDuplicates checks entity duplicates by UUID") @SneakyThrows void hasDuplicatesChecksEntitiesByUUID(List entities, BaseEntity targetEntity, Boolean hasDuplicates) { var hasDuplicatesMethod = getMethodByName(CollectionUtil.class, "hasDuplicates"); var result = hasDuplicatesMethod.invoke(null, entities, targetEntity); assertThat(result).isEqualTo(hasDuplicates); } static Stream hasDuplicatesArgs() { var uniqueEntity = new TestEntity(UUID.randomUUID()); var duplicateId = UUID.randomUUID(); var duplicateEntity1 = new TestEntity(duplicateId); var duplicateEntity2 = new TestEntity(duplicateId); return Stream.of( arguments(List.of(uniqueEntity, duplicateEntity1), uniqueEntity, false), arguments(List.of(uniqueEntity, duplicateEntity1, duplicateEntity2), duplicateEntity1, true), arguments(List.of(duplicateEntity1, duplicateEntity2), duplicateEntity1, true) ); } @Test @Order(58) @DisplayName("Method findMax has public access type") void findMaxHasPublicAccessType() { var findMaxMethod = getMethodByName(CollectionUtil.class, "findMax"); assertThat(findMaxMethod).isNotNull(); } @ParameterizedTest @Order(59) @MethodSource("findMaxArgs") @DisplayName("Method findMax returns the max value based on given comparator") @SneakyThrows void findMaxMethodReturnMaxValue(List elements, Comparator comparator, Object maxElement) { var findMaxMethod = getMethodByName(CollectionUtil.class, "findMax"); var result = findMaxMethod.invoke(null, elements, comparator); assertThat(result).isEqualTo(Optional.of(maxElement)); } static Stream findMaxArgs() { var maxEntity = new TestEntity(LocalDateTime.now().plus(10, DAYS)); var entities = new ArrayList<>(List.of(new TestEntity(), new TestEntity(), maxEntity, new TestEntity())); var entityCreatedOnComparator = Comparator.comparing(BaseEntity::getCreatedOn); var maxNumber = 100; var numbers = List.of(maxNumber, 5, 20); var intComparator = Comparator.comparingInt(Integer::intValue); var maxWord = "what's up?"; var greetings = List.of("hey", maxWord, "hello"); var lengthComparator = Comparator.comparing(String::length); return Stream.of( arguments(entities, entityCreatedOnComparator, maxEntity), arguments(numbers, intComparator, maxNumber), arguments(greetings, lengthComparator, maxWord) ); } @Test @Order(60) @DisplayName("Method findMostRecentlyCreatedEntity has public access type") void findMostRecentlyCreatedEntityHasPublicAccessType() { var findMostRecentlyCreatedEntity = getMethodByName(CollectionUtil.class, "findMostRecentlyCreatedEntity"); assertThat(findMostRecentlyCreatedEntity).isNotNull(); } @Test @Order(61) @DisplayName("findMostRecentlyCreatedEntity is a generic method that accepts a collection of entities") void findMostRecentlyCreatedEntityIsAGenericMethod() { var hasDuplicatesMethod = getMethodByName(CollectionUtil.class, "findMostRecentlyCreatedEntity"); var typeParameter = hasDuplicatesMethod.getTypeParameters()[0]; var bound = typeParameter.getBounds()[0]; assertThat(typeParameter.getName()).isEqualTo("T"); assertThat(bound.getTypeName()).isEqualTo(BaseEntity.class.getTypeName()); } @ParameterizedTest @Order(62) @MethodSource("findMostRecentlyCreatedEntityArgs") @DisplayName("findMostRecentlyCreatedEntity returns the most recently created entity") @SneakyThrows void findMostRecentlyCreatedEntityReturnsEntityWithMaxCreatedOnValue(List entities, BaseEntity mostRecentlyCreatedEntity) { var findMostRecentlyCreatedEntityMethod = getMethodByName(CollectionUtil.class, "findMostRecentlyCreatedEntity"); var result = findMostRecentlyCreatedEntityMethod.invoke(null, entities); assertThat(result).isEqualTo(mostRecentlyCreatedEntity); } static Stream findMostRecentlyCreatedEntityArgs() { var yearAgoEntity = new TestEntity(LocalDateTime.now().minus(1, YEARS)); var monthAgoEntity = new TestEntity(LocalDateTime.now().minus(1, MONTHS)); var dayAgoEntity = new TestEntity(LocalDateTime.now().minus(1, DAYS)); return Stream.of( arguments(List.of(yearAgoEntity, monthAgoEntity, dayAgoEntity), dayAgoEntity), arguments(List.of(yearAgoEntity, monthAgoEntity), monthAgoEntity), arguments(List.of(yearAgoEntity, dayAgoEntity), dayAgoEntity), arguments(List.of(dayAgoEntity, monthAgoEntity), dayAgoEntity) ); } @Test @Order(63) @DisplayName("findMostRecentlyCreatedEntity throws exception when collection is empty") @SneakyThrows void findMostRecentlyCreatedEntityThrowsException() { var findMostRecentlyCreatedEntityMethod = getMethodByName(CollectionUtil.class, "findMostRecentlyCreatedEntity"); assertThatThrownBy(() -> findMostRecentlyCreatedEntityMethod.invoke(null, Collections.emptyList())) .hasCauseInstanceOf(NoSuchElementException.class); } @Test @Order(64) @DisplayName("Method swap does not declare type parameter") void swapMethodDoesNotDeclareTypeParameter() { var swapMethod = getMethodByName(CollectionUtil.class, "swap"); var typeParameters = swapMethod.getTypeParameters(); assertThat(typeParameters).isEmpty(); } @Test @Order(65) @DisplayName("Method swap change elements by indexes") @SneakyThrows void swapChangesElements() { var testEntity1 = new TestEntity(); var testEntity2 = new TestEntity(); var entities = new ArrayList<>(List.of(new TestEntity(), testEntity1, testEntity2, new TestEntity())); var swapMethod = getMethodByName(CollectionUtil.class, "swap"); swapMethod.invoke(null, entities, 1, 2); assertThat(entities.get(1)).isEqualTo(testEntity2); assertThat(entities.get(2)).isEqualTo(testEntity1); } static class TestEntity extends BaseEntity { public TestEntity() { this(UUID.randomUUID()); } public TestEntity(UUID uuid) { super(uuid); } public TestEntity(LocalDateTime createdOn) { super(UUID.randomUUID(), createdOn); } } } ================================================ FILE: 1-0-java-basics/1-3-2-heterogeneous-max-holder/README.MD ================================================ # Heterogeneous Max Holder #### Improve your generics-related skills implementing a multi-type max holder container 💪 ### Objectives * create a container class that uses *a type token* ✅ * declare a generic type parameter that is `Comparable` ✅ * implement a generic method put that stores max values by its type ✅ * overload a generic method put with a custom `Comparator` ✅ * implement a generic method getMax that retrieves a max value by its type ✅ * wrap a comparator instance to make it null-safe ✅ ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/jQnGDgH-Zfg/0.jpg)](https://www.youtube.com/watch?v=jQnGDgH-Zfg) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 1-0-java-basics/1-3-2-heterogeneous-max-holder/pom.xml ================================================ 1-0-java-basics com.bobocode 1.0-SNAPSHOT 4.0.0 1-3-2-heterogeneous-max-holder ================================================ FILE: 1-0-java-basics/1-3-2-heterogeneous-max-holder/src/main/java/com/bobocode/basics/HeterogeneousMaxHolder.java ================================================ package com.bobocode.basics; import java.util.Map; /** * {@link HeterogeneousMaxHolder} is a multi-type container that holds maximum values per each type. It's kind of a * key/value map, where the key is a type and the value is the maximum among all values of this type that were put. *

* It's based on the {@link Map} and provides an API that allows to put a value by type, and get a max value by type. *

*

* TODO: to get the most out of your learning, visit our website *

* * @author Taras Boychuk */ public class HeterogeneousMaxHolder { /** * A method put stores a provided value by its type, if the value is greater than the current maximum. In other words, the logic * of this method makes sure that only max value is stored and everything else is ignored. *

* If the current max value is less than a provided one, or if it's null, then a provided value gets stored and the old * max is returned. Otherwise, nothing new is added, and the provided value is returned. *

* So technically, this method always stores the greater value and returns the smaller one. * * @param key a provided value type * @param value a value to put * @param value type parameter * @return a smaller value among the provided value and the current maximum */ // todo: implement a method according to javadoc /** * An overloaded method put implements the same logic using a custom comparator. A given comparator is wrapped with * a null-safe comparator, considering null smaller than any non-null object. *

* All arguments must not be null. * * @param key a provided value type * @param value a value to put * @param comparator a custom comparator for the given type * @param value type parameter * @return a smaller value among the provided value and the current maximum */ // todo: implement a method according to javadoc /** * A method getMax returns a max value by the given type. If no value is stored by this type, then it returns null. * * @param key a provided value type * @param value type parameter * @return current max value or null */ // todo: implement a method according to javadoc } ================================================ FILE: 1-0-java-basics/1-3-2-heterogeneous-max-holder/src/test/java/com/bobocode/basics/HeterogeneousMaxHolderTest.java ================================================ package com.bobocode.basics; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import lombok.SneakyThrows; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.mockito.Mockito; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.math.BigDecimal; import java.util.Arrays; import java.util.Comparator; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; @TestMethodOrder(OrderAnnotation.class) class HeterogeneousMaxHolderTest { private HeterogeneousMaxHolder heterogeneousMaxHolder = new HeterogeneousMaxHolder(); private HeterogeneousMaxHolder heterogeneousMaxHolderMock = Mockito.spy(HeterogeneousMaxHolder.class); @Test @Order(1) @DisplayName("A class does not declare type parameters") void classDoesNotDeclareTypeParameters() { var classTypeParams = HeterogeneousMaxHolder.class.getTypeParameters(); assertThat(classTypeParams).isEmpty(); } @Test @Order(2) @DisplayName("A class has one private field Map") void classHasOneField() { var fields = HeterogeneousMaxHolder.class.getDeclaredFields(); assertThat(fields).hasSize(1); assertThat(fields[0].getType()).isEqualTo(Map.class); } @Test @Order(3) @DisplayName("A Map field declares type arguments") void mapFieldStoresTypeToObject() { var field = HeterogeneousMaxHolder.class.getDeclaredFields()[0]; assertThat(field.getGenericType().getTypeName()) .isEqualTo(Map.class.getTypeName() + "<%s, %s>", Class.class.getTypeName() + "", Object.class.getTypeName()); } @Test @Order(4) @DisplayName("put method exists") void putExists() { var putMethodExists = Arrays.stream(HeterogeneousMaxHolder.class.getDeclaredMethods()) .anyMatch(m -> m.getName().equals("put")); assertTrue(putMethodExists); } @Test @Order(5) @DisplayName("put method declares one type parameter T") void putDeclaresOneTypeParam() { var putMethod = getPutMethod(); var methodTypeParameters = putMethod.getTypeParameters(); assertThat(methodTypeParameters).hasSize(1); var typeParam = putMethod.getTypeParameters()[0]; assertThat(typeParam.getName()).isEqualTo("T"); } @Test @Order(6) @DisplayName("put method accepts type-safe key") void putMethodAcceptsTypeSafeKeyParameter() { var putMethod = getPutMethod(); var typeParam = (ParameterizedType) putMethod.getGenericParameterTypes()[0]; var typeArgument = typeParam.getActualTypeArguments()[0]; assertThat(typeParam.getRawType()).isEqualTo(Class.class); assertThat(typeArgument.getTypeName()).isEqualTo("T"); } @Test @Order(7) @DisplayName("put method accepts comparable value") void putMethodAcceptsComparableValueParameter() { var putMethod = getPutMethod(); var typeParam = putMethod.getTypeParameters()[0]; var boundType = (ParameterizedType) typeParam.getBounds()[0]; assertThat(boundType.getRawType()).isEqualTo(Comparable.class); } @Test @Order(8) @DisplayName("put method supports value that has comparable super class") void putMethodAcceptsValueParameterWithComparableSuperClass() { var putMethod = getPutMethod(); var typeParam = putMethod.getTypeParameters()[0]; var boundType = (ParameterizedType) typeParam.getBounds()[0]; var typeArgument = boundType.getActualTypeArguments()[0].getTypeName(); assertThat(boundType.getRawType()).isEqualTo(Comparable.class); assertThat(typeArgument).isEqualTo("? super T"); } @Test @Order(9) @SneakyThrows @DisplayName("put stores provided value when current max is null") void putStoresValueWhenCurrentMaxIsNull() { callPut(String.class, "I am maximum"); var storedMaxValue = getMaxHelper(String.class); assertThat(storedMaxValue).isEqualTo("I am maximum"); } @Test @Order(10) @SneakyThrows @DisplayName("put returns null when current max is null") void putReturnsNullWhenCurrentMaxIsNull() { var result = callPut(String.class, "I am maximum"); assertThat(result).isNull(); } @Test @Order(11) @SneakyThrows @DisplayName("put stores provided value when current max is smaller than it") void putStoresValueWhenCurrentMaxIsSmaller() { givenMaxHolderWithData(Long.class, 123L); callPut(Long.class, 222L); var storedMaxValue = getMaxHelper(Long.class); assertThat(storedMaxValue).isEqualTo(222L); } @Test @Order(12) @SneakyThrows @DisplayName("put returns old max value when the provided value is greater than it") void putReturnsOldMaxValue() { givenMaxHolderWithData(Long.class, 123L); var returnedValue = callPut(Long.class, 222L); assertThat(returnedValue).isEqualTo(123L); } @Test @Order(13) @SneakyThrows @DisplayName("put ignores provided value when the current max is greater than it") void putIgnoresNewValueWhenCurrentMaxIsGreater() { givenMaxHolderWithData(Long.class, 123L); callPut(Long.class, 101L); var storedMaxValue = getMaxHelper(Long.class); assertThat(storedMaxValue).isEqualTo(123L); } @Test @Order(14) @SneakyThrows @DisplayName("put returns provided value when the current max is greater than it") void putReturnsProvidedValueWhenCurrentMaxIsGreater() { givenMaxHolderWithData(Long.class, 123L); var returnedValue = callPut(Long.class, 101L); assertThat(returnedValue).isEqualTo(101L); } @Test @Order(15) @SneakyThrows @DisplayName("put method is overloaded with additional Comparator parameter") void putIsOverloadedWithAdditionalComparatorParam() { HeterogeneousMaxHolder.class.getMethod("put", Class.class, Object.class, Comparator.class); } @Test @Order(16) @DisplayName("Overloaded put method declares one type parameter T") void overloadedPutDeclaresOneTypeParam() { var putMethod = getOverloadedPutMethod(); var methodTypeParameters = putMethod.getTypeParameters(); assertThat(methodTypeParameters).hasSize(1); var typeParam = putMethod.getTypeParameters()[0]; assertThat(typeParam.getName()).isEqualTo("T"); } @Test @Order(17) @DisplayName("Overloaded put method accepts type-safe key") void overloadedPutMethodAcceptsTypeSafeKeyParameter() { var putMethod = getOverloadedPutMethod(); var typeParam = (ParameterizedType) putMethod.getGenericParameterTypes()[0]; var typeArgument = typeParam.getActualTypeArguments()[0]; assertThat(typeParam.getRawType()).isEqualTo(Class.class); assertThat(typeArgument.getTypeName()).isEqualTo("T"); } @Test @Order(18) @DisplayName("Overloaded put method accepts value of arbitrary type T") void overloadedPutMethodAcceptsAnyValue() { var putMethod = getOverloadedPutMethod(); var genericValueTypeParam = putMethod.getGenericParameterTypes()[1]; var actualValueTypeParm = putMethod.getParameterTypes()[1]; assertThat(genericValueTypeParam.getTypeName()).isEqualTo("T"); assertThat(actualValueTypeParm).isEqualTo(Object.class); } @Test @Order(19) @SneakyThrows @DisplayName("Overloaded put method supports comparator of a super type") void overloadedPutAcceptsComparatorOfSuperTypes() { var putMethod = HeterogeneousMaxHolder.class.getMethod("put", Class.class, Object.class, Comparator.class); var comparatorParam = (ParameterizedType) putMethod.getGenericParameterTypes()[2]; var comparatorTypeArgument = comparatorParam.getActualTypeArguments()[0]; assertThat(comparatorTypeArgument.getTypeName()).isEqualTo("? super T"); } @Test @Order(20) @SneakyThrows @DisplayName("Overloaded put stores provided value when current max is null") void overloadedPutStoresValueWhenCurrentMaxIsNull() { var account = Accounts.generateAccount(); callPut(Account.class, account, Comparator.comparing(Account::getBalance)); var storedMaxValue = getMaxHelper(Account.class); assertThat(storedMaxValue).isEqualTo(account); } @Test @Order(21) @SneakyThrows @DisplayName("Overloaded put returns null when current max is null") void overloadedPutReturnsNullWhenCurrentMaxIsNull() { var result = callPut(Account.class, Accounts.generateAccount(), Comparator.comparing(Account::getBalance)); assertThat(result).isNull(); } @Test @Order(22) @SneakyThrows @DisplayName("Overloaded put stores provided value when current max is smaller than it") void overloadedPutStoresValueWhenCurrentMaxIsSmaller() { var givenAccount = Accounts.generateAccount(); givenMaxHolderWithData(Account.class, givenAccount); var biggerBalanceAccount = Accounts.generateAccount(); biggerBalanceAccount.setBalance(givenAccount.getBalance().add(BigDecimal.TEN)); callPut(Account.class, biggerBalanceAccount, Comparator.comparing(Account::getBalance)); var storedMaxValue = getMaxHelper(Account.class); assertThat(storedMaxValue).isEqualTo(biggerBalanceAccount); } @Test @Order(23) @SneakyThrows @DisplayName("Overloaded put returns old max value when the provided value is greater than it") void overloadedPutReturnsOldMaxValue() { var givenAccount = Accounts.generateAccount(); givenMaxHolderWithData(Account.class, givenAccount); var biggerBalanceAccount = Accounts.generateAccount(); biggerBalanceAccount.setBalance(givenAccount.getBalance().add(BigDecimal.TEN)); var returnedValue = callPut(Account.class, biggerBalanceAccount, Comparator.comparing(Account::getBalance)); assertThat(returnedValue).isEqualTo(givenAccount); } @Test @Order(24) @SneakyThrows @DisplayName("Overloaded put ignores provided value when the current max is greater than it") void overloadedPutIgnoresNewValueWhenCurrentMaxIsGreater() { var givenAccount = Accounts.generateAccount(); givenMaxHolderWithData(Account.class, givenAccount); var smallerBalanceAccount = Accounts.generateAccount(); smallerBalanceAccount.setBalance(givenAccount.getBalance().subtract(BigDecimal.TEN)); callPut(Account.class, smallerBalanceAccount, Comparator.comparing(Account::getBalance)); var storedMaxValue = getMaxHelper(Account.class); assertThat(storedMaxValue).isEqualTo(givenAccount); } @Test @Order(25) @SneakyThrows @DisplayName("Overloaded put returns provided value when the current max is greater") void overloadedPutReturnsProvidedValueWhenCurrentMaxIsGreater() { var givenAccount = Accounts.generateAccount(); givenMaxHolderWithData(Account.class, givenAccount); var smallerBalanceAccount = Accounts.generateAccount(); smallerBalanceAccount.setBalance(givenAccount.getBalance().subtract(BigDecimal.TEN)); var returnedValue = callPut(Account.class, smallerBalanceAccount, Comparator.comparing(Account::getBalance)); assertThat(returnedValue).isEqualTo(smallerBalanceAccount); } @Test @Order(26) @DisplayName("getMax method exists") void getMaxExists() { var getMaxMethodExists = Arrays.stream(HeterogeneousMaxHolder.class.getDeclaredMethods()) .anyMatch(m -> m.getName().equals("getMax")); assertThat(getMaxMethodExists).isTrue(); } @Test @Order(27) @DisplayName("getMax declares one simple type param 'T'") void getMaxDeclaresOneTypeParam() { var getMaxMethod = getGetMaxMethod(); var typeParams = getMaxMethod.getTypeParameters(); assertThat(typeParams).hasSize(1); assertThat(typeParams[0].getTypeName()).isEqualTo("T"); } @Test @Order(28) @DisplayName("getMax has one parameter") void getMaxHasOneParameter() { var getMaxMethod = getGetMaxMethod(); var parameters = getMaxMethod.getParameters(); assertThat(parameters).hasSize(1); assertThat(parameters[0].getType()).isEqualTo(Class.class); } @Test @Order(29) @DisplayName("getMax param specifies type arguments") void getMaxParamSpecifyTypeArguments() { var getMaxMethod = getGetMaxMethod(); var parameter = getMaxMethod.getParameters()[0]; assertThat(parameter.getParameterizedType().getTypeName()).isEqualTo(Class.class.getTypeName() + ""); } @Test @Order(30) @DisplayName("getMax returns value when it exists") void getMaxReturnsValueWhenItExists() { givenMaxHolderWithData(String.class, "I am maximum"); var returnedValue = callGetMax(String.class); assertThat(returnedValue).isEqualTo("I am maximum"); } @Test @Order(31) @DisplayName("getMax returns value when it exists") void getMaxReturnsNullWhenNoValueByGivenTypeExists() { var returnedValue = callGetMax(String.class); assertThat(returnedValue).isNull(); } @Test @Order(32) @DisplayName("HeterogeneousMaxHolder keeps track of value one per each type") void maxHolderKeepsTrackOfMultipleValuesPerType() { callPut(String.class, "A"); callPut(String.class, "C"); callPut(String.class, "B"); callPut(Long.class, 1L); callPut(Long.class, 5L); callPut(Long.class, 25L); var stringMax = callGetMax(String.class); var longMax = callGetMax(Long.class); assertThat(stringMax).isEqualTo("C"); assertThat(longMax).isEqualTo(25L); } @SneakyThrows private T callGetMax(Class type) { var getMaxMethod = getGetMaxMethod(); var result = getMaxMethod.invoke(heterogeneousMaxHolder, type); return type.cast(result); } private Method getGetMaxMethod() { return Arrays.stream(HeterogeneousMaxHolder.class.getDeclaredMethods()) .filter(m -> m.getName().equals("getMax")) .findAny() .orElseThrow(); } private Method getPutMethod() { return Arrays.stream(HeterogeneousMaxHolder.class.getDeclaredMethods()) .filter(m -> m.getName().equals("put")) .filter(m -> m.getParameters().length <= 2) .findAny() .orElseThrow(); } private Method getOverloadedPutMethod() { return Arrays.stream(HeterogeneousMaxHolder.class.getDeclaredMethods()) .filter(m -> m.getName().equals("put")) .filter(m -> m.getParameters().length == 3) .findAny() .orElseThrow(); } private void givenMaxHolderWithData(Class type, Object value) { var map = getInternalMap(); map.put(type, value); assertThat(getMaxHelper(type)).isEqualTo(value); } @SneakyThrows private T callPut(Class type, T value) { var putMethod = HeterogeneousMaxHolder.class.getMethod("put", Class.class, Comparable.class); return type.cast(putMethod.invoke(heterogeneousMaxHolder, type, value)); } @SneakyThrows private T callPut(Class type, T value, Comparator comparator) { var putMethod = HeterogeneousMaxHolder.class.getMethod("put", Class.class, Object.class, Comparator.class); return type.cast(putMethod.invoke(heterogeneousMaxHolder, type, value, comparator)); } @SneakyThrows private T getMaxHelper(Class type) { Map, Object> map = getInternalMap(); return type.cast(map.get(type)); } @SneakyThrows private Map, Object> getInternalMap() { var mapField = HeterogeneousMaxHolder.class.getDeclaredFields()[0]; mapField.setAccessible(true); @SuppressWarnings("unchecked") var map = (Map, Object>) mapField.get(heterogeneousMaxHolder); return map; } } ================================================ FILE: 1-0-java-basics/1-5-0-hello-annotations/README.MD ================================================ # Hello Annotations Learn annotations basics to better understand how the frameworks use them 💪 ### Objectives * **create a custom annotation** ✅ * specify **where it can be used** ([`@Target`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Target.html)) ✅ * specify **where its information can be accessed** ([`@Retention`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/annotation/Retention.html)) ✅ * add **annotation members** (methods that act like fields) ✅ * set **default value** for a member ✅ * **use custom annotation** on a class ✅ ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/KF1H2EOCdD4/0.jpg)](https://www.youtube.com/watch?v=KF1H2EOCdD4) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 1-0-java-basics/1-5-0-hello-annotations/pom.xml ================================================ 1-0-java-basics com.bobocode 1.0-SNAPSHOT 4.0.0 1-5-0-hello-annotations ================================================ FILE: 1-0-java-basics/1-5-0-hello-annotations/src/main/java/com/bobocode/basics/HelloAnnotationsExercise.java ================================================ package com.bobocode.basics; /** * {@link HelloAnnotationsExercise} is an exercise class that is marked with be corresponding @{@link Exercise} * annotation. The annotation value specifies exercise name "hello-annotation-basic". It does not specify any custom * complexity level, because this exercise is a basic, which correspond to the default value provided by annotation. *

* todo: Create an annotation @{@link Exercise}. * todo: Set its retention policy so it is visible at runtime * todo: Set its target so it can only be applied to a class * todo: Add String value that will store exercise name * todo: Add complexityLevel with a default {@link Level} basic * * @author Taras Boychuk */ public class HelloAnnotationsExercise { // todo: mark class with the annotation according to the javadoc } ================================================ FILE: 1-0-java-basics/1-5-0-hello-annotations/src/main/java/com/bobocode/basics/Level.java ================================================ package com.bobocode.basics; /** * Enum that lists all possible exercise complexity levels. */ public enum Level { BEGINNER, BASIC, ADVANCED, CRAZY } ================================================ FILE: 1-0-java-basics/1-5-0-hello-annotations/src/test/java/com/bobocode/basics/HelloAnnotationsExerciseTest.java ================================================ package com.bobocode.basics; import lombok.SneakyThrows; import org.junit.jupiter.api.*; import java.lang.annotation.*; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class HelloAnnotationsExerciseTest { @Test @Order(1) @DisplayName("Annotation @Exercise exists") void exerciseAnnotationExists() { assertThatCode(this::getExerciseAnnotation).doesNotThrowAnyException(); } @Test @Order(2) @DisplayName("@Exercise can be applied to classes and interfaces but not to methods & fields") @SneakyThrows void exerciseAnnotationCanBeAppliedForClasses() { var exerciseAnnotation = getExerciseAnnotation(); var target = exerciseAnnotation.getAnnotation(Target.class); assertThat(target.value()).hasSize(1); assertThat(target.value()[0]).isEqualTo(ElementType.TYPE); } @Test @Order(3) @DisplayName("@Exercise information is accessible at runtime") @SneakyThrows void exerciseAnnotationInfoIsAccessibleAtRuntime() { var exerciseAnnotation = getExerciseAnnotation(); var retention = exerciseAnnotation.getAnnotation(Retention.class); assertThat(retention.value()).isEqualTo(RetentionPolicy.RUNTIME); } @Test @Order(4) @DisplayName("@Exercise has declared value") @SneakyThrows void exerciseAnnotationHasValueDeclared() { var exerciseAnnotation = getExerciseAnnotation(); assertThatCode(() -> exerciseAnnotation.getDeclaredMethod("value")) .doesNotThrowAnyException(); } @Test @Order(4) @DisplayName("@Exercise has complexityLevel declared") @SneakyThrows void exerciseAnnotationHasComplexityDeclared() { var exerciseAnnotation = getExerciseAnnotation(); assertThatCode(() -> exerciseAnnotation.getDeclaredMethod("complexityLevel")) .doesNotThrowAnyException(); } @Test @Order(5) @DisplayName("@Exercise complexityLevel is BASIC by default") @SneakyThrows void exerciseAnnotationComplexityLevelDefaultValue() { var exerciseAnnotation = getExerciseAnnotation(); var complexityLevel = exerciseAnnotation.getDeclaredMethod("complexityLevel"); assertThat(complexityLevel.getDefaultValue()).isEqualTo(Level.BASIC); } @Test @Order(6) @DisplayName("HelloAnnotationExercise is marked as @Exercise with name \"hello-annotation-basic\"") @SneakyThrows void helloAnnotationExerciseIsAnnotatedWithExercise() { var exerciseAnnotationClass = getExerciseAnnotation(); var basicExerciseAnnotation = HelloAnnotationsExercise.class.getAnnotation(exerciseAnnotationClass); var valueMethod = exerciseAnnotationClass.getMethod("value"); var exerciseName = valueMethod.invoke(basicExerciseAnnotation); assertThat(exerciseName).isEqualTo("hello-annotation-basic"); } @SneakyThrows private Class getExerciseAnnotation() { return Class.forName("com.bobocode.basics.Exercise") .asSubclass(Annotation.class); } } ================================================ FILE: 1-0-java-basics/README.md ================================================ # Java Basics This module intentionally left empty and will be populated with new material in future ================================================ FILE: 1-0-java-basics/pom.xml ================================================ 4.0.0 pom 1-3-0-hello-generics 1-3-1-crazy-generics 1-3-2-heterogeneous-max-holder 1-5-0-hello-annotations com.bobocode java-fundamentals-exercises 1.0-SNAPSHOT 1-0-java-basics com.bobocode java-fundamentals-util 1.0-SNAPSHOT compile ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-1-node/README.MD ================================================ # Node exercise 💪 Build strong skill of creating and linking **nodes – building blocks** that are used in order to create **LinkedList**, **LinkedQueue** and other important data structures 💪 ### Pre-conditions ❗ You're supposed to be familiar **Java classes** and **generics** ### Objectives * implement a generic class `Node` ✅ * **link** two node objects ✅ * create a **list of linked nodes** ✅ * create a **circle of linked nodes** ✅ ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/Ot5ma8NXcS0/0.jpg)](https://www.youtube.com/watch?v=Ot5ma8NXcS0) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 2-0-data-structures-and-algorithms/2-2-1-node/pom.xml ================================================ 2-0-data-structures-and-algorithms com.bobocode 1.0-SNAPSHOT 4.0.0 2-2-1-node ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Node.java ================================================ package com.bobobode.cs; /** * Class {@link Node} is a very simple data structure that consists of an element itself and the reference to the next * node. An element can have any value since it's a generic. A reference to the next node allows to link {@link Node} * objects and build more comprehensive data structures on top of those liked nodes. * * @param a generic type T * @author Taras Boychuk */ public class Node { // todo: } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-1-node/src/main/java/com/bobobode/cs/Nodes.java ================================================ package com.bobobode.cs; import com.bobocode.util.ExerciseNotCompletedException; /** * A class that consists of static methods only and provides util methods for {@link Node}. *

* TODO: to get the most out of your learning, visit our website *

* * @author Taras Boychuk */ public class Nodes { private Nodes() { } /** * Creates a new instance of {@link Node} that holds provided element * * @param element any element of type T * @param generic type * @return a new instance of {@link Node} */ public static Node create(T element) { throw new ExerciseNotCompletedException(); // todo: } /** * Create a connection between first and second nodes, so object first stores a reference to the second. * * @param first any {@link Node} object * @param second any {@link Node} object * @param a genetic type */ public static void link(Node first, Node second) { throw new ExerciseNotCompletedException(); // todo: } /** * Creates two new {@link Node} objects using provided firstElement and secondElement, and create a connection * between those two elements so the first node will hold a reference to a second one. * * @param firstElement any element of type T * @param secondElement any element of type T * @param a genetic type * @return a reference to a first node created based on firstElement */ public static Node pairOf(T firstElement, T secondElement) { throw new ExerciseNotCompletedException(); // todo: } /** * Creates two new {@link Node} objects using provided firstElement and secondElement, and creates connections * between those nodes so the first node will hold a reference to a second one, and a second node will hold * a reference to the first one. * * @param firstElement any element of type T * @param secondElement any element of type T * @param generic type T * @return a reference to the first node */ public static Node closedPairOf(T firstElement, T secondElement) { throw new ExerciseNotCompletedException(); // todo: } /** * Creates a linked chain of {@link Node} objects based on provided elements. Creates a connection between those * nodes so each node will hold a reference to the next one in the chain. HINT: it's basically a linked list. * * @param elements a array of elements of type T * @param generic type T * @return a reference to the first element of the chain */ public static Node chainOf(T... elements) { throw new ExerciseNotCompletedException(); // todo: } /** * Creates a linked circle of {@link Node} objects based on provided elements. Creates a connection between those * nodes so each node will hold a reference to the next one in the chain, and the last one will hold a reference to * the first one. * * @param elements a array of elements of type T * @param generic type T * @return a reference to the first element of the chain */ public static Node circleOf(T... elements) { throw new ExerciseNotCompletedException(); // todo: } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-1-node/src/test/java/com/bobocode/cs/NodesTest.java ================================================ package com.bobocode.cs; import com.bobobode.cs.Node; import com.bobobode.cs.Nodes; import lombok.SneakyThrows; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class NodesTest { @Test @Order(1) void create() { int element = 5; Node node = Nodes.create(element); assertThat(getNodeElement(node)).isEqualTo(element); assertThat(getNodeNext(node)).isNull(); } @Test @Order(2) void link() { Node firstNode = createNodeOf(5); Node secondNode = createNodeOf(9); Node thirdNode = createNodeOf(100); setNodeNext(secondNode, thirdNode); Nodes.link(firstNode, secondNode); assertThat(getNodeNext(firstNode)).isEqualTo(secondNode); assertThat(getNodeNext(secondNode)).isEqualTo(thirdNode); } @Test @Order(3) void pair() { int firstElement = 8; int secondElement = 2; Node firstNode = Nodes.pairOf(firstElement, secondElement); Node secondNode = getNodeNext(firstNode); assertThat(getNodeElement(firstNode)).isEqualTo(firstElement); assertThat(getNodeElement(secondNode)).isEqualTo(secondElement); assertThat(getNodeNext(secondNode)).isNull(); } @Test @Order(4) void closedPair() { int firstElement = 8; int secondElement = 2; Node firstNode = Nodes.closedPairOf(firstElement, secondElement); Node secondNode = getNodeNext(firstNode); assertThat(getNodeElement(firstNode)).isEqualTo(firstElement); assertThat(getNodeElement(secondNode)).isEqualTo(secondElement); assertThat(getNodeNext(secondNode)).isEqualTo(firstNode); } @Test @Order(5) void chain() { int firstElement = 8; int secondElement = 1; int thirdElement = 13; int fourthElement = 5; Node firstNode = Nodes.chainOf(firstElement, secondElement, thirdElement, fourthElement); Node secondNode = getNodeNext(firstNode); Node thirdNode = getNodeNext(secondNode); Node fourthNode = getNodeNext(thirdNode); assertThat(getNodeElement(firstNode)).isEqualTo(firstElement); assertThat(getNodeElement(secondNode)).isEqualTo(secondElement); assertThat(getNodeElement(thirdNode)).isEqualTo(thirdElement); assertThat(getNodeElement(fourthNode)).isEqualTo(fourthElement); assertThat(getNodeNext(fourthNode)).isNull(); } @Test @Order(6) void circle() { int firstElement = 8; int secondElement = 1; int thirdElement = 13; int fourthElement = 5; Node firstNode = Nodes.circleOf(firstElement, secondElement, thirdElement, fourthElement); Node secondNode = getNodeNext(firstNode); Node thirdNode = getNodeNext(secondNode); Node fourthNode = getNodeNext(thirdNode); assertThat(getNodeElement(firstNode)).isEqualTo(firstElement); assertThat(getNodeElement(secondNode)).isEqualTo(secondElement); assertThat(getNodeElement(thirdNode)).isEqualTo(thirdElement); assertThat(getNodeElement(fourthNode)).isEqualTo(fourthElement); assertThat(getNodeNext(fourthNode)).isEqualTo(firstNode); } @SneakyThrows @SuppressWarnings("unchecked") private Node createNodeOf(int element) { Constructor constructor = Arrays.stream(Node.class.getDeclaredConstructors()) .findAny() .orElseThrow(); constructor.setAccessible(true); Node node; if (constructor.getParameters().length > 0) { node = (Node) constructor.newInstance(element); } else { node = (Node) constructor.newInstance(); setNodeElement(node, element); } return node; } @SneakyThrows @SuppressWarnings("unchecked") private T getNodeElement(Node node) { Field elementField = getAccessibleElementField(); return (T) elementField.get(node); } @SneakyThrows private void setNodeElement(Node node, T element) { Field elementField = getAccessibleElementField(); elementField.set(node, element); } @SneakyThrows @SuppressWarnings("unchecked") private Node getNodeNext(Node node) { Field nextField = getAccessibleNextField(); return (Node) nextField.get(node); } @SneakyThrows private void setNodeNext(Node node, Node next) { Field elementField = getAccessibleNextField(); elementField.set(node, next); } private Field getAccessibleElementField() { Field elementField = Arrays.stream(Node.class.getDeclaredFields()) .filter(field -> field.getType().equals(Object.class)) .findAny() .orElseThrow(); elementField.setAccessible(true); return elementField; } private Field getAccessibleNextField() { Field nextField = Arrays.stream(Node.class.getDeclaredFields()) .filter(field -> field.getType().equals(Node.class)) .findAny() .orElseThrow(); nextField.setAccessible(true); return nextField; } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-2-stack/README.md ================================================ # Stack 🥞 Learn the Stack data structure and gain deep understanding implementing it on your own 💪 ### WHY ❓ [Stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) is one of the most important data structures for software developers. It can be used in various algorithms, but the most important is that **JVM creates a stack for each thread.** 😯 Even if you don't know anything about concurrency, and you write a simple application, it is still **executed by one main thread.** So each thread has its own stack, and **that stack is used to store method frames.** 😲 A [method frame](https://docs.oracle.com/javase/specs/jvms/se16/html/jvms-2.html#jvms-2.6) is just a term. It simply means all the information we operate with, when executing a method. E.g. **when you pass some arguments, or create local variable, all that data is stored to the stack.** So, **every a method is called, a new stack frame is created and stored to the stack.** > It is important to understand Stack, because otherwise you won't be able to understand fundamental things like **how JVM executes methods** and **how it uses memory**. ### Objectives * implement a generic class `Node` ✅ * **push an element onto the stack** ✅ * **get an element from the stack** ✅ * maintain stack **size** ✅ --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 2-0-data-structures-and-algorithms/2-2-2-stack/pom.xml ================================================ 2-0-data-structures-and-algorithms com.bobocode 1.0-SNAPSHOT 4.0.0 2-2-2-stack ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-2-stack/src/main/java/com/bobocode/cs/LinkedStack.java ================================================ package com.bobocode.cs; import com.bobocode.cs.exception.EmptyStackException; import com.bobocode.util.ExerciseNotCompletedException; /** * {@link LinkedStack} is a stack implementation that is based on singly linked generic nodes. * A node is implemented as inner static class {@link Node}. *

* TODO: to get the most out of your learning, visit our website *

* * @param generic type parameter * @author Taras Boychuk * @author Serhii Hryhus */ public class LinkedStack implements Stack { /** * This method creates a stack of provided elements * * @param elements elements to add * @param generic type * @return a new stack of elements that were passed as method parameters */ public static LinkedStack of(T... elements) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * The method pushes an element onto the top of this stack. This has exactly the same effect as: * addElement(item) * * @param element elements to add */ @Override public void push(T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * This method removes the object at the top of this stack * and returns that object as the value of this function. * * @return The object at the top of this stack * @throws EmptyStackException - if this stack is empty */ @Override public T pop() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns the number of elements in the stack * * @return number of elements */ @Override public int size() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Checks if a stack is empty * * @return {@code true} if a stack is empty, {@code false} otherwise */ @Override public boolean isEmpty() { throw new ExerciseNotCompletedException(); // todo: implement this method; } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-2-stack/src/main/java/com/bobocode/cs/Stack.java ================================================ package com.bobocode.cs; /** * {@link Stack} is a fundamental data structure that follows last-in-first-out (LIFO) principle. This interface * represents a simple contact, that can be implemented in various ways (e.g. using existing collections, arrays or * custom linked nodes) * * @param type parameter * @author Taras Boychuk * @author Serhii Hryhus */ public interface Stack { void push(T element); T pop(); int size(); boolean isEmpty(); } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-2-stack/src/main/java/com/bobocode/cs/exception/EmptyStackException.java ================================================ package com.bobocode.cs.exception; public class EmptyStackException extends RuntimeException{ } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-2-stack/src/test/java/com/bobocode/cs/LinkedStackTest.java ================================================ package com.bobocode.cs; import com.bobocode.cs.exception.EmptyStackException; import lombok.SneakyThrows; import org.junit.jupiter.api.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.Arrays; import java.util.function.Predicate; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNullPointerException; import static org.junit.jupiter.api.Assertions.assertThrows; /** * A reflection-based test class for {@link LinkedStack}. *

* PLEASE NOTE: we use Reflection API only for learning purposes. It should NOT be used for production tests. * * @author Ivan Virchenko * @author Taras Boychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class LinkedStackTest { private static final String PROPER_CLASSNAME = "Node"; private static final Predicate NODE_FIELD_PREDICATE = field -> field.getType().getSimpleName().equals(PROPER_CLASSNAME) && field.getName().toLowerCase().contains("head") || field.getName().toLowerCase().contains("first"); private static final Predicate SIZE_FIELD_PREDICATE = field -> field.getName().toLowerCase().contains("size"); private static final Predicate NODE_ELEMENT_FIELD_PREDICATE = field -> field.getName().toLowerCase().contains("element") || field.getName().toLowerCase().contains("value") || field.getName().toLowerCase().contains("item"); private static final Predicate NODE_NEXT_FIELD_PREDICATE = field -> field.getType().getSimpleName().equals(PROPER_CLASSNAME) && field.getName().toLowerCase().contains("next"); private Stack intStack = new LinkedStack<>(); @Test @Order(1) @DisplayName("Inner class Node is created") void checkProperInnerClassName() { String name = getInnerClass().getSimpleName(); assertThat(name).isEqualTo(PROPER_CLASSNAME); } @Test @Order(2) @DisplayName("Class Node is a generic class") void noteIsAGenericClass() { var nodeTypeParams = getInnerClass().getTypeParameters(); assertThat(nodeTypeParams).hasSize(1); } @Test @Order(3) @DisplayName("LinkedStack class has a field that stores a reference to the first(head) element") void checkProperHeadFieldName() { Field[] fields = LinkedStack.class.getDeclaredFields(); boolean hasNodeField = Arrays.stream(fields) .anyMatch(NODE_FIELD_PREDICATE); assertThat(hasNodeField).isTrue(); } @Test @Order(4) @DisplayName("LinkedStack class has a field to store stack size") void checkProperSizeFieldName() { Field[] fields = LinkedStack.class.getDeclaredFields(); boolean hasSizeField = Arrays.stream(fields) .anyMatch(SIZE_FIELD_PREDICATE); assertThat(hasSizeField).isTrue(); } @Test @Order(5) @DisplayName("Node class has a field to store a generic element") void checkProperElementField() { var fields = getInnerClass().getDeclaredFields(); var elementField = Arrays.stream(fields) .filter(NODE_ELEMENT_FIELD_PREDICATE) .findAny() .orElseThrow(); var nodeTypeParameter = getInnerClass().getTypeParameters()[0]; assertThat(elementField.getGenericType().getTypeName()).isEqualTo(nodeTypeParameter.getTypeName()); } @Test @Order(6) @DisplayName("Node class has a field to store a reference to the next node") void checkProperNextField() { Field[] fields = getInnerClass().getDeclaredFields(); boolean hasNext = Arrays.stream(fields) .anyMatch(NODE_NEXT_FIELD_PREDICATE); assertThat(hasNext).isTrue(); } @Test @Order(7) @DisplayName("Method of() creates a new LinkedStack of given elements") void of() { intStack = LinkedStack.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); for (int i = 1; i <= 10; i++) { assertThat(contains(i)).isTrue(); } } @Test @Order(8) @DisplayName("Method push() adds new element on top of the stack") void push() { intStack.push(55); assertThat(contains(55)).isTrue(); } @Test @Order(9) @DisplayName("Method push() adds new element on top of the stack") void pushAddsElementWhenStackIsEmpty() { intStack.push(243); assertThat(contains(243)).isTrue(); } @Test @Order(10) @DisplayName("Method push() adds new element on top of the stack when it's empty") void pushAddsElementToHeadWhenStackIsEmpty() { intStack.push(10); Object head = getHeadObject(); int innerElement = getNodeElementInt(head); assertThat(innerElement).isEqualTo(10); } @Test @Order(11) @DisplayName("Method push() adds new element on top of the stack when it's not empty") void pushAddsElementToHeadWhenStackIsNotEmpty() { intStack.push(10); intStack.push(15); intStack.push(20); Object head = getHeadObject(); int innerElement = getNodeElementInt(head); assertThat(innerElement).isEqualTo(20); } @Test @Order(12) @DisplayName("Method push() links new element with the previous top (head) element") void pushPutsHeadToNextOfNewHead() { fillTestStack(10, 15, 20); assertThat(getNodeElementInt(getHeadObject())).isEqualTo(20); intStack.push(30); Object head = getHeadObject(); Object next = getNodeNextObject(head); int nextElement = getNodeElementInt(next); assertThat(nextElement).isEqualTo(20); } @Test @Order(13) @DisplayName("Method push() throws exception when element is null") void pushThrowsExceptionWhenElementIsNull() { assertThatNullPointerException().isThrownBy(() -> intStack.push(null)); } @Test @Order(14) @DisplayName("Method pop() throws exception when stack is empty") void popElementWhenStackIsEmpty() { assertThrows(EmptyStackException.class, () -> intStack.pop()); } @Test @Order(15) @DisplayName("Method pop() retrieves top element from the stack (LIFO)") void pop() { fillTestStack(55, 17, 66, 234); int lastElement = intStack.pop(); assertThat(lastElement).isEqualTo(234); } @Test @Order(16) @DisplayName("Method pop() assigns next element to be a head") void popResetsHeadFromNextOfOldHead() { fillTestStack(10, 15, 20); Object head = getHeadObject(); assertThat(getNodeElementInt(head)).isEqualTo(20); intStack.pop(); head = getHeadObject(); assertThat(getNodeElementInt(head)).isEqualTo(15); } @Test @Order(17) @DisplayName("Method size() returns 0 when stack is empty") void sizeWhenStackIsEmpty() { int actualSize = getInnerSize(); assertThat(actualSize).isEqualTo(0); } @Test @Order(18) @DisplayName("Method size() returns number of element in the stack") void size() { fillTestStack(1, 5, 7); assertThat(intStack.size()).isEqualTo(3); } @Test @Order(19) @DisplayName("Method size() returns correct value when stack was created via method of()") void sizeIncreasesWhenUseOfMethod() { intStack = LinkedStack.of(1, 2, 3, 4, 5, 6, 7, 8); assertThat(intStack.size()).isEqualTo(8); } @Test @Order(20) @DisplayName("Method size() correct value when elements were added via push()") void sizeIncreasesWhenPush() { intStack.push(1); intStack.push(2); intStack.push(3); assertThat(intStack.size()).isEqualTo(3); } @Test @Order(21) @DisplayName("Method size() correct value when elements were removed via pop()") void sizeDecreasesWhenPop() { fillTestStack(1, 2, 3, 4, 5); intStack.pop(); assertThat(intStack.size()).isEqualTo(4); } @Test @Order(22) @DisplayName("Method isEmpty() returns true when stack contains elements") void isEmpty() { fillTestStack(87, 53, 66); boolean stackEmpty = intStack.isEmpty(); assertThat(stackEmpty).isEqualTo(false); } @Test @Order(23) @DisplayName("Method isEmpty() returns false when stack contains no elements") void isEmptyWhenStackIsEmpty() { boolean stackEmpty = intStack.isEmpty(); assertThat(stackEmpty).isEqualTo(true); } private Class getInnerClass() { return Arrays.stream(LinkedStack.class.getDeclaredClasses()) .filter(Class::isMemberClass) .findAny().orElseThrow(); } private Field getHeadField() { Field headField = Arrays.stream(LinkedStack.class.getDeclaredFields()) .filter(NODE_FIELD_PREDICATE) .findAny() .orElseThrow(); headField.setAccessible(true); return headField; } private Field getNodeElementField(Object node) { Field fieldElement = Arrays.stream(node.getClass().getDeclaredFields()) .filter(NODE_ELEMENT_FIELD_PREDICATE) .findAny() .orElseThrow(); fieldElement.setAccessible(true); return fieldElement; } private Field getNodeNextField(Object node) { Field field = Arrays.stream(node.getClass().getDeclaredFields()) .filter(NODE_NEXT_FIELD_PREDICATE) .findAny() .orElseThrow(); field.setAccessible(true); return field; } @SneakyThrows private Object getHeadObject() { return getHeadField().get(intStack); } @SneakyThrows private int getNodeElementInt(Object node) { return (int) getNodeElementField(node).get(node); } @SneakyThrows private Object getNodeNextObject(Object node) { return getNodeNextField(node).get(node); } private boolean contains(int element) { Object head = getHeadObject(); if (head == null) { return false; } if (getNodeNextObject(head) != null) { return checkNext(head, element); } else { return getNodeElementInt(head) == element; } } private boolean checkNext(Object node, int element) { if (getNodeElementInt(node) == element) { return true; } else { return checkNext(getNodeNextObject(node), element); } } @SneakyThrows private Object newNode(int element) { Constructor constructor = getInnerClass().getDeclaredConstructors()[0]; constructor.setAccessible(true); if (constructor.getParameters().length == 1) { return constructor.newInstance(element); } else if (constructor.getParameters().length == 2) { return constructor.newInstance(element, null); } else { Object node = constructor.newInstance(); getNodeElementField(node).set(node, element); return node; } } private void fillTestStack(int... elements) { for (int element : elements) { addToStack(element); } setInnerSize(elements.length); } @SneakyThrows private void addToStack(int element) { Object newNode = newNode(element); if (getHeadObject() != null) { getNodeNextField(newNode).set(newNode, getHeadObject()); } getHeadField().set(intStack, newNode); } private Field getInnerSizeField() { Field size = Arrays.stream(LinkedStack.class.getDeclaredFields()) .filter(SIZE_FIELD_PREDICATE) .findAny() .orElseThrow(); size.setAccessible(true); return size; } @SneakyThrows private void setInnerSize(int size) { getInnerSizeField().set(intStack, size); } @SneakyThrows private int getInnerSize() { return (int) getInnerSizeField().get(intStack); } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-3-linked-queue/README.MD ================================================ # Linked Queue Learn the idea of a queue and build strong skills implementing a queue based on linked nodes 💪 ### Pre-conditions ❗ You're supposed to be familiar [Queue](https://en.wikipedia.org/wiki/Queue_(abstract_data_type)) data structure and generics in Java ### Objectives * implement a generic class `Node` ✅ * **add an element** to the end of the queue ✅ * **retrieve an element** from the begging of the queue ** ✅ * maintain queue **size** ✅ --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 2-0-data-structures-and-algorithms/2-2-3-linked-queue/pom.xml ================================================ 2-0-data-structures-and-algorithms com.bobocode 1.0-SNAPSHOT 4.0.0 2-2-3-linked-queue ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-3-linked-queue/src/main/java/com/bobocode/cs/LinkedQueue.java ================================================ package com.bobocode.cs; import com.bobocode.util.ExerciseNotCompletedException; /** * {@link LinkedQueue} implements FIFO {@link Queue}, using singly linked nodes. Nodes are stores in instances of nested * class Node. In order to perform operations {@link LinkedQueue#add(Object)} and {@link LinkedQueue#poll()} * in a constant time, it keeps to references to the head and tail of the queue. *

* TODO: to get the most out of your learning, visit our website *

* * @param a generic parameter * @author Taras Boychuk * @author Ivan Virchenko */ public class LinkedQueue implements Queue { /** * Adds an element to the end of the queue. * * @param element the element to add */ public void add(T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Retrieves and removes queue head. * * @return an element that was retrieved from the head or null if queue is empty */ public T poll() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns a size of the queue. * * @return an integer value that is a size of queue */ public int size() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Checks if the queue is empty. * * @return {@code true} if the queue is empty, returns {@code false} if it's not */ public boolean isEmpty() { throw new ExerciseNotCompletedException(); // todo: implement this method } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-3-linked-queue/src/main/java/com/bobocode/cs/Queue.java ================================================ package com.bobocode.cs; /** * Queue is a data structure that follows "first in, first out" rule (FIFO). Operations {@link Queue#add(Object)} and * {@link Queue#poll()} are performed in constant time O(1) */ public interface Queue { /** * Adds an element to the end of the queue. * * @param element the element to add */ void add(T element); /** * Retrieves and removes queue head. * * @return an element that was retrieved from the head or null if queue is empty */ T poll(); /** * Returns a size of the queue. * * @return an integer value that is a size of queue */ int size(); /** * Checks if the queue is empty. * * @return {@code true} if the queue is empty, returns {@code false} if it's not */ boolean isEmpty(); } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-3-linked-queue/src/test/java/com/bobocode/cs/LinkedQueueTest.java ================================================ package com.bobocode.cs; import lombok.SneakyThrows; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.function.Predicate; import static org.assertj.core.api.Assertions.assertThat; /** * A reflection-based test class for {@link LinkedQueue}. *

* PLEASE NOTE: we use Reflection API only for learning purposes. It should NOT be used for production tests. * * @author Victor Kuzma * @author Taras Boychuk * @author Ivan Virchenko */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class LinkedQueueTest { private static final String NODE_NAME = "Node"; private static final String SIZE_NAME = "size"; private static final Predicate NODE_FIELD = field -> field.getType().getSimpleName().equals(NODE_NAME) && (field.getName().contains("next")); private static final Predicate ELEMENT_FIELD = field -> field.getGenericType().getTypeName().equals("T") && (field.getName().contains("elem") || field.getName().contains("value") || field.getName().contains("item")); private static final Predicate NEXT_FIELD = field -> field.getGenericType().getTypeName().endsWith("Node") && (field.getName().contains("next")); private static final Predicate SIZE_FIELD = field -> field.getType().getSimpleName().equals("int") && (field.getName().equals(SIZE_NAME)); private static final Predicate HEAD_FIELD = field -> field.getType().getSimpleName().equals(NODE_NAME) && (field.getName().contains("head") || field.getName().contains("first")); private static final Predicate TAIL_FIELD = field -> field.getType().getSimpleName().equals(NODE_NAME) && (field.getName().contains("tail") || field.getName().contains("last")); private Queue integerQueue = new LinkedQueue<>(); @Test @Order(1) void createNodeClass() { Class innerClass = getInnerStaticNodeClass(); String name = innerClass.getSimpleName(); assertThat(name).isEqualTo(NODE_NAME); } @Test @Order(2) void checkFieldsNameInNodeClass() { Class innerClass = getInnerStaticNodeClass(); boolean hasElementField = hasField(innerClass, ELEMENT_FIELD); boolean hasNodeField = hasField(innerClass, NODE_FIELD); assertThat(hasElementField).isTrue(); assertThat(hasNodeField).isTrue(); } @Test @Order(3) void checkFieldsInQueueCLass() { Class baseClass = this.integerQueue.getClass(); boolean hasHeadField = hasField(baseClass, HEAD_FIELD); boolean hasSizeFiled = hasField(baseClass, SIZE_FIELD); boolean hasTailFiled = hasField(baseClass, TAIL_FIELD); assertThat(hasHeadField).isTrue(); assertThat(hasSizeFiled).isTrue(); assertThat(hasTailFiled).isTrue(); } @Test @Order(4) void addFillsQueueWhenItIsEmpty() { integerQueue.add(1); integerQueue.add(228); int size = getInternalSize(); boolean isEmpty = isEmptyQueue(); Integer firstElement = (Integer) pollElementFromQueue(); Integer secondElement = (Integer) pollElementFromQueue(); assertThat(size).isEqualTo(2); assertThat(isEmpty).isEqualTo(false); assertThat(firstElement).isEqualTo(1); assertThat(secondElement).isEqualTo(228); } @Test @Order(5) void addFillsQueueWhenItIsNotEmpty() { addIntElementToQueue(12); addIntElementToQueue(13); integerQueue.add(111); int size = getInternalSize(); boolean isEmpty = isEmptyQueue(); Integer firstElement = (Integer) pollElementFromQueue(); Integer secondElement = (Integer) pollElementFromQueue(); Integer tailValue = (Integer) getNodeValue(TAIL_FIELD); assertThat(size).isEqualTo(3); assertThat(isEmpty).isEqualTo(false); assertThat(firstElement).isEqualTo(12); assertThat(secondElement).isEqualTo(13); assertThat(tailValue).isEqualTo(111); } @Test @Order(6) void addIncreasesQueueSize() { integerQueue.add(1); integerQueue.add(2); int size = getInternalSize(); assertThat(size).isEqualTo(2); } @Test @Order(7) void pollReturnsNullWhenQueueIsEmpty() { Integer firstElement = this.integerQueue.poll(); assertThat(firstElement).isEqualTo(null); } @Test @Order(8) void pollDeletesElementFromHead() { addIntElementToQueue(11); addIntElementToQueue(111); Integer firstElement = this.integerQueue.poll(); Integer secondElement = this.integerQueue.poll(); boolean isEmpty = isEmptyQueue(); assertThat(isEmpty).isEqualTo(true); assertThat(firstElement).isEqualTo(11); assertThat(secondElement).isEqualTo(111); } @Test @Order(9) void pollDecreasesQueueSize() { addIntElementToQueue(11); addIntElementToQueue(111); this.integerQueue.poll(); int size = getInternalSize(); assertThat(size).isEqualTo(1); } @Test @Order(10) void pollMakesSizeZeroWhenQueueHasSingleElement() { addIntElementToQueue(12); Integer element = this.integerQueue.poll(); int size = getInternalSize(); assertThat(size).isEqualTo(0); assertThat(element).isEqualTo(12); } @Test @SneakyThrows @Order(11) void pollMakesQueueEmptyWhenQueueHasSingleElement() { addIntElementToQueue(1); this.integerQueue.poll(); boolean isEmpty = isEmptyQueue(); Object tail = getAccessibleFieldByPredicate(integerQueue, TAIL_FIELD).get(integerQueue); Object head = getAccessibleFieldByPredicate(integerQueue, HEAD_FIELD).get(integerQueue); assertThat(isEmpty).isEqualTo(true); assertThat(tail).isNull(); assertThat(head).isNull(); } @Test @Order(12) void sizeReturnsZeroWhenQueueIsEmpty() { int size = this.integerQueue.size(); assertThat(size).isEqualTo(0); } @Test @Order(13) void size() { addIntElementToQueue(1); int size = this.integerQueue.size(); assertThat(size).isEqualTo(1); } @Test @Order(14) void isEmptyReturnsTrueWhenQueueIsEmpty() { boolean isEmpty = this.integerQueue.isEmpty(); assertThat(isEmpty).isEqualTo(true); } @Test @Order(15) void isEmpty() { addIntElementToQueue(1); boolean isEmpty = integerQueue.isEmpty(); assertThat(isEmpty).isEqualTo(false); } private Class getInnerStaticNodeClass() { return Arrays.stream(integerQueue.getClass().getDeclaredClasses()) .filter(aClass -> Modifier.isStatic(aClass.getModifiers())) .findAny() .orElseThrow(); } private boolean hasField(Class classToSearch, Predicate targetField) { return Arrays.stream(classToSearch.getDeclaredFields()) .anyMatch(targetField); } @SneakyThrows private int getInternalSize() { return (int) getAccessibleFieldByPredicate(this.integerQueue, SIZE_FIELD) .get(this.integerQueue); } @SneakyThrows private Object pollElementFromQueue() { Object element; Object nextElement; Object tail; Object head = getAccessibleFieldByPredicate(this.integerQueue, HEAD_FIELD) .get(this.integerQueue); Integer size = (Integer) getAccessibleFieldByPredicate(this.integerQueue, SIZE_FIELD) .get(this.integerQueue); if (head != null) { element = getAccessibleFieldByPredicate(head, ELEMENT_FIELD) .get(head); nextElement = getAccessibleFieldByPredicate(head, NODE_FIELD) .get(head); head = nextElement; setHead(head); if (head == null) { tail = null; setTail(tail); } if (size != null) { int tmpInt = size; tmpInt--; setInternalSize(tmpInt); } return element; } else { return null; } } @SneakyThrows private void addIntElementToQueue(int value) { Object newNode = createNode(value); Object head = getAccessibleFieldByPredicate(this.integerQueue, HEAD_FIELD).get(this.integerQueue); Object tail = getAccessibleFieldByPredicate(this.integerQueue, TAIL_FIELD).get(this.integerQueue); Integer size = (Integer) getAccessibleFieldByPredicate(this.integerQueue, SIZE_FIELD).get(this.integerQueue); if (head == null) { setHead(newNode); } else { setNextNode(tail, newNode); } setTail(newNode); if (size == null) { setInternalSize(1); } else { int tmpInt = size; tmpInt++; setInternalSize(tmpInt); } } @SneakyThrows private Object createNode(int value) { Object nodeObject; Class innerClass = getInnerStaticNodeClass(); Constructor[] declaredConstructors = innerClass.getDeclaredConstructors(); Constructor constructor = declaredConstructors[0]; constructor.setAccessible(true); if (constructor.getParameterTypes().length == 1) { nodeObject = constructor.newInstance(value); } else { nodeObject = constructor.newInstance(); Field nodeElement = getAccessibleFieldByPredicate(nodeObject, ELEMENT_FIELD); nodeElement.set(nodeObject, value); } return nodeObject; } @SneakyThrows private boolean isEmptyQueue() { Object head = getAccessibleFieldByPredicate(this.integerQueue, HEAD_FIELD) .get(this.integerQueue); return head == null; } @SneakyThrows private void setInternalSize(int size) { Field sizeField = getAccessibleFieldByPredicate(this.integerQueue, SIZE_FIELD); sizeField.setInt(this.integerQueue, size); } @SneakyThrows private void setHead(Object obj) { Field head = getAccessibleFieldByPredicate(this.integerQueue, HEAD_FIELD); head.set(this.integerQueue, obj); } @SneakyThrows private void setTail(Object obj) { Field tail = getAccessibleFieldByPredicate(this.integerQueue, TAIL_FIELD); tail.set(this.integerQueue, obj); } @SneakyThrows private void setNextNode(Object current, Object next) { Field nodeNextField = getAccessibleFieldByPredicate(current, NEXT_FIELD); nodeNextField.set(current, next); } private Field getAccessibleFieldByPredicate(Object object, Predicate predicate) { Field field = Arrays.stream(object.getClass().getDeclaredFields()) .filter(predicate) .findAny() .orElseThrow(); field.setAccessible(true); return field; } @SneakyThrows private Object getNodeValue(Predicate predicate) { Object field = getAccessibleFieldByPredicate(integerQueue, predicate).get(integerQueue); final Field value = getAccessibleFieldByPredicate(field, ELEMENT_FIELD); value.setAccessible(true); return value.get(field); } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-4-linked-list/README.MD ================================================ # Linked List Build strong skills implementing a well known Linked List data structure 💪 ### Pre-conditions ❗ You're supposed to be familiar [Linked List](https://en.wikipedia.org/wiki/Linked_list) data structure and generics in Java ### Objectives * implement a generic class `Node` ✅ * **add an element** to the end of the list in **O(1)** ✅ * **add an element by index** (relink nodes when adding a new one inside the chain) ✅ * **set element** by index (find the correct node by index starting from `head`) ✅ * **remove element** by index (link prev and next nodes to get rid of the one that should be removed) ✅ * maintain list **size** ✅ ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/knhSNO3bAHo/0.jpg)](https://www.youtube.com/watch?v=knhSNO3bAHo) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 2-0-data-structures-and-algorithms/2-2-4-linked-list/pom.xml ================================================ 2-0-data-structures-and-algorithms com.bobocode 1.0-SNAPSHOT 4.0.0 2-2-4-linked-list com.bobocode data-structures-and-algorithms-util 1.0-SNAPSHOT ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-4-linked-list/src/main/java/com/bobocode/cs/LinkedList.java ================================================ package com.bobocode.cs; import com.bobocode.util.ExerciseNotCompletedException; /** * {@link LinkedList} is a list implementation that is based on singly linked generic nodes. A node is implemented as * inner static class {@link Node}. *

* TODO: to get the most out of your learning, visit our website *

* * @param generic type parameter * @author Taras Boychuk * @author Serhii Hryhus */ public class LinkedList implements List { /** * This method creates a list of provided elements * * @param elements elements to add * @param generic type * @return a new list of elements the were passed as method parameters */ public static LinkedList of(T... elements) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Adds an element to the end of the list. * * @param element element to add */ @Override public void add(T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Adds a new element to the specific position in the list. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index an index of new element * @param element element to add */ @Override public void add(int index, T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Changes the value of an list element at specific position. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index an position of element to change * @param element a new element value */ @Override public void set(int index, T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Retrieves an elements by its position index. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index element index * @return an element value */ @Override public T get(int index) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns the first element of the list. Operation is performed in constant time O(1) * * @return the first element of the list * @throws java.util.NoSuchElementException if list is empty */ @Override public T getFirst() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns the last element of the list. Operation is performed in constant time O(1) * * @return the last element of the list * @throws java.util.NoSuchElementException if list is empty */ @Override public T getLast() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Removes an elements by its position index. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index element index * @return deleted element */ @Override public T remove(int index) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Checks if a specific exists in he list * * @return {@code true} if element exist, {@code false} otherwise */ @Override public boolean contains(T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Checks if a list is empty * * @return {@code true} if list is empty, {@code false} otherwise */ @Override public boolean isEmpty() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns the number of elements in the list * * @return number of elements */ @Override public int size() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Removes all list elements */ @Override public void clear() { throw new ExerciseNotCompletedException(); // todo: implement this method } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-4-linked-list/src/test/java/com/bobocode/cs/LinkedListTest.java ================================================ package com.bobocode.cs; import lombok.SneakyThrows; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.NoSuchElementException; import java.util.function.Predicate; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; /** * A reflection-based test class for {@link LinkedList}. *

* PLEASE NOTE: we use Reflection API only for learning purposes. It should NOT be used for production tests. * * @author Serhii Hryhus * @author Taras Boychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class LinkedListTest { private static final Predicate NODE_FIELD = field -> field.getType().getSimpleName().equals("Node"); private static final Predicate HEAD_NODE_FIELD = field -> field.getType().getSimpleName().equals("Node") && (field.getName().contains("head") || field.getName().contains("first")); private static final Predicate TAIL_NODE_FIELD = field -> field.getType().getSimpleName().equals("Node") && (field.getName().equals("tail") || field.getName().contains("last")); private static final Predicate SIZE_FIELD = field -> field.getName().equals("size"); private static final Predicate ELEMENT_FIELD = field -> field.getGenericType().getTypeName().equals("T") && (field.getName().contains("elem") || field.getName().contains("value") || field.getName().contains("item")); private LinkedList intList = new LinkedList<>(); @Test @Order(1) void properNodeName() { Class innerClass = getInnerClass(); String name = innerClass.getSimpleName(); assertThat(name).isEqualTo("Node"); } @Test @Order(2) void properNodeFields() { Class innerClass = getInnerClass(); boolean hasElementField = Arrays.stream(innerClass.getDeclaredFields()) .anyMatch(ELEMENT_FIELD); boolean hasNodeField = Arrays.stream(innerClass.getDeclaredFields()) .anyMatch(NODE_FIELD); assertThat(hasElementField).isTrue(); assertThat(hasNodeField).isTrue(); } private Class getInnerClass() { return Arrays.stream(intList.getClass().getDeclaredClasses()) .filter(aClass -> Modifier.isStatic(aClass.getModifiers())) .findAny() .orElseThrow(); } @Test @Order(3) void addIntoEmptyList() { intList.add(41); int element = getInternalElement(0); assertThat(element).isEqualTo(41); } @Test @Order(4) void addIntoEmptyListChangesSize() { intList.add(41); int size = getInternalSize(); assertThat(size).isEqualTo(1); } @Test @Order(5) void add() { intList.add(41); intList.add(30); intList.add(75); int firstElement = getInternalElement(0); int secondElement = getInternalElement(1); int thirdElement = getInternalElement(2); assertThat(firstElement).isEqualTo(41); assertThat(secondElement).isEqualTo(30); assertThat(thirdElement).isEqualTo(75); } @Test @Order(6) void addChangesSize() { intList.add(41); intList.add(30); intList.add(75); int size = getInternalSize(); assertThat(size).isEqualTo(3); } @Test @Order(7) void addByIndex() { addInternalElements(43, 5, 6, 8); int newElementIdx = 2; intList.add(newElementIdx, 66); int elementByNewElementIndex = getInternalElement(newElementIdx); int size = getInternalSize(); assertThat(elementByNewElementIndex).isEqualTo(66); assertThat(getInternalElement(0)).isEqualTo(43); assertThat(getInternalElement(1)).isEqualTo(5); assertThat(getInternalElement(3)).isEqualTo(6); assertThat(getInternalElement(4)).isEqualTo(8); assertThat(size).isEqualTo(5); } @Test @Order(8) void addByZeroIndexWhenListIsEmpty() { intList.add(0, 45); int element = getInternalElement(0); int size = getInternalSize(); assertThat(element).isEqualTo(45); assertThat(size).isEqualTo(1); } @Test @Order(9) void addByIndexToTheEndOfList() { addInternalElements(98, 64, 23, 1, 3, 4); int newElementIndex = getInternalSize(); intList.add(newElementIndex, 44); assertThat(getInternalElement(newElementIndex)).isEqualTo(44); assertThat(getInternalSize()).isEqualTo(7); } @Test @Order(10) void addToHeadWhenListIsNotEmpty() { addInternalElements(4, 6, 8, 9, 0, 2); intList.add(0, 53); int firstElement = getInternalElement(0); int secondElement = getInternalElement(1); int size = getInternalSize(); assertThat(firstElement).isEqualTo(53); assertThat(secondElement).isEqualTo(4); assertThat(size).isEqualTo(7); } @Test @Order(11) void addThrowsExceptionWhenIndexIsNegative() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> intList.add(-1, 66)); } @Test @Order(12) void addThrowsExceptionWhenIndexLargerThanSize() { addInternalElements(4, 6, 11, 9); int newElementIdx = 5; assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> intList.add(newElementIdx, 88)); } @Test @Order(13) void addWhenIndexEqualToSize() { addInternalElements(1, 2, 3, 4, 5); // size = 5 intList.add(5, 111); int element = getInternalElement(5); int size = getInternalSize(); assertThat(element).isEqualTo(111); assertThat(size).isEqualTo(6); } @Test @Order(14) void of() { intList = LinkedList.of(43, 233, 54); assertThat(getInternalElement(0)).isEqualTo(43); assertThat(getInternalElement(1)).isEqualTo(233); assertThat(getInternalElement(2)).isEqualTo(54); } @Test @Order(15) void ofChangeSize() { intList = LinkedList.of(43, 233, 54); int size = getInternalSize(); assertThat(size).isEqualTo(3); } @Test @Order(16) void setByIndex() { addInternalElements(34, 78, 9, 8); int index = 2; //element = 78 intList.set(index, 99); int elementOnNewElementIndex = getInternalElement(index); int nextElementToNewElementIndex = getInternalElement(3); int internalSize = getInternalSize(); assertThat(elementOnNewElementIndex).isEqualTo(99); assertThat(nextElementToNewElementIndex).isEqualTo(8); assertThat(getInternalElement(0)).isEqualTo(34); assertThat(getInternalElement(1)).isEqualTo(78); assertThat(internalSize).isEqualTo(4); } @Test @Order(17) void setFirstElementWhenListIsEmpty() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> intList.set(0, 34)); } @Test @Order(18) void setByIndexEqualToSize() { addInternalElements(2, 3, 4); // size = 3 assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> intList.set(3, 222)); } @Test @Order(19) void get() { addInternalElements(25, 87, 45); int firstElement = intList.get(0); int secondElement = intList.get(1); int thirdElement = intList.get(2); assertThat(firstElement).isEqualTo(25); assertThat(secondElement).isEqualTo(87); assertThat(thirdElement).isEqualTo(45); } @Test @Order(20) void getByZeroIndexWhenListHasSingleElement() { addInternalElements(25); int element = intList.get(0); assertThat(element).isEqualTo(25); } @Test @Order(21) void getByZeroIndexWhenListIsEmpty() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> intList.get(0)); } @Test @Order(22) void getWhenIndexIsNegative() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> intList.get(-1)); } @Test @Order(23) void getWhenIndexIsEqualToListSize() { addInternalElements(33, 46, 25, 87, 45); assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> intList.get(5)); } @Test @Order(24) void getFirst() { addInternalElements(31, 32, 5); int firstElement = intList.getFirst(); assertThat(firstElement).isEqualTo(31); } @Test @Order(25) void getLast() { addInternalElements(41, 6, 42); int lastElement = intList.getLast(); assertThat(lastElement).isEqualTo(42); } @Test @Order(26) void getFirstWhenListIsEmpty() { assertThatExceptionOfType(NoSuchElementException.class) .isThrownBy(() -> intList.getFirst()); } @Test @Order(27) void getLastWhenListIsEmpty() { assertThatExceptionOfType(NoSuchElementException.class) .isThrownBy(() -> intList.getLast()); } @Test @Order(28) void remove() { addInternalElements(1, 2, 3, 4, 5); int elementIndex = 2; int deletedElement = intList.remove(elementIndex); // element = 3 int replacedElement = getInternalElement(elementIndex); assertThat(deletedElement).isEqualTo(3); assertThat(replacedElement).isEqualTo(4); } @Test @Order(29) void removeChangesSize() { addInternalElements(1, 2, 3, 4, 5); int elementIndex = 2; int deletedElement = intList.remove(elementIndex); // element = 3 int size = getInternalSize(); assertThat(deletedElement).isEqualTo(3); assertThat(size).isEqualTo(4); } @Test @Order(30) void removeFirst() { addInternalElements(4, 6, 8, 9); int deletedElement = intList.remove(0); int replacedElement = getInternalElement(0); int size = getInternalSize(); assertThat(deletedElement).isEqualTo(4); assertThat(replacedElement).isEqualTo(6); assertThat(size).isEqualTo(3); } @Test @Order(31) void removeLast() { addInternalElements(4, 6, 8, 9); int deletedElement = intList.remove(getInternalSize() - 1); int newLastElement = getInternalElement(getInternalSize() - 1); int tailElement = (int) getNodeValue(TAIL_NODE_FIELD); int size = getInternalSize(); assertThat(deletedElement).isEqualTo(9); assertThat(newLastElement).isEqualTo(8); assertThat(tailElement).isEqualTo(8); assertThat(size).isEqualTo(3); } @Test @Order(32) void removeWhenListIsEmpty() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> intList.remove(234)); } @Test @Order(33) void removeByZeroIndexWhenListIsEmpty() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> intList.remove(0)); } @Test @Order(34) void size() { setInternalSize(5); int sizeFromMethod = intList.size(); assertThat(sizeFromMethod).isEqualTo(5); } @Test @Order(35) void sizeWhenListIsEmpty() { int size = getInternalSize(); assertThat(size).isEqualTo(0); } @Test @Order(36) void contains() { addInternalElements(45, 6, 3, 6); boolean containsExistingElement = intList.contains(3); boolean containsNotExistingElement = intList.contains(54); assertThat(containsExistingElement).isTrue(); assertThat(containsNotExistingElement).isFalse(); } @Test @Order(37) void containsWhenListIsEmpty() { boolean contains = intList.contains(34); assertThat(contains).isFalse(); } @Test @Order(38) void isEmpty() { addInternalElements(34, 5, 6); boolean empty = intList.isEmpty(); assertThat(empty).isFalse(); } @Test @Order(39) void isEmptyWhenListIsEmpty() { boolean empty = intList.isEmpty(); assertThat(empty).isTrue(); } @Test @Order(40) void clearWhenListIsEmpty() { intList.clear(); int size = getInternalSize(); assertThat(size).isEqualTo(0); } @Test @Order(41) void clearChangesSize() { addInternalElements(4, 5, 6); intList.clear(); int size = getInternalSize(); assertThat(size).isEqualTo(0); } @Test @Order(42) void clearRemovesAllElements() { addInternalElements(4, 5, 6); intList.clear(); assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> getInternalElement(0)); } @SneakyThrows private int getInternalElement(int index) { Object head = getAccessibleFieldByPredicate(intList, HEAD_NODE_FIELD).get(intList); for (int j = 0; j < index; j++) { head = getAccessibleFieldByPredicate(head, NODE_FIELD).get(head); } return (int) getAccessibleFieldByPredicate(head, ELEMENT_FIELD).get(head); } @SneakyThrows private int getInternalSize() { return (int) getAccessibleFieldByPredicate(intList, SIZE_FIELD).get(intList); } @SneakyThrows private void addInternalElements(int... elements) { Field nodeField = getInternalHeadField(); Field tailNode = getInternalTailField(); Class nodeType = nodeField.getType(); Object previousObject = intList; Object nodeObject = null; for (int element : elements) { nodeObject = createNodeObjectWithInternalElement(nodeType, element); nodeField.set(previousObject, nodeObject); nodeField = getAccessibleFieldByPredicate(nodeObject, NODE_FIELD); previousObject = nodeObject; } tailNode.set(intList, nodeObject); setInternalSize(elements.length); } private Field getInternalHeadField() { return getAccessibleFieldByPredicate(intList, HEAD_NODE_FIELD); } private Field getInternalTailField() { return getAccessibleFieldByPredicate(intList, TAIL_NODE_FIELD); } @SneakyThrows private void setInternalSize(int size) { Field sizeField = getAccessibleFieldByPredicate(intList, SIZE_FIELD); sizeField.setInt(intList, size); } @SneakyThrows private Object createNodeObjectWithInternalElement(Class nodeClass, int element) { Object nodeObject; Constructor[] declaredConstructors = nodeClass.getDeclaredConstructors(); Constructor constructor; constructor = declaredConstructors[0]; constructor.setAccessible(true); if (constructor.getParameterTypes().length == 1) { nodeObject = constructor.newInstance(element); } else { nodeObject = createNodeByConstructorWithoutParameters(element, constructor); } return nodeObject; } @SneakyThrows private Object createNodeByConstructorWithoutParameters(int element, Constructor constructor) { Object nodeObject; nodeObject = constructor.newInstance(); Field nodeElement = getAccessibleFieldByPredicate(nodeObject, ELEMENT_FIELD); nodeElement.set(nodeObject, element); return nodeObject; } private Field getAccessibleFieldByPredicate(Object object, Predicate predicate) { Field field = Arrays.stream(object.getClass().getDeclaredFields()) .filter(predicate) .findAny() .orElseThrow(); field.setAccessible(true); return field; } @SneakyThrows private Object getNodeValue(Predicate predicate) { Object field = getAccessibleFieldByPredicate(intList, predicate).get(intList); final Field value = getAccessibleFieldByPredicate(field, ELEMENT_FIELD); value.setAccessible(true); return value.get(field); } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-5-array-list/README.MD ================================================ # Array List Build strong skills implementing a well known Array List data structure 💪 ### Pre-conditions ❗ * You're supposed to be familiar `List` collection and generics in Java ### Objectives * use array of type `Object` to store any elements ✅ * resize array using native method `System.arrayCopy()` ✅ * **add an element** to the end of array ✅ * **add an element by index** (shift whole array tail to the right) ✅ * **set element** by index ✅ * **remove element** by index (shift whole array tail to the left) ✅ * maintain list **size** ✅ ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/jFBKToSC3ag/0.jpg)](https://www.youtube.com/watch?v=jFBKToSC3ag) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 2-0-data-structures-and-algorithms/2-2-5-array-list/pom.xml ================================================ 2-0-data-structures-and-algorithms com.bobocode 1.0-SNAPSHOT 4.0.0 2-2-5-array-list com.bobocode data-structures-and-algorithms-util 1.0-SNAPSHOT ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-5-array-list/src/main/java/com/bobocode/cs/ArrayList.java ================================================ package com.bobocode.cs; import com.bobocode.util.ExerciseNotCompletedException; /** * {@link ArrayList} is an implementation of {@link List} interface. This resizable data structure * based on an array and is simplified version of {@link java.util.ArrayList}. *

* TODO: to get the most out of your learning, visit our website *

* * @author Serhii Hryhus */ public class ArrayList implements List { /** * This constructor creates an instance of {@link ArrayList} with a specific capacity of an array inside. * * @param initCapacity - the initial capacity of the list * @throws IllegalArgumentException – if the specified initial capacity is negative or 0. */ public ArrayList(int initCapacity) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * This constructor creates an instance of {@link ArrayList} with a default capacity of an array inside. * A default size of inner array is 5; */ public ArrayList() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Creates and returns an instance of {@link ArrayList} with provided elements * * @param elements to add * @return new instance */ public static List of(T... elements) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Adds an element to the array. * * @param element element to add */ @Override public void add(T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Adds an element to the specific position in the array where * * @param index index of position * @param element element to add */ @Override public void add(int index, T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Retrieves an element by its position index. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index index of element * @return en element */ @Override public T get(int index) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns the first element of the list. Operation is performed in constant time O(1) * * @return the first element of the list * @throws java.util.NoSuchElementException if list is empty */ @Override public T getFirst() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns the last element of the list. Operation is performed in constant time O(1) * * @return the last element of the list * @throws java.util.NoSuchElementException if list is empty */ @Override public T getLast() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Changes the value of array at specific position. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index position of value * @param element a new value */ @Override public void set(int index, T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Removes an elements by its position index. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index element index * @return deleted element */ @Override public T remove(int index) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Checks for existing of a specific element in the list. * * @param element is element * @return If element exists method returns true, otherwise it returns false */ @Override public boolean contains(T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Checks if a list is empty * * @return {@code true} if list is empty, {@code false} otherwise */ @Override public boolean isEmpty() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * @return amount of saved elements */ @Override public int size() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Removes all list elements */ @Override public void clear() { throw new ExerciseNotCompletedException(); // todo: implement this method } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-5-array-list/src/test/java/com/bobocode/cs/ArrayListTest.java ================================================ package com.bobocode.cs; import lombok.SneakyThrows; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import java.lang.reflect.Field; import java.util.NoSuchElementException; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; /** * A reflection-based test class for {@link ArrayList}. *

* PLEASE NOTE: we use Reflection API only for learning purposes. It should NOT be used for production tests. * * @author Serhii Hryhus * @author Ivan Virchenko */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class ArrayListTest { private List arrayList = new ArrayList<>(); @Test @Order(1) void add() { arrayList.add(10); arrayList.add(15); arrayList.add(20); Object[] internalArray = getTestArray(); assertThat(internalArray[0]).isEqualTo(10); assertThat(internalArray[1]).isEqualTo(15); assertThat(internalArray[2]).isEqualTo(20); } @Test @Order(2) void sizeOfEmptyArrayWrapper() { assertThat(arrayList.size()).isEqualTo(0); } @Test @Order(3) void size() { setTestSize(3); assertThat(arrayList.size()).isEqualTo(3); } @Test @Order(4) void getElementsByIndex() { fillTestArray(10, 15, 20); assertThat(arrayList.get(0)).isEqualTo(10); assertThat(arrayList.get(1)).isEqualTo(15); assertThat(arrayList.get(2)).isEqualTo(20); assertThat(arrayList.size()).isEqualTo(3); } @Test @Order(5) void getFirstElement() { fillTestArray(31, 24); assertThat(arrayList.getFirst()).isEqualTo(31); } @Test @Order(6) void getLastElement() { fillTestArray(31, 34); assertThat(arrayList.getLast()).isEqualTo(34); } @Test @Order(7) void getFirstOfEmptyList() { assertThatExceptionOfType(NoSuchElementException.class) .isThrownBy(() -> arrayList.getFirst()); } @Test @Order(8) void getLastOfEmptyList() { assertThatExceptionOfType(NoSuchElementException.class) .isThrownBy(() -> arrayList.getLast()); } @Test @Order(9) void createListWithSpecificArrayCapacity() { arrayList = new ArrayList<>(8); assertThat(getTestArray().length).isEqualTo(8); } @Test @Order(10) void createListWithWrongCapacity() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> arrayList = new ArrayList<>(-2)); } @Test @Order(11) void addElements() { arrayList = ArrayList.of(15, 69, 58, 78); assertThat(getTestArray()[0]).isEqualTo(15); assertThat(getTestArray()[1]).isEqualTo(69); assertThat(getTestArray()[2]).isEqualTo(58); assertThat(getTestArray()[3]).isEqualTo(78); assertThat(getTestSize()).isEqualTo(4); } @Test @Order(12) void addShouldResizeDefaultCapacityWhenArrayIsFull() { arrayList = new ArrayList<>(); int defaultCapacity = getTestArray().length; arrayList.add(15); arrayList.add(69); arrayList.add(58); arrayList.add(78); arrayList.add(6); arrayList.add(33); arrayList.add(21); assertThat(getTestArray().length).isGreaterThan(defaultCapacity); assertThat(getTestSize()).isEqualTo(7); } @Test @Order(13) void addShouldResizeSpecificCapacityWhenArrayIsFull() { arrayList = new ArrayList<>(4); arrayList.add(15); arrayList.add(69); arrayList.add(58); arrayList.add(78); arrayList.add(6); arrayList.add(33); arrayList.add(21); assertThat(getTestArray().length).isGreaterThan(4); assertThat(getTestSize()).isEqualTo(7); } @Test @Order(14) void addElementByIndex() { fillTestArray(15, 69, 58, 78, 68); arrayList.add(50); arrayList.add(2, 10); Object[] internalArray = getTestArray(); assertThat(internalArray[2]).isEqualTo(10); assertThat(internalArray[5]).isEqualTo(68); assertThat(getTestSize()).isEqualTo(7); } @Test @Order(15) void addElementByNegativeIndex() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.add(-1, 66)); } @Test @Order(16) void addElementByIndexLargerThanListSize() { setTestSize(4); assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.add(5, 88)); } @Test @Order(17) void addElementByIndexEqualToSize() { fillTestArray(1, 2, 3, 4, 5); arrayList.add(5, 111); Object[] internalArray = getTestArray(); assertThat(internalArray[5]).isEqualTo(111); assertThat(getTestSize()).isEqualTo(6); } @Test @Order(18) void getFirstElementFromEmptyList() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.get(0)); } @Test @Order(19) void getElementByNegativeIndex() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.get(-1)); } @Test @Order(20) void getElementByIndexThrowsExceptionWhenIndexIsOutOfBound() { fillTestArray(1, 2, 3, 4); assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.get(4)); } @Test @Order(21) void setElementByIndex() { fillTestArray(15, 69, 58, 78); Object[] internalArray = getTestArray(); arrayList.set(2, 10); assertThat(internalArray[2]).isEqualTo(10); assertThat(internalArray[3]).isEqualTo(78); assertThat(getTestSize()).isEqualTo(4); } @Test @Order(22) void setElementByIndexThrowsExceptionWhenIndexIsOutOfBound() { fillTestArray(15, 69, 58, 78); assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.set(4, 10)); } @Test @Order(23) void setFirstElementOnEmptyList() { assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.set(0, 34)); } @Test @Order(24) void removeElementByIndex() { fillTestArray(15, 69, 58, 78, 100); Object[] internalArray = getTestArray(); int removedElement = arrayList.remove(2); assertThat(internalArray[2]).isEqualTo(78); assertThat(internalArray[1]).isEqualTo(69); assertThat(getTestSize()).isEqualTo(4); assertThat(removedElement).isEqualTo(58); } @Test @Order(25) void removeElementByIndexThrowsExceptionWhenIndexEqualsSize() { fillTestArray(15, 69, 58, 78); assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.remove(4)); } @Test @Order(26) void removeLastElementByIndex() { fillTestArray(15, 69, 58, 78, 100); Object[] internalArray = getTestArray(); int removedElement = arrayList.remove(4); assertThat(internalArray[3]).isEqualTo(78); assertThat(getTestSize()).isEqualTo(4); assertThat(removedElement).isEqualTo(100); } @Test @Order(27) void removeElementByIndexThrowsExceptionWhenIndexIsOutOfBounds() { fillTestArray(15, 69, 58, 78, 100); assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.remove(6)); } @Test @Order(28) void containsOnEmptyList() { assertThat(arrayList.contains(8)).isEqualTo(false); } @Test @Order(29) void containsElement() { fillTestArray(15, 69, 58, 78, 100); assertThat(arrayList.contains(58)).isEqualTo(true); } @Test @Order(30) void containsNotExistingWhenArrayIsNotFilled() { arrayList = new ArrayList<>(100); Object[] internalArray = getTestArray(); internalArray[0] = 5; internalArray[1] = 10; boolean result = arrayList.contains(3); assertThat(result).isFalse(); } @Test @Order(31) void findNotExistingElement() { fillTestArray(15, 69, 58, 78, 100); assertThat(arrayList.contains(200)).isEqualTo(false); } @Test @Order(32) void isEmptyOnEmptyList() { assertThat(arrayList.isEmpty()).isEqualTo(true); } @Test @Order(33) void isEmpty() { setTestSize(3); assertThat(arrayList.isEmpty()).isEqualTo(false); } @Test @Order(34) void clearOnEmptyList() { assertThat(arrayList.isEmpty()).isEqualTo(true); } @Test @Order(35) void clearChangesTheSize() { setTestSize(100); arrayList.clear(); assertThat(arrayList.size()).isEqualTo(0); } @Test @Order(36) void clearRemovesElements() { fillTestArray(4, 5, 6); arrayList.clear(); assertThatExceptionOfType(IndexOutOfBoundsException.class) .isThrownBy(() -> arrayList.get(0)); } @SneakyThrows private void setTestSize(int size) { Field sizeField = arrayList.getClass().getDeclaredField("size"); sizeField.setAccessible(true); sizeField.set(arrayList, size); } @SneakyThrows private int getTestSize() { Field testSize = arrayList.getClass().getDeclaredField("size"); testSize.setAccessible(true); return (int) testSize.get(arrayList); } @SneakyThrows private Object[] getTestArray() { Field field = arrayList.getClass().getDeclaredField(getTestArrayName()); field.setAccessible(true); return (Object[]) field.get(arrayList); } private String getTestArrayName() { Field[] fields = arrayList.getClass().getDeclaredFields(); String name = null; for (Field field : fields) { if (field.getType().isArray()) { field.setAccessible(true); name = field.getName(); } } return name; } @SneakyThrows private void fillTestArray(Object... elements) { Field arrayField = arrayList.getClass().getDeclaredField(getTestArrayName()); Field sizeField = arrayList.getClass().getDeclaredField("size"); arrayField.setAccessible(true); sizeField.setAccessible(true); arrayField.set(arrayList, elements); sizeField.set(arrayList, elements.length); } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/README.md ================================================ # Binary Search Tree Learn tree data structures and build strong skills implementing binary search tree using recursion 💪 ### Pre-conditions ❗️ * You're supposed to be familiar with trees like [Binary Search Tree](https://en.wikipedia.org/wiki/Binary_search_tree) * You should understand the [recursion](https://en.wikipedia.org/wiki/Recursion_(computer_science)) ### Objectives * implement a generic class `Node` with left and right child nodes ✅ * **insert an element** to the tree using recursion ✅ * **search an element** in the tree ✅ * **traverse tree elements** in a ascending order ✅ * calculate tree **depth** ✅ * maintain tree **size** ✅ ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/alxzyWswCVg/0.jpg)](https://www.youtube.com/watch?v=alxzyWswCVg) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/pom.xml ================================================ 2-0-data-structures-and-algorithms com.bobocode 1.0-SNAPSHOT 4.0.0 2-2-6-binary-search-tree ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/BinarySearchTree.java ================================================ package com.bobocode.cs; import java.util.function.Consumer; public interface BinarySearchTree> { /** * insert an element * @return true if element did not exist in the tree and was inserted successfully */ boolean insert(T element); /** * @return true if tree contains element */ boolean contains(T element); /** * @return number of elements in the tree */ int size(); /** * @return max. number of transition between root node and any other node; 0 - if tree is empty or contains 1 element */ int depth(); /** * traverse the tree in element's natural order * @param consumer accepts ref. to node during traversing */ void inOrderTraversal(Consumer consumer); } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/main/java/com/bobocode/cs/RecursiveBinarySearchTree.java ================================================ package com.bobocode.cs; import com.bobocode.util.ExerciseNotCompletedException; import java.util.function.Consumer; /** * {@link RecursiveBinarySearchTree} is an implementation of a {@link BinarySearchTree} that is based on a linked nodes * and recursion. A tree node is represented as a nested class {@link Node}. It holds an element (a value) and * two references to the left and right child nodes. *

* TODO: to get the most out of your learning, visit our website *

* * @param a type of elements that are stored in the tree * @author Taras Boychuk * @author Maksym Stasiuk */ public class RecursiveBinarySearchTree> implements BinarySearchTree { public static > RecursiveBinarySearchTree of(T... elements) { throw new ExerciseNotCompletedException(); } @Override public boolean insert(T element) { throw new ExerciseNotCompletedException(); } @Override public boolean contains(T element) { throw new ExerciseNotCompletedException(); } @Override public int size() { throw new ExerciseNotCompletedException(); } @Override public int depth() { throw new ExerciseNotCompletedException(); } @Override public void inOrderTraversal(Consumer consumer) { throw new ExerciseNotCompletedException(); } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-6-binary-search-tree/src/test/java/com/bobocode/cs/RecursiveBinarySearchTreeTest.java ================================================ package com.bobocode.cs; import lombok.SneakyThrows; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNullPointerException; import static org.junit.jupiter.params.provider.Arguments.arguments; /** * A reflection-based test class for {@link ArrayList}. *

* PLEASE NOTE: we use Reflection API only for learning purposes. It should NOT be used for production tests. * * @author Ivan Virchenko * @author Maksym Stasiuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class RecursiveBinarySearchTreeTest { private static final Predicate SIZE_FIELD = field -> field.getName().toLowerCase().contains("size") || field.getName().toLowerCase().contains("length"); private static final Predicate NODE_FIELD = field -> field.getType().getSimpleName().equals("Node"); private static final Predicate ELEMENT_FIELD = field -> field.getName().toLowerCase().contains("element") || field.getName().toLowerCase().contains("item") || field.getName().toLowerCase().contains("value"); private static final Predicate LEFT_FIELD = field -> field.getName().toLowerCase().contains("left") && field.getType().getSimpleName().equals("Node"); private static final Predicate RIGHT_FIELD = field -> field.getName().toLowerCase().contains("right") && field.getType().getSimpleName().equals("Node"); private static final Integer[] someElements = {10, 9, 11, 8, 12, 7}; private BinarySearchTree tree = new RecursiveBinarySearchTree<>(); @Test @Order(1) void properNodeClassNameCheck() { Class innerClass = getInnerClass(); String name = innerClass.getSimpleName(); assertThat(name).isEqualTo("Node"); } @Test @Order(2) void properTreeFieldsCheck() { Class treeClass = tree.getClass(); boolean hasSizeField = Arrays.stream(treeClass.getDeclaredFields()) .anyMatch(SIZE_FIELD); boolean hasNodeField = Arrays.stream(treeClass.getDeclaredFields()) .anyMatch(NODE_FIELD); assertThat(hasSizeField).isTrue(); assertThat(hasNodeField).isTrue(); } @Test @Order(3) void properNodeFieldsCheck() { Class innerClass = getInnerClass(); boolean isElement = Arrays.stream(innerClass.getDeclaredFields()) .anyMatch(ELEMENT_FIELD); boolean isLeft = Arrays.stream(innerClass.getDeclaredFields()) .anyMatch(LEFT_FIELD); boolean isRight = Arrays.stream(innerClass.getDeclaredFields()) .anyMatch(RIGHT_FIELD); assertThat(isElement).isTrue(); assertThat(isLeft).isTrue(); assertThat(isRight).isTrue(); } @Test @Order(4) void of() { tree = RecursiveBinarySearchTree.of(someElements); for (var e : someElements) { assertThat(contains(getRootObject(), e)).isTrue(); } assertThat(getInnerSize()).isEqualTo(someElements.length); } @Test @Order(5) void insert() { for (Integer e : someElements) { assertThat(contains(getRootObject(), e)).isFalse(); //does not contain assertThat(tree.insert(e)).isTrue(); //do insert assertThat(contains(getRootObject(), e)).isTrue(); //and contains } } @Test @Order(6) void insertToRootIfTreeIsEmpty() { assertThat(getRootObject()).isNull(); tree.insert(10); assertThat(getRootObject()).isNotNull(); } @Test @Order(7) void insertLeftIfLessThanRoot() { tree.insert(10); //root tree.insert(5); //left assertThat(getLeftNode(getRootObject())).isNotNull(); } @Test @Order(8) void insertRightIfGreaterThanRoot() { tree.insert(10); //root tree.insert(15); //right assertThat(getRightNode(getRootObject())).isNotNull(); } @Test @Order(9) void insertDoesNotAddDuplicateElements() { fillTestTree(10, 11, 12); assertThat(tree.insert(10)).isFalse(); assertThat(tree.insert(11)).isFalse(); assertThat(tree.insert(12)).isFalse(); } @Test @Order(10) void insertThrowsExceptionWhenArgumentIsNull() { fillTestTree(someElements); assertThatNullPointerException().isThrownBy( () -> tree.insert(null) ); } @Test @Order(11) void containsReturnsTrueIfElementExist() { fillTestTree(9, 10, 11); assertThat(tree.contains(10)).isTrue(); assertThat(tree.contains(9)).isTrue(); assertThat(tree.contains(11)).isTrue(); } @Test @Order(12) void containsReturnsFalseIfElementDoesntExist() { fillTestTree(someElements); assertThat(tree.contains(100)).isFalse(); } @Test @Order(13) void containsThrowsExceptionIFParameterIsNull() { assertThatNullPointerException().isThrownBy(() -> tree.contains(null)); } @Test @Order(14) void sizeIsGrowingWhenInserting() { tree.insert(10); tree.insert(15); tree.insert(20); assertThat(getInnerSize()).isEqualTo(3); } @Test @Order(15) void sizeDoesNotGrowWhenInsertingNotUnique() { fillTestTree(10, 11, 12); assertThat(getInnerSize()).isEqualTo(3); tree.insert(10); tree.insert(11); tree.insert(12); assertThat(getInnerSize()).isEqualTo(3); } @Order(16) @ParameterizedTest @MethodSource("depthArguments") void depth(Integer[] elements, int depth) { fillTestTree(elements); assertThat(tree.depth()).isEqualTo(depth); } @Test @Order(17) void depthGrowWhenInsert() { tree.insert(13); tree.insert(11); tree.insert(12); tree.insert(15); tree.insert(-15); assertThat(tree.depth()).isGreaterThan(0); } @Test @Order(18) void depthIsZeroIfRootIsNull() { assertThat(tree.depth()).isEqualTo(0); } @Test @Order(19) void inorderTraversal() { fillTestTree(someElements); Integer[] sortedElements = Arrays.copyOf(someElements, someElements.length); Arrays.sort(sortedElements); List traversedElements = new ArrayList<>(getInnerSize()); tree.inOrderTraversal(traversedElements::add); assertThat(traversedElements).isEqualTo(List.of(sortedElements)); } public static Stream depthArguments() { return Stream.of( //empty tree arguments(new Integer[]{}, 0), //tree with a single element arguments(new Integer[]{24}, 0), /* * .......10 * ....../ \ * .....5 15 * ..../ \ * ...1 20 */ arguments(new Integer[]{10, 5, 15, 1, 20}, 2), /* * ..1 * ...\ * ....2 * .....\ * ..... 3 * .......\ * ........4 * .........\ * ..........5 */ arguments(new Integer[]{1, 2, 3, 4, 5}, 4), /* * .........6 * ....../.....\ * .....2.......7 * .../...\......\ * ..1.....5......8 * ......./........\ * ......4..........9 * ...../............. * ....3............... */ arguments(new Integer[]{6, 2, 7, 1, 5, 8, 4, 9, 3}, 4)); } @SneakyThrows private int getInnerSize() { return (int) getInnerSizeField().get(tree); } private Field getInnerSizeField() { Field sizeField = Arrays.stream(tree.getClass().getDeclaredFields()) .filter(SIZE_FIELD) .findAny() .orElseThrow(); sizeField.setAccessible(true); return sizeField; } private Class getInnerClass() { return Arrays.stream(tree.getClass().getDeclaredClasses()) .filter(Class::isMemberClass) .findAny() .orElseThrow(); } @SneakyThrows private Field getRootField() { Field nodeField = Arrays.stream(tree.getClass().getDeclaredFields()) .filter(NODE_FIELD) .findAny() .orElseThrow(); nodeField.setAccessible(true); return nodeField; } @SneakyThrows private Object getRootObject() { Field nodeField = Arrays.stream(tree.getClass().getDeclaredFields()) .filter(NODE_FIELD) .findAny() .orElseThrow(); nodeField.setAccessible(true); return nodeField.get(tree); } @SneakyThrows private boolean contains(Object node, int element) { return findNodeByElement(node, element) != null; } private Object findNodeByElement(Object node, int element) { if (node == null) { return null; } if (element == getElement(node)) { return node; } else if (element < getElement(node)) { return findNodeByElement(getLeftNode(node), element); } else if (element > getElement(node)) { return findNodeByElement(getRightNode(node), element); } else { return node; } } @SneakyThrows private Field getNodesField(Object node, Predicate option) { Field field = Arrays.stream(node.getClass().getDeclaredFields()) .filter(option) .findAny() .orElseThrow(); field.setAccessible(true); return field; } @SneakyThrows private int getElement(Object node) { return (int) getNodesField(node, ELEMENT_FIELD).get(node); } @SneakyThrows private Object getLeftNode(Object node) { return getNodesField(node, LEFT_FIELD).get(node); } @SneakyThrows private Object getRightNode(Object node) { return getNodesField(node, RIGHT_FIELD).get(node); } @SneakyThrows private Object newNode(int element) { Object nodeInstance; Constructor[] constructors = getInnerClass().getDeclaredConstructors(); Constructor constructor; constructor = constructors[0]; constructor.setAccessible(true); if (constructor.getParameters().length == 1) { nodeInstance = constructor.newInstance(element); } else { nodeInstance = constructor.newInstance(); Field nodeElement = getNodesField(nodeInstance, ELEMENT_FIELD); nodeElement.set(nodeInstance, element); } return nodeInstance; } @SneakyThrows private void insertElement(int element) { Object root = getRootObject(); if (root == null) { getRootField().set(tree, newNode(element)); } else { insertToSubtree(root, element); } } private boolean insertToSubtree(Object node, int element) { if (element < getElement(node)) { return insertLeft(node, element); } else if (element > getElement(node)) { return insertRight(node, element); } else { return false; } } @SneakyThrows private boolean insertLeft(Object node, int element) { if (getLeftNode(node) != null) { return insertToSubtree(getLeftNode(node), element); } else { getNodesField(node, LEFT_FIELD).set(node, newNode(element)); return true; } } @SneakyThrows private boolean insertRight(Object node, int element) { if (getRightNode(node) != null) { return insertToSubtree(getRightNode(node), element); } else { getNodesField(node, RIGHT_FIELD).set(node, newNode(element)); return true; } } @SneakyThrows private void fillTestTree(Integer... elements) { tree = new RecursiveBinarySearchTree<>(); for (Integer e : elements) { insertElement(e); } getInnerSizeField().set(tree, elements.length); } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-9-hash-table/README.md ================================================ # Hash Table Learn a Hash Table data structures and build strong skills creating a hash table-based implementation of a Map interface 💪 ### Objectives (at the end of the training you will be able to...) * understand the **main idea** behind the **Hash Table** data structure ✅ * explain why do we need a `hashCode` method and how it speeds up the search ✅ * implement key-value class `Node` ✅ * implement a simple `HashTable` based on the array of linked `Node` objects * realize the limitations of the Hash Table ✅ * understand why do we need the resizing logic and how does it work ✅ --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 2-0-data-structures-and-algorithms/2-2-9-hash-table/pom.xml ================================================ 2-0-data-structures-and-algorithms com.bobocode 1.0-SNAPSHOT 4.0.0 2-2-9-hash-table ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-9-hash-table/src/main/java/com/bobocode/cs/HashTable.java ================================================ package com.bobocode.cs; import com.bobocode.util.ExerciseNotCompletedException; /** * {@link HashTable} is a simple Hashtable-based implementation of {@link Map} interface with some additional methods. * It is based on the array of {@link Node} objects. Both {@link HashTable} and {@link Node} have two type parameters: * K and V, which represent key and value. *

* Elements are stored int the table by their key. A table is basically an array, and fast access is possible due to * array capabilities. (You can access an array element by its index in O(1) time). In order to find an index for any * given key, it uses calculateIndex method which is based on the element's hash code. *

* If two elements (keys) have the same array index, they form a linked list. That's why class {@link Node} requires * a reference to the next field. *

* Since you don't always know the number of elements in advance, the table can be resized. You can do that manually by * calling method resizeTable, or it will be done automatically once the table reach resize threshold. *

* The initial array size (initial capacity) is 8. *

* TODO: to get the most out of your learning, visit our website *

* * @param key type * @param value type * @author Taras Boychuk */ public class HashTable implements Map { /** * This method is a critical part of the hast table. The main idea is that having a key, you can calculate its index * in the array using the hash code. Since the computation is done in constant time (O(1)), it's faster than * any other kind search. *

* It's a function that accepts a key and calculates its index using a hash code. Please note that index cannot be * equal or greater than array size (table capacity). *

* This method is used for all other operations (put, get, remove). * * @param key * @param tableCapacity underlying array size * @return array index of the given key */ public static int calculateIndex(Object key, int tableCapacity) { throw new ExerciseNotCompletedException(); // todo: } /** * Creates a mapping between provided key and value, and returns the old value. If there was no such key, it returns * null. {@link HashTable} does not support duplicate keys, so if you put the same key it just overrides the value. *

* It uses calculateIndex method to find the corresponding array index. Please note, that even different keys can * produce the same array index. * * @param key * @param value * @return old value or null */ @Override public V put(K key, V value) { throw new ExerciseNotCompletedException(); // todo: } /** * Retrieves a value by the given key. It uses calculateIndex method to find the corresponding array index. * Then it iterates though all elements that are stored by that index, and uses equals to compare its keys. * * @param key * @return value stored in the table by the given key or null if there is no such key */ @Override public V get(K key) { throw new ExerciseNotCompletedException(); // todo: } /** * Checks if the table contains a given key. * * @param key * @return true is there is such key in the table or false otherwise */ @Override public boolean containsKey(K key) { throw new ExerciseNotCompletedException(); // todo: } /** * Checks if the table contains a given value. * * @param value * @return true is there is such value in the table or false otherwise */ @Override public boolean containsValue(V value) { throw new ExerciseNotCompletedException(); // todo: } /** * Return a number of elements in the table. * * @return size */ @Override public int size() { throw new ExerciseNotCompletedException(); // todo: } /** * Checks is the table is empty. * * @return true is table size is zero or false otherwise */ @Override public boolean isEmpty() { throw new ExerciseNotCompletedException(); // todo: } /** * Removes an element by its key and returns a removed value. If there is no such key in the table, it returns null. * * @param key * @return removed value or null */ @Override public V remove(K key) { throw new ExerciseNotCompletedException(); // todo: } /** * It's a special toString method dedicated to help you visualize a hash table. It creates a string that represents * an underlying array as a table. It has multiples rows. Every row starts with an array index followed by ": ". * Then it adds every key and value (key=value) that have a corresponding index. Every "next" reference is * represented as an arrow like this " -> ". *

* E.g. imagine a table, where the key is a string username, and the value is the number of points of that user. * Is this case method toString can return something like this: *

     * 0: johnny=439
     * 1:
     * 2: madmax=833 -> leon=886
     * 3:
     * 4: altea=553
     * 5:
     * 6:
     * 7:
     * 
* * @return */ @Override public String toString() { throw new ExerciseNotCompletedException(); // todo: } /** * Creates a new underlying table with a given size and adds all elements to the new table. *

* In order to allow a fast access, this hash table needs to have a sufficient capacity. * (You can imagine a hash table, with a default capacity of 8 that stores hundreds of thousands of elements. * In that case it's just 8 huge linked lists. That's why we need this method.) *

* PLEASE NOTE that such method should not be a part of the public API, but it was made public * for learning purposes. You can create a table, print it using toString, then resizeTable and print it again. * It will help you to understand how it works. * * @param newCapacity a size of the new underlying array */ public void resizeTable(int newCapacity) { throw new ExerciseNotCompletedException(); // todo: } } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-9-hash-table/src/main/java/com/bobocode/cs/Map.java ================================================ package com.bobocode.cs; /** * A {@link Map} is a simplified interface of so-called dictionary. It maps keys to values and provides an API for data * access and manipulation. Please note that a map does not support duplicate keys. *

* It was added as a simple contact on top of a {@link HashTable} class. * * @param key type * @param value type * @author Taras Boychuk */ public interface Map { /** * Creates or updates a mapping for a given key and value. If the key is new, it creates a mapping and return null. * If the key exists, it updates the value and returns the old value. * * @param key * @param value * @return an old value or null */ V put(K key, V value); /** * Returns the value that is mapped to the given key, or null if there is no such key. * * @param key * @return the value that is mapped to the given key, or null if there is no such key */ V get(K key); /** * Returns true if a given key exist or false otherwise. * * @param key * @return true if a given key exist or false otherwise */ boolean containsKey(K key); /** * Returns true if a given value exist or false otherwise. * * @param value * @return true if a given value exist or false otherwise */ boolean containsValue(V value); /** * Returns the number of entries (key-value mappings). * * @return the number of entries */ int size(); /** * Returns true if there is no entries or false otherwise. * * @return true if there is no entries or false otherwise */ boolean isEmpty(); /** * Removes a mapping for a given key, and returns a removed value. If there is no such key, it just returns null. * * @param key * @return a removed value or null */ V remove(K key); } ================================================ FILE: 2-0-data-structures-and-algorithms/2-2-9-hash-table/src/test/java/com/bobocode/cs/HashTableTest.java ================================================ package com.bobocode.cs; import lombok.SneakyThrows; import org.junit.jupiter.api.ClassOrderer.OrderAnnotation; import org.junit.jupiter.api.*; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.lang.reflect.Modifier.isStatic; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.*; /** * A Reflection-based step by step test for a {@link HashTable} class. PLEASE NOTE that Reflection API should not be used * for testing a production code. We use it for learning purposes only! * * @author Taras Boychuk */ @TestClassOrder(OrderAnnotation.class) @DisplayName("HashTable Test") class HashTableTest { private HashTable hashTable = new HashTable<>(); @Nested @Order(1) @DisplayName("1. Node Test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class NodeClassTest { @Test @Order(1) @DisplayName("A nested class Node exists") void nodeClassExists() { var nestedClassList = Arrays.stream(HashTable.class.getDeclaredClasses()) .map(Class::getSimpleName) .toList(); assertThat(nestedClassList).contains("Node"); } @Test @Order(2) @DisplayName("Node is a static nested class") @SneakyThrows void nodeIsStaticClass() { var nodeClass = Class.forName("com.bobocode.cs.HashTable$Node"); assertTrue(isStatic(nodeClass.getModifiers())); } @Test @Order(3) @DisplayName("Node class has two type parameters") @SneakyThrows void nodeHasTwoTypeParams() { var nodeClass = Class.forName("com.bobocode.cs.HashTable$Node"); var typeParametersList = nodeClass.getTypeParameters(); assertThat(typeParametersList).hasSize(2); } @Test @Order(4) @DisplayName("Node class has 'key' and 'value' fields") @SneakyThrows void nodeHasKeyValuesFields() { var nodeClass = Class.forName("com.bobocode.cs.HashTable$Node"); var fieldNames = Arrays.stream(nodeClass.getDeclaredFields()) .map(Field::getName) .toList(); assertThat(fieldNames).contains("key").contains("value"); } @Test @Order(5) @DisplayName("Node class has a field 'next'") @SneakyThrows void nodeHasReferenceToNext() { var nodeClass = Class.forName("com.bobocode.cs.HashTable$Node"); var fieldNames = Arrays.stream(nodeClass.getDeclaredFields()) .map(Field::getName) .toList(); assertThat(fieldNames).contains("next"); } @Test @Order(6) @DisplayName("Node class has one constructor") @SneakyThrows void nodeHasOneConstructor() { var nodeClass = Class.forName("com.bobocode.cs.HashTable$Node"); var constructors = nodeClass.getDeclaredConstructors(); assertThat(constructors).hasSize(1); } @Test @Order(7) @DisplayName("Node constructor accept key and value") @SneakyThrows void nodeConstructorAcceptKeyValue() { var nodeClass = Class.forName("com.bobocode.cs.HashTable$Node"); var constructor = nodeClass.getDeclaredConstructors()[0]; assertThat(constructor.getParameters()).hasSize(2); assertThat(constructor.getParameters()[0].getName()).isEqualTo("key"); assertThat(constructor.getParameters()[1].getName()).isEqualTo("value"); } } @Nested @Order(2) @DisplayName("2. HashTable fields Test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class HashTableFieldsTest { @Test @Order(1) @DisplayName("HastTable has a field 'table' which is an array of nodes") @SneakyThrows void tableFieldExists() { var tableField = HashTable.class.getDeclaredField("table"); var tableType = tableField.getType(); var nodeClass = Class.forName("com.bobocode.cs.HashTable$Node"); assertTrue(tableType.isArray()); assertThat(tableType.getComponentType()).isEqualTo(nodeClass); } @Test @Order(2) @DisplayName("HashTable has an integer field 'size'") @SneakyThrows void sizeFieldExists() { var sizeField = HashTable.class.getDeclaredField("size"); assertThat(sizeField.getType()).isEqualTo(int.class); } } @Nested @Order(3) @DisplayName("3. HashTable constructors Test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class HashTableConstructorsTest { @Test @Order(1) @SneakyThrows @DisplayName("A default constructor initializes an array with default size 8") void defaultConstructor() { var defaultConstructor = HashTable.class.getConstructor(); var hashTable = defaultConstructor.newInstance(); var table = getInternalTable(hashTable); assertThat(table).hasSize(8); } @Test @Order(2) @SneakyThrows @DisplayName("An additional constructor accepts an initial array size") void constructorWithTableCapacity() { var constructor = HashTable.class.getConstructor(int.class); var hashTable = constructor.newInstance(16); var table = getInternalTable(hashTable); assertThat(table).hasSize(16); } @Test @Order(3) @SneakyThrows @DisplayName("An additional constructor throws exception when argument is negative") void constructorWithTableCapacityWhenArgumentIsNegative() { var constructor = HashTable.class.getConstructor(int.class); assertThatThrownBy(() -> constructor.newInstance(-2)) .hasCauseInstanceOf(IllegalArgumentException.class); } } @Nested @Order(4) @DisplayName("4. Hash Function Test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class HashFunctionTest { @Test @Order(1) @DisplayName("calculateIndex returns the same value for the same key") void calculateIndexReturnTheSameValueWhenKeyIsTheSame() { var indexSet = Stream.generate(() -> "ASDFDFSD34234234") .limit(10) .map(key -> HashTable.calculateIndex(key, 8)) .collect(Collectors.toSet()); assertThat(indexSet).hasSize(1); } @Test @Order(2) @DisplayName("calculateIndex returns different values for different keys") void calculateIndexReturnDifferentValuesWheKeysAreDifferent() { var arrayCapacity = 8; var indexSet = Stream.of("A", "Aa", "AaB", "4234", "2234fasdf", "ASDFDFSD34234234", "afsd-fdfd-ae43-5gd3") .map(str -> HashTable.calculateIndex(str, arrayCapacity)) .collect(Collectors.toSet()); assertThat(indexSet) .hasSizeGreaterThan(1); } @Test @Order(3) @DisplayName("calculateIndex returns values in array bounds") void calculateIndexReturnIndexInArrayBounds() { var arrayCapacity = 8; var keys = Stream.generate(() -> ThreadLocalRandom.current().nextLong()) .limit(100) .toList(); var indexes = keys.stream() .map(key -> HashTable.calculateIndex(key, arrayCapacity)) .toList(); assertThat(indexes) .isNotEmpty() .allMatch(i -> i >= 0 && i < arrayCapacity); } @Test @Order(4) @DisplayName("calculateIndex return non-negative value when hashCode is negative") void calculateIndexReturnPositiveIndexWhenHashCodeIsNegative() { var key = Long.MAX_VALUE; var index = HashTable.calculateIndex(key, 8); assertThat(index).isNotNegative(); } } @Nested @Order(5) @DisplayName("5. HashTable methods Test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class HashTableMethodsTest { @Test @SneakyThrows @Order(1) @DisplayName("put creates new entry and returns null when the table is empty, should increase the table size") void putWhenTableIsEmpty() { var previousValue = hashTable.put("madmax", 833); var keyValueExists = checkKeyValueExists("madmax", 833); assertNull(previousValue); assertTrue(keyValueExists); assertEquals(1, getSize()); } @Test @Order(2) @DisplayName("put elements adds entry to the same bucket and increases table size when the hash code is the same") @SneakyThrows void putTwoElementsWithTheSameHashCode() { var table = getInternalTable(hashTable); var prevValueA = hashTable.put("AaAa", 123); var prevValueB = hashTable.put("BBBB", 456); var containsKeyValueA = checkKeyValueExists("AaAa", 123); var containsKeyValueB = checkKeyValueExists("BBBB", 456); var bucketIndexA = HashTable.calculateIndex("AaAa", table.length); var bucketIndexB = HashTable.calculateIndex("BBBB", table.length); assertNull(prevValueA); assertNull(prevValueB); assertTrue(containsKeyValueA); assertTrue(containsKeyValueB); assertThat(bucketIndexA).isEqualTo(bucketIndexB); assertEquals(2, getSize()); } @Test @Order(3) @DisplayName( "put element updates the value and returns the previous one when key is the same, should not increase table size") void putElementWithTheSameKey() { hashTable.put("madmax", 833); var previousValue = hashTable.put("madmax", 876); var containsNewValueByKey = checkKeyValueExists("madmax", 876); assertThat(previousValue).isEqualTo(833); assertTrue(containsNewValueByKey); assertEquals(1, getSize()); } @Test @Order(4) @DisplayName("get returns null when given key does not exists") void getElementWhenKeyDoesNotExists() { var foundValue = hashTable.get("xxx"); assertNull(foundValue); } @Test @Order(5) @DisplayName("get returns a corresponding value by the given key") void getWhenKeyExists() { addToTable("madmax", 833); var foundValue = hashTable.get("madmax"); assertThat(foundValue).isEqualTo(833); } @Test @Order(6) @DisplayName("get returns a corresponding value when there are other keys with the same index") void getWhenOtherKeyHaveTheSameIndex() { addToTable("madmax", 833); addToTable("AaAa", 654); addToTable("BBBB", 721); var foundValue = hashTable.get("BBBB"); assertThat(foundValue).isEqualTo(721); } @Test @Order(7) @DisplayName("containsKey returns true if element exists") void containsKeyWhenElementExists() { addToTable("madmax", 833); var result = hashTable.containsKey("madmax"); assertThat(result).isTrue(); } @Test @Order(8) @DisplayName("containsKey returns false if element does not exist") void containsKeyWhenElementDoesNotExist() { var result = hashTable.containsKey("madmax"); assertThat(result).isFalse(); } @Test @Order(9) @DisplayName("containsValue returns true if value exists") void containsValue() { addToTable("madmax", 833); var result = hashTable.containsValue(833); assertThat(result).isTrue(); } @Test @Order(9) @DisplayName("containsValue returns false if value does not exist") void containsValueWhenItDoesNotExist() { addToTable("madmax", 833); var result = hashTable.containsValue(666); assertThat(result).isFalse(); } @Test @Order(10) @DisplayName("containsValue returns true if the same value appears multiple times") void containsValueWhenValueAppearsMultipleTimes() { addToTable("madmax", 833); addToTable("bobby", 833); addToTable("altea", 833); var result = hashTable.containsValue(833); assertThat(result).isTrue(); } @Test @Order(11) @SneakyThrows @DisplayName("size returns the number of entries in the table") void size() { setSize(12); var size = hashTable.size(); assertThat(size).isEqualTo(12); } @Test @Order(12) @DisplayName("isEmpty returns false when there are some elements") void isEmptyWhenTableGetsElements() { addToTable("madmax", 833); setSize(1); var empty = hashTable.isEmpty(); assertFalse(empty); } @Test @Order(13) @DisplayName("isEmpty returns true when there is no elements") void isEmptyWhenThereIsNoElements() { var empty = hashTable.isEmpty(); assertTrue(empty); } @Test @Order(13) @DisplayName("remove deletes the entry, decreases table size and returns a value") void remove() { addToTable("madmax", 833); setSize(1); var result = hashTable.remove("madmax"); assertThat(result).isEqualTo(833); assertFalse(checkKeyValueExists("madmaxx", 833)); assertEquals(0, getSize()); } @Test @Order(14) @DisplayName("remove returns null when key does not exists") void removeWhenKeyDoesNotExists() { var result = hashTable.remove("madmax"); assertNull(result); } @Test @Order(15) @DisplayName("remove deletes the element when it's in the middle of the list and decreases the size of table") void removeFromTheMiddleOfTheList() { addToTable("AaAa", 843); addToTable("BBBB", 434); addToTable("AaBB", 587); var size = 3; setSize(size); var removedValue = hashTable.remove("BBBB"); assertTrue(checkKeyValueExists("AaAa", 843)); assertFalse(checkKeyExists("BBBB")); assertTrue(checkKeyValueExists("AaBB", 587)); assertThat(removedValue).isEqualTo(434); assertEquals(size - 1, getSize()); } @Test @Order(16) @DisplayName("remove deletes the element when it's in the end of the list and decreases the size of table") void removeFromTheEndOfTheList() { addToTable("AaAa", 843); addToTable("BBBB", 434); addToTable("AaBB", 587); var size = 3; setSize(size); var removedValue = hashTable.remove("AaBB"); assertTrue(checkKeyValueExists("AaAa", 843)); assertTrue(checkKeyValueExists("BBBB", 434)); assertFalse(checkKeyExists("AaBB")); assertThat(removedValue).isEqualTo(587); assertEquals(2, getSize()); } } @Nested @Order(6) @DisplayName("6. Helper methods Test") @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class HashTableHelperMethodsTest { @Test @Order(1) @DisplayName("resizeTable creates a new array and put there all elements") void resizeTable() { addToTable("madmax", 833); addToTable("altea", 553); addToTable("AaAa", 123); addToTable("BBBB", 456); hashTable.resizeTable(16); assertThat(getInternalTable(hashTable)).hasSize(16); assertTrue(checkKeyValueExists("madmax", 833)); assertTrue(checkKeyValueExists("altea", 553)); assertTrue(checkKeyValueExists("AaAa", 123)); assertTrue(checkKeyValueExists("BBBB", 456)); } @Test @DisplayName("toString returns a string that represents an underlying table") void toStringTest() { addToTable("madmax", 833); addToTable("altea", 553); addToTable("johnny", 439); addToTable("leon", 886); var table = getInternalTable(hashTable); var expectedString = tableToString(table); String tableStr = hashTable.toString(); assertThat(tableStr).isEqualTo(expectedString); } } // Util methods @SneakyThrows private Object[] getInternalTable(HashTable hashTable) { var tableField = HashTable.class.getDeclaredField("table"); tableField.setAccessible(true); return (Object[]) tableField.get(hashTable); } private void addToTable(String key, Integer value) { var table = getInternalTable(hashTable); var index = HashTable.calculateIndex(key, table.length); var newNode = createNewNode(key, value); if (table[index] == null) { table[index] = newNode; } else { var current = new NodeProxy(table[index]); while (current.next() != null && !current.key().equals(key)) { current = current.next(); } if (current.key().equals(key)) { current.setValue(value); } else { current.setNext(newNode); } } } private NodeProxy getNodeByKey(Object key) { var table = getInternalTable(hashTable); for (var head : table) { if (head != null) { var current = new NodeProxy(head); while (current != null) { if (current.key().equals(key)) { return current; } current = current.next(); } } } return null; } private boolean checkKeyValueExists(Object key, Object value) { var node = getNodeByKey(key); return node != null && node.value().equals(value); } private boolean checkKeyExists(Object key) { var node = getNodeByKey(key); return node != null; } @SneakyThrows private void setSize(int size) { var sizeField = HashTable.class.getDeclaredField("size"); sizeField.setAccessible(true); sizeField.set(hashTable, size); } @SneakyThrows private int getSize() { var sizeField = HashTable.class.getDeclaredField("size"); sizeField.setAccessible(true); return sizeField.getInt(hashTable); } private String tableToString(Object[] table) { StringBuilder result = new StringBuilder(); var n = table.length; for (int i = 0; i < n; i++) { result.append(i).append(": "); if (table[i] != null) { var current = new NodeProxy(table[i]); while (current.next() != null) { result.append(current.key()).append("=").append(current.value()).append(" -> "); current = current.next(); } result.append(current.key()).append("=").append(current.value()); } result.append("\n"); } return result.toString(); } @SneakyThrows private Object createNewNode(String key, Integer value) { var nodeClass = Class.forName("com.bobocode.cs.HashTable$Node"); var constructor = nodeClass.getConstructor(Object.class, Object.class); return constructor.newInstance(key, value); } static class NodeProxy { Class targetClass; Field keyField; Field valueField; Field nextField; Object target; @SneakyThrows public NodeProxy(Object target) { Objects.requireNonNull(target); this.targetClass = Class.forName("com.bobocode.cs.HashTable$Node"); this.target = target; this.keyField = targetClass.getDeclaredField("key"); this.keyField.setAccessible(true); this.valueField = targetClass.getDeclaredField("value"); this.valueField.setAccessible(true); this.nextField = targetClass.getDeclaredField("next"); this.nextField.setAccessible(true); } @SneakyThrows public Object key() { return keyField.get(target); } @SneakyThrows public Object value() { return valueField.get(target); } @SneakyThrows public NodeProxy next() { return Optional.ofNullable(nextField.get(target)) .map(NodeProxy::new) .orElse(null); } @SneakyThrows public void setValue(Object value) { valueField.set(target, value); } @SneakyThrows public void setNext(Object newNode) { nextField.set(target, newNode); } } } ================================================ FILE: 2-0-data-structures-and-algorithms/README.md ================================================ # Data Structures & Angorithms Learn Data Structures & Algorithms and build strong related skills needed for enterprise Java development 💪 ================================================ FILE: 2-0-data-structures-and-algorithms/data-structures-and-algorithms-util/pom.xml ================================================ 2-0-data-structures-and-algorithms com.bobocode 1.0-SNAPSHOT 4.0.0 data-structures-and-algorithms-util ================================================ FILE: 2-0-data-structures-and-algorithms/data-structures-and-algorithms-util/src/main/java/com/bobocode/cs/List.java ================================================ package com.bobocode.cs; public interface List { void add(T element); void add(int index, T element); void set(int index, T element); T get(int index); T getFirst(); T getLast(); T remove(int index); boolean contains(T element); boolean isEmpty(); int size(); void clear(); } ================================================ FILE: 2-0-data-structures-and-algorithms/pom.xml ================================================ 4.0.0 pom 2-2-1-node 2-2-2-stack 2-2-3-linked-queue 2-2-4-linked-list 2-2-5-array-list 2-2-6-binary-search-tree 2-2-9-hash-table data-structures-and-algorithms-util com.bobocode java-fundamentals-exercises 1.0-SNAPSHOT 2-0-data-structures-and-algorithms com.bobocode java-fundamentals-util 1.0-SNAPSHOT compile ================================================ FILE: 3-0-java-core/3-6-1-file-reader/README.MD ================================================ # File Reader Improve your Java SE skills by implementing a logic that reads file content using Stream API 💪 ### Pre-conditions ❗ You're supposed to know the basics of Stream API and how to work with files in Java ### Objectives * **find a text file** in the classpath ✅ * **open a** `Stream` of file lines ✅ * **collect** file content into a single `String` ✅ * **deal with exceptions** when accessing the file ✅ --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 3-0-java-core/3-6-1-file-reader/pom.xml ================================================ 3-0-java-core com.bobocode 1.0-SNAPSHOT 4.0.0 3-6-1-file-reader ================================================ FILE: 3-0-java-core/3-6-1-file-reader/src/main/java/com/bobocode/se/FileReaders.java ================================================ package com.bobocode.se; import com.bobocode.util.ExerciseNotCompletedException; /** * {@link FileReaders} provides an API that allow to read whole file into a {@link String} by file name. */ public class FileReaders { /** * Returns a {@link String} that contains whole text from the file specified by name. * * @param fileName a name of a text file * @return string that holds whole file content */ public static String readWholeFile(String fileName) { throw new ExerciseNotCompletedException(); //todo } } ================================================ FILE: 3-0-java-core/3-6-1-file-reader/src/test/java/com/bobocode/se/FileReadersTest.java ================================================ package com.bobocode.se; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class FileReadersTest { @Test void testReadWholeFileOnEmptyFile() { String fileContent = FileReaders.readWholeFile("empty.txt"); assertEquals("", fileContent); } @Test void testReadWholeFileOnFileWithEmptyLines() { String fileContent = FileReaders.readWholeFile("lines.txt"); assertEquals("Hey!\n" + "\n" + "What's up?\n" + "\n" + "Hi!", fileContent); } @Test void testReadWholeFile() { String fileContent = FileReaders.readWholeFile("simple.txt"); assertEquals("Hello!\n" + "It's a test file.", fileContent); } } ================================================ FILE: 3-0-java-core/3-6-1-file-reader/src/test/resources/empty.txt ================================================ ================================================ FILE: 3-0-java-core/3-6-1-file-reader/src/test/resources/lines.txt ================================================ Hey! What's up? Hi! ================================================ FILE: 3-0-java-core/3-6-1-file-reader/src/test/resources/simple.txt ================================================ Hello! It's a test file. ================================================ FILE: 3-0-java-core/3-6-2-file-stats/README.MD ================================================ # Files Stats Improve your Stream API skills calculating character statistic using a text file 💪 ### Pre-conditions ❗ You're supposed to know how to work with text files and be able to write Java code ### Objectives * **find a text file** in the classpath ✅ * **open a** `Stream` of file lines ✅ * **transform** a stream of lines into a stream of characters * **group** characters by value and calculate needed stats ✅ * **deal with exceptions** when accessing the file ✅ --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##
================================================ FILE: 3-0-java-core/3-6-2-file-stats/pom.xml ================================================ 3-0-java-core com.bobocode 1.0-SNAPSHOT 4.0.0 3-6-2-file-stats ================================================ FILE: 3-0-java-core/3-6-2-file-stats/src/main/java/com/bobocode/se/FileStats.java ================================================ package com.bobocode.se; import com.bobocode.util.ExerciseNotCompletedException; /** * {@link FileStats} provides an API that allow to get character statistic based on text file. All whitespace characters * are ignored. */ public class FileStats { /** * Creates a new immutable {@link FileStats} objects using data from text file received as a parameter. * * @param fileName input text file name * @return new FileStats object created from text file */ public static FileStats from(String fileName) { throw new ExerciseNotCompletedException(); //todo } /** * Returns a number of occurrences of the particular character. * * @param character a specific character * @return a number that shows how many times this character appeared in a text file */ public int getCharCount(char character) { throw new ExerciseNotCompletedException(); //todo } /** * Returns a character that appeared most often in the text. * * @return the most frequently appeared character */ public char getMostPopularCharacter() { throw new ExerciseNotCompletedException(); //todo } /** * Returns {@code true} if this character has appeared in the text, and {@code false} otherwise * * @param character a specific character to check * @return {@code true} if this character has appeared in the text, and {@code false} otherwise */ public boolean containsCharacter(char character) { throw new ExerciseNotCompletedException(); //todo } } ================================================ FILE: 3-0-java-core/3-6-2-file-stats/src/main/java/com/bobocode/se/FileStatsException.java ================================================ package com.bobocode.se; public class FileStatsException extends RuntimeException{ public FileStatsException(String message) { super(message); } public FileStatsException(String message, Throwable cause) { super(message, cause); } } ================================================ FILE: 3-0-java-core/3-6-2-file-stats/src/test/java/com/bobocode/se/FileStatsTest.java ================================================ package com.bobocode.se; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import static org.assertj.core.api.Assertions.*; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class FileStatsTest { @Test @Order(1) void createFileStatsFromExistingFile() { FileStats fileStats = FileStats.from("sotl.txt"); } @Test @Order(2) void createFileStatsFromNonExistingFile() { assertThatThrownBy(() -> FileStats.from("blahblah.txt")).isInstanceOf(FileStatsException.class); } @Test @Order(3) void getCharCount() { FileStats lambdaArticleFileStats = FileStats.from("sotl.txt"); FileStats springCloudArticleFileStats = FileStats.from("scosb.txt"); int aCharCountInLambdaArticle = lambdaArticleFileStats.getCharCount('a'); int bCharCountInSpringArticle = springCloudArticleFileStats.getCharCount('b'); assertThat(aCharCountInLambdaArticle).isEqualTo(2345); assertThat(bCharCountInSpringArticle).isEqualTo(4); } @Test @Order(4) void getMostPopularCharacter() { FileStats lambdaArticleFileStats = FileStats.from("sotl.txt"); FileStats springCloudArticleFileStats = FileStats.from("scosb.txt"); char mostPopularCharacterInLambdaArticle = lambdaArticleFileStats.getMostPopularCharacter(); char mostPopularCharacterInSpringArticle = springCloudArticleFileStats.getMostPopularCharacter(); System.out.println(mostPopularCharacterInSpringArticle); assertThat(mostPopularCharacterInLambdaArticle).isEqualTo('e'); assertThat(mostPopularCharacterInSpringArticle).isEqualTo('e'); } @Test @Order(5) void containsCharacter() { FileStats lambdaArticleFileStats = FileStats.from("sotl.txt"); FileStats springCloudArticleFileStats = FileStats.from("scosb.txt"); boolean lambdaArticleContainsExistingCharacter = lambdaArticleFileStats.containsCharacter('a'); boolean lambdaArticleContainsWhitespace = lambdaArticleFileStats.containsCharacter(' '); boolean springArticleContainsExistingCharacter = springCloudArticleFileStats.containsCharacter('b'); boolean springArticleContainsWhitespace = springCloudArticleFileStats.containsCharacter(' '); assertThat(lambdaArticleContainsExistingCharacter).isTrue(); assertThat(lambdaArticleContainsWhitespace).isFalse(); assertThat(springArticleContainsExistingCharacter).isTrue(); assertThat(springArticleContainsWhitespace).isFalse(); } } ================================================ FILE: 3-0-java-core/3-6-2-file-stats/src/test/resources/scosb.txt ================================================ We’re pleased to announce that the 2.0.1 release of Spring Cloud Open Service Broker is now available. This release resolves a few issues that were raised since the 2.0.0 release. Thank you to the community for your interest and feedback! Spring Cloud Open Service Broker is a framework for building Spring Boot applications that implement the Open Service Broker API. The Open Service Broker API project allows developers to deliver services to applications running within cloud native platforms such as Cloud Foundry, Kubernetes, and OpenShift. ================================================ FILE: 3-0-java-core/3-6-2-file-stats/src/test/resources/sotl.txt ================================================ State of the Lambda September 2013 Java SE 8 Edition This is an informal overview of the enhancements to the Java programming language specified by JSR 335 and implemented in the OpenJDK Lambda Project. It refines the previous iteration posted in December 2011. A formal description of some of the language changes may be found in the Early Draft Specification for the JSR; an OpenJDK Developer Preview is also available. Additional historical design documents can be found at the OpenJDK project page. There is also a companion document, State of the Lambda, Libraries Edition, describing the library enhancements added as part of JSR 335. The high-level goal of Project Lambda is to enable programming patterns that require modeling code as data to be convenient and idiomatic in Java. The principal new language features include: Lambda expressions (informally, "closures" or "anonymous methods") Method and constructor references Expanded target typing and type inference Default and static methods in interfaces These are described and illustrated below. 1. Background Java is, primarily, an object-oriented programming language. In both object-oriented and functional languages, basic values can dynamically encapsulate program behavior: object-oriented languages have objects with methods, and functional languages have functions. This similarity may not be obvious, however, because Java objects tend to be relatively heavyweight: instantiations of separately-declared classes wrapping a handful of fields and many methods. Yet it is common for some objects to essentially encode nothing more than a function. In a typical use case, a Java API defines an interface, sometimes described as a "callback interface," expecting the user to provide an instance of the interface when invoking the API. For example: public interface ActionListener { void actionPerformed(ActionEvent e); } Rather than declaring a class that implements ActionListener for the sole purpose of allocating it once at an invocation site, a user typically instantiates the implementing class inline, anonymously: button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ui.dazzle(e.getModifiers()); } }); Many useful libraries rely on this pattern. It is particularly important for parallel APIs, in which the code to execute must be expressed independently of the thread in which it will run. The parallel-programming domain is of special interest, because as Moore's Law continues to give us more cores but not faster cores, serial APIs are limited to a shrinking fraction of available processing power. Given the increasing relevance of callbacks and other functional-style idioms, it is important that modeling code as data in Java be as lightweight as possible. In this respect, anonymous inner classes are imperfect for a number of reasons, primarily: Bulky syntax Confusion surrounding the meaning of names and this Inflexible class-loading and instance-creation semantics Inability to capture non-final local variables Inability to abstract over control flow This project addresses many of these issues. It eliminates (1) and (2) by introducing new, much more concise expression forms with local scoping rules, sidesteps (3) by defining the semantics of the new expressions in a more flexible, optimization-friendly manner, and ameliorates (4) by allowing the compiler to infer finality (allowing capture of effectively final local variables). However, it is not a goal of this project to address all the problems of inner classes. Neither arbitrary capture of mutable variables (4) nor nonlocal control flow (5) are within this project's scope (though such features may be revisited in a future iteration of the language.) 2. Functional interfaces The anonymous inner class approach, despite its limitations, has the nice property of fitting very cleanly into Java's type system: a function value with an interface type. This is convenient for a number of reasons: interfaces are already an intrinsic part of the type system; they naturally have a runtime representation; and they carry with them informal contracts expressed by Javadoc comments, such as an assertion that an operation is commutative. The interface ActionListener, used above, has just one method. Many common callback interfaces have this property, such as Runnable and Comparator. We'll give all interfaces that have just one method a name: functional interfaces. (These were previously called SAM Types, which stood for "Single Abstract Method".) Nothing special needs to be done to declare an interface as functional; the compiler identifies it as such based on its structure. (This identification process is a little more than just counting method declarations; an interface might redundantly declare a method that is automatically provided by the class Object, such as toString(), or might declare static or default methods, none of which count against the one-method limit.) However, API authors may additionally capture the design intent that an interface be functional (as opposed to accidentally having only one method) with the @FunctionalInterface annotation, in which case the compiler will validate that the interface meets the structural requirements to be a functional interface. An alternative (or complementary) approach to function types, suggested by some early proposals, would have been to introduce a new, structural function type, sometimes called arrow types. A type like "function from a String and an Object to an int" might be expressed as (String,Object)->int. This idea was considered and rejected, at least for now, due to several disadvantages: It would add complexity to the type system and further mix structural and nominal types (Java is almost entirely nominally typed). It would lead to a divergence of library styles -- some libraries would continue to use callback interfaces, while others would use structural function types. The syntax could be unwieldy, especially when checked exceptions were included. It is unlikely that there would be a runtime representation for each distinct function type, meaning developers would be further exposed to and limited by erasure. For example, it would not be possible (perhaps surprisingly) to overload methods m(T->U) and m(X->Y). So, we have instead followed the path of "use what you know" -- since existing libraries use functional interfaces extensively, we codify and leverage this pattern. This enables existing libraries to be used with lambda expressions. To illustrate, here is a sampling of some of the functional interfaces already in Java SE 7 that are well-suited for being used with the new language features; the examples that follow illustrate the use of a few of them. java.lang.Runnable java.util.concurrent.Callable java.security.PrivilegedAction java.util.Comparator java.io.FileFilter java.beans.PropertyChangeListener In addition, Java SE 8 adds a new package, java.util.function, which contains functional interfaces that are expected to be commonly used, such as: Predicate -- a boolean-valued property of an object Consumer -- an action to be performed on an object Function -- a function transforming a T to a R Supplier -- provide an instance of a T (such as a factory) UnaryOperator -- a function from T to T BinaryOperator -- a function from (T, T) to T In addition to these basic "shapes", there are also primitive specializations such as IntSupplier or LongBinaryOperator. (Rather than provide the full complement of primitive specializations, we provide only specializations for int, long, and double; the other primitive types can be accomodated through conversions.) Similarly, there are some specializations for multiple arities, such as BiFunction, which represents a function from (T,U) to R. 3. Lambda expressions The biggest pain point for anonymous classes is bulkiness. They have what we might call a "vertical problem": the ActionListener instance from section 1 uses five lines of source code to encapsulate a single aspect of behavior. Lambda expressions are anonymous methods, aimed at addressing the "vertical problem" by replacing the machinery of anonymous inner classes with a lighter-weight mechanism. Here are some examples of lambda expressions: (int x, int y) -> x + y () -> 42 (String s) -> { System.out.println(s); } The first expression takes two integer arguments, named x and y, and returns their sum. The second takes no arguments and returns the integer 42. The third takes a string and prints it to the console, returning nothing. The general syntax consists of an argument list, the arrow token ->, and a body. The body can either be a single expression, or a statement block. In the expression form, the body is simply evaluated and returned. In the block form, the body is evaluated like a method body -- a return statement returns control to the caller of the anonymous method; break and continue are illegal at the top level, but are of course permitted within loops; and if the body produces a result, every control path must return something or throw an exception. The syntax is optimized for the common case in which a lambda expression is quite small, as illustrated above. For example, the expression-body form eliminates the need for a return keyword, which could otherwise represent a substantial syntactic overhead relative to the size of the expression. It is also expected that lambda expressions will frequently appear in nested contexts, such as the argument to a method invocation or the result of another lambda expression. To minimize noise in these cases, unnecessary delimiters are avoided. However, for situations in which it is useful to set the entire expression apart, it can be surrounded with parentheses, just like any other expression. Here are some examples of lambda expressions appearing in statements: FileFilter java = (File f) -> f.getName().endsWith(".java"); String user = doPrivileged(() -> System.getProperty("user.name")); new Thread(() -> { connectToService(); sendNotification(); }).start(); 4. Target typing Note that the name of a functional interface is not part of the lambda expression syntax. So what kind of object does a lambda expression represent? Its type is inferred from the surrounding context. For example, the following lambda expression is an ActionListener: ActionListener l = (ActionEvent e) -> ui.dazzle(e.getModifiers()); An implication of this approach is that the same lambda expression can have different types in different contexts: Callable c = () -> "done"; PrivilegedAction a = () -> "done"; In the first case, the lambda expression () -> "done" represents an instance of Callable. In the second case, the same expression represents an instance of PrivilegedAction. The compiler is responsible for inferring the type of each lambda expression. It uses the type expected in the context in which the expression appears; this type is called the target type. A lambda expression can only appear in a context whose target type is a functional interface. Of course, no lambda expression will be compatible with every possible target type. The compiler checks that the types used by the lambda expression are consistent with the target type's method signature. That is, a lambda expression can be assigned to a target type T if all of the following conditions hold: T is a functional interface type The lambda expression has the same number of parameters as T's method, and those parameters' types are the same Each expression returned by the lambda body is compatible with T's method's return type Each exception thrown by the lambda body is allowed by T's method's throws clause Since a functional interface target type already "knows" what types the lambda expression's formal parameters should have, it is often unnecessary to repeat them. The use of target typing enables the lambda parameters' types to be inferred: Comparator c = (s1, s2) -> s1.compareToIgnoreCase(s2); Here, the compiler infers that the type of s1 and s2 is String. In addition, when there is just one parameter whose type is inferred (a very common case), the parentheses surrounding a single parameter name are optional: FileFilter java = f -> f.getName().endsWith(".java"); button.addActionListener(e -> ui.dazzle(e.getModifiers())); These enhancements further a desirable design goal: "Don't turn a vertical problem into a horizontal problem." We want the reader of the code to have to wade through as little syntax as possible before arriving at the "meat" of the lambda expression. Lambda expressions are not the first Java expressions to have context-dependent types: generic method invocations and "diamond" constructor invocations, for example, are similarly type-checked based on an assignment's target type. List ls = Collections.emptyList(); List li = Collections.emptyList(); Map m1 = new HashMap<>(); Map m2 = new HashMap<>(); 5. Contexts for target typing We stated earlier that lambda expressions can only appear in contexts that have target types. The following contexts have target types: Variable declarations Assignments Return statements Array initializers Method or constructor arguments Lambda expression bodies Conditional expressions (?:) Cast expressions In the first three cases, the target type is simply the type being assigned to or returned. Comparator c; c = (String s1, String s2) -> s1.compareToIgnoreCase(s2); public Runnable toDoLater() { return () -> { System.out.println("later"); }; } Array initializer contexts are like assignments, except that the "variable" is an array component and its type is derived from the array's type. filterFiles(new FileFilter[] { f -> f.exists(), f -> f.canRead(), f -> f.getName().startsWith("q") }); In the method argument case, things are more complicated: target type determination interacts with two other language features, overload resolution and type argument inference. Overload resolution involves finding the best method declaration for a particular method invocation. Since different declarations have different signatures, this can impact the target type of a lambda expression used as an argument. The compiler will use what it knows about the lambda expression to make this choice. If a lambda expression is explicitly typed (specifies the types of its parameters), the compiler will know not only the parameter types but also the type of all return expressions in its body. If the lambda is implicitly typed (inferred parameter types), overload resolution will ignore the lambda body and only use the number of lambda parameters. If the choice of a best method declaration is ambiguous, casts or explicit lambdas can provide additional type information for the compiler to disambiguate. If the return type targeted by a lambda expression depends on type argument inference, then the lambda body may provide information to the compiler to help infer the type arguments. List ps = ... String names = ps.stream().map(p -> p.getName()); Here, ps is a List, so ps.stream() is a Stream. The map() method is generic in R, where the parameter of map() is a Function, where T is the stream element type. (T is known to be Person at this point.) Once the overload is selected and the lambda's target type is known, we need to infer R; we do this by type-checking the lambda body, and discovering that its return type is String, and hence R is String, and therefore the map() expression has a type of Stream. Most of the time, the compiler just figures this all out, but if it gets stuck, we can provide additional type information via an explicit lambda (give the argument p an explicit type), casting the lambda to an explicit target type such as Function, or providing an explicit type witness for the generic parameter R (.map(p -> p.getName())). Lambda expressions themselves provide target types for their bodies, in this case by deriving that type from the outer target type. This makes it convenient to write functions that return other functions: Supplier c = () -> () -> { System.out.println("hi"); }; Similarly, conditional expressions can "pass down" a target type from the surrounding context: Callable c = flag ? (() -> 23) : (() -> 42); Finally, cast expressions provide a mechanism to explicitly provide a lambda expression's type if none can be conveniently inferred from context: // Illegal: Object o = () -> { System.out.println("hi"); }; Object o = (Runnable) () -> { System.out.println("hi"); }; Casts are also useful to help resolve ambiguity when a method declaration is overloaded with unrelated functional interface types. The expanded role of target typing in the compiler is not limited to lambda expressions: generic method invocations and "diamond" constructor invocations can also take advantage of target types wherever they are available. The following declarations are illegal in Java SE 7 but valid in Java SE 8: List ls = Collections.checkedList(new ArrayList<>(), String.class); Set si = flag ? Collections.singleton(23) : Collections.emptySet(); 6. Lexical scoping Determining the meaning of names (and this) in inner classes is significantly more difficult and error-prone than when classes are limited to the top level. Inherited members -- including methods of class Object -- can accidentally shadow outer declarations, and unqualified references to this always refer to the inner class itself. Lambda expressions are much simpler: they do not inherit any names from a supertype, nor do they introduce a new level of scoping. Instead, they are lexically scoped, meaning names in the body are interpreted just as they are in the enclosing environment (with the addition of new names for the lambda expression's formal parameters). As a natural extension, the this keyword and references to its members have the same meaning as they would immediately outside the lambda expression. To illustrate, the following program prints "Hello, world!" twice to the console: public class Hello { Runnable r1 = () -> { System.out.println(this); } Runnable r2 = () -> { System.out.println(toString()); } public String toString() { return "Hello, world!"; } public static void main(String... args) { new Hello().r1.run(); new Hello().r2.run(); } } The equivalent using anonymous inner classes would instead, perhaps to the programmer's surprise, print something like Hello$1@5b89a773 and Hello$2@537a7706. Consistent with the lexical-scoping approach, and following the pattern set by other local parameterized constructs like for loops and catch clauses, the parameters of a lambda expression must not shadow any local variables in the enclosing context. 7. Variable capture The compiler check for references to local variables of enclosing contexts in inner classes (captured variables) is quite restrictive in Java SE 7: an error occurs if the captured variable is not declared final. We relax this restriction -- for both lambda expressions and inner classes -- by also allowing the capture of effectively final local variables. Informally, a local variable is effectively final if its initial value is never changed -- in other words, declaring it final would not cause a compilation failure. Callable helloCallable(String name) { String hello = "Hello"; return () -> (hello + ", " + name); } References to this -- including implicit references through unqualified field references or method invocations -- are, essentially, references to a final local variable. Lambda bodies that contain such references capture the appropriate instance of this. In other cases, no reference to this is retained by the object. This has a beneficial implication for memory management: while inner class instances always hold a strong reference to their enclosing instance, lambdas that do not capture members from the enclosing instance do not hold a reference to it. This characteristic of inner class instances can often be a source of memory leaks. While we relax the syntactic restrictions on captured values, we still prohibit capture of mutable local variables. The reason is that idioms like this: int sum = 0; list.forEach(e -> { sum += e.size(); }); // ERROR are fundamentally serial; it is quite difficult to write lambda bodies like this that do not have race conditions. Unless we are willing to enforce -- preferably at compile time -- that such a function cannot escape its capturing thread, this feature may well cause more trouble than it solves. Lambda expressions close over values, not variables. Another reason to not support capture of mutable variables is that there's a better way to address accumulation problems without mutation, and instead treat this problem as a reduction. The java.util.stream package provides both general and specialized (such as sum, min, and max) reductions on collections and other data structures. For example, instead of using forEach and mutation, we could do a reduction which is safe both sequentially or in parallel: int sum = list.stream() .mapToInt(e -> e.size()) .sum(); The sum() method is provided for convenience, but is equivalent to the more general form of reduction: int sum = list.stream() .mapToInt(e -> e.size()) .reduce(0, (x,y) -> x+y); Reduction takes a base value (in case the input is empty) and an operator (here, addition), and computes the following expression: 0 + list[0] + list[1] + list[2] + ... Reduction can be done with other operations as well, such as minimum, maximum, product, etc, and if the operator is associative, is easily and safely parallelized. So, rather than supporting an idiom that is fundamentally sequential and prone to data races (mutable accumulators), we instead choose to provide library support to express accumulations in a more parallelizable and less error-prone way. 8. Method references Lambda expressions allow us to define an anonymous method and treat it as an instance of a functional interface. It is often desirable to do the same with an existing method. Method references are expressions which have the same treatment as lambda expressions (i.e., they require a target type and encode functional interface instances), but instead of providing a method body, they refer an existing method by name. For example, consider a Person class that can be sorted by name or by age. class Person { private final String name; private final int age; public int getAge() { return age; } public String getName() { return name; } ... } Person[] people = ... Comparator byName = Comparator.comparing(p -> p.getName()); Arrays.sort(people, byName); We can rewrite this to use a method reference to Person.getName() instead: Comparator byName = Comparator.comparing(Person::getName); Here, the expression Person::getName can be considered shorthand for a lambda expression which simply invokes the named method with its arguments, and returns the result. While the method reference may not (in this case) be any more syntactically compact, it is clearer -- the method that we want to call has a name, and so we can refer to it directly by name. Because the functional interface method's parameter types act as arguments in an implicit method invocation, the referenced method signature is allowed to manipulate the parameters -- via widening, boxing, grouping as a variable-arity array, etc. -- just like a method invocation. Consumer b1 = System::exit; // void exit(int status) Consumer b2 = Arrays::sort; // void sort(Object[] a) Consumer b3 = MyProgram::main; // void main(String... args) Runnable r = MyProgram::main; // void main(String... args) 9. Kinds of method references There are several different kinds of method references, each with slightly different syntax: A static method (ClassName::methName) An instance method of a particular object (instanceRef::methName) A super method of a particular object (super::methName) An instance method of an arbitrary object of a particular type (ClassName::methName) A class constructor reference (ClassName::new) An array constructor reference (TypeName[]::new) For a static method reference, the class to which the method belongs precedes the :: delimiter, such as in Integer::sum. For a reference to an instance method of a particular object, an expression evaluating to an object reference precedes the delimiter: Set knownNames = ... Predicate isKnown = knownNames::contains; Here, the implicit lambda expression would capture the String object referred to by knownNames, and the body would invoke Set.contains using that object as the receiver. The ability to reference the method of a specific object provides a convenient way to convert between different functional interface types: Callable c = ... PrivilegedAction a = c::call; For a reference to an instance method of an arbitrary object, the type to which the method belongs precedes the delimiter, and the invocation's receiver is the first parameter of the functional interface method: Function upperfier = String::toUpperCase; Here, the implicit lambda expression has one parameter, the string to be converted to upper case, which becomes the receiver of the invocation of the toUpperCase() method. If the class of the instance method is generic, its type parameters can be provided before the :: delimiter or, in most cases, inferred from the target type. Note that the syntax for a static method reference might also be interpreted as a reference to an instance method of a class. The compiler determines which is intended by attempting to identify an applicable method of each kind (noting that the instance method has one less argument). For all forms of method references, method type arguments are inferred as necessary, or they can be explicitly provided following the :: delimiter. Constructors can be referenced in much the same was as static methods by using the name new: SocketImplFactory factory = MySocketImpl::new; If a class has multiple constructors, the target type's method signature is used to select the best match in the same way that a constructor invocation is resolved. For inner classes, no syntax supports explicitly providing an enclosing instance parameter at the site of the constructor reference. If the class to instantiate is generic, type arguments can be provided after the class name, or they are inferred as for a "diamond" constructor invocation. There is a special syntactic form of constructor references for arrays, which treats arrays as if they had a constructor that accepts an int parameter. For example: IntFunction arrayMaker = int[]::new; int[] array = arrayMaker.apply(10); // creates an int[10] 10. Default and static interface methods Lambda expressions and method references add a lot of expressiveness to the Java language, but the key to really achieving our goal of making code-as-data patterns convenient and idiomatic is to complement these new features with libraries tailored to take advantage of them. Adding new functionality to existing libraries is somewhat difficult in Java SE 7. In particular, interfaces are essentially set in stone once they are published; unless one can update all possible implementations of an interface simultaneously, adding a new method to an interface can cause existing implementations to break. The purpose of default methods (previously referred to as virtual extension methods or defender methods) is to enable interfaces to be evolved in a compatible manner after their initial publication. To illustrate, the standard collections API obviously ought to provide new lambda-friendly operations. For example, the removeAll method could be generalized to remove any of a collection's elements for which an arbitrary property held, where the property was expressed as an instance of a functional interface Predicate. But where would this new method be defined? We can't add an abstract method to the Collection interface -- many existing implementations wouldn't know about the change. We could make it a static method in the Collections utility class, but that would relegate these new operations to a sort of second-class status. Default methods provide a more object-oriented way to add concrete behavior to an interface. These are a new kind of method: interface method can either be abstract or default. Default methods have an implementation that is inherited by classes that do not override it (see the next section for the details). Default methods in a functional interface don't count against its limit of one abstract method. For example, we could have (though did not) add a skip method to Iterator, as follows: interface Iterator { boolean hasNext(); E next(); void remove(); default void skip(int i) { for (; i > 0 && hasNext(); i--) next(); } } Given the above definition of Iterator, all classes that implement Iterator would inherit a skip method. From a client's perspective, skip is just another virtual method provided by the interface. Invoking skip on an instance of a subclass of Iterator that does not provide a body for skip has the effect of invoking the default implementation: calling hasNext and next up to a certain number of times. If a class wants to override skip with a better implementation -- by advancing a private cursor directly, for example, or incorporating an atomicity guarantee -- it is free to do so. When one interface extends another, it can add a default to an inherited abstract method, provide a new default for an inherited default method, or reabstract a default method by redeclaring the method as abstract. In addition to allowing code in interfaces in the form of default methods, Java SE 8 also introduces the ability to place static methods in interfaces as well. This allows helper methods that are specific to an interface to live with the interface, rather than in a side class (which is often named for the plural of the interface). For example, Comparator acquired static helper methods for making comparators, which takes a function that extracts a Comparable sort key and produces a Comparator: public static > Comparator comparing(Function keyExtractor) { return (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); } 11. Inheritance of default methods Default methods are inherited just like other methods; in most cases, the behavior is just as one would expect. However, when a class's or interface's supertypes provide multiple methods with the same signature, the inheritance rules attempt to resolve the conflict. Two basic principles drive these rules: Class method declarations are preferred to interface defaults. This is true whether the class method is concrete or abstract. (Hence the default keyword: default methods are a fallback if the class hierarchy doesn't say anything.) Methods that are already overridden by other candidates are ignored. This circumstance can arise when supertypes share a common ancestor. As an example of how the second rule comes into play, say the Collection and List interfaces provided different defaults for removeAll, and Queue inherits the default method from Collection; in the following implements clause, the List declaration would have priority over the Collection declaration inherited by Queue: class LinkedList implements List, Queue { ... } In the event that two independently-defined defaults conflict, or a default method conflicts with an abstract method, it is a compilation error. In this case, the programmer must explicitly override the supertype methods. Often, this amounts to picking the preferred default, and declaring a body that invokes the preferred default. An enhanced syntax for super supports the invocation of a particular superinterface's default implementation: interface Robot implements Artist, Gun { default void draw() { Artist.super.draw(); } } The name preceding super must refer to a direct superinterface that defines or inherits a default for the invoked method. This form of method invocation is not restricted to simple disambiguation -- it can be used just like any other invocation, in both classes and interfaces. In no case does the order in which interfaces are declared in an inherits or extends clause, or which interface was implemented "first" or "more recently", affect inheritance. 12. Putting it together The language and library features for Project Lambda were designed to work together. To illustrate, we'll consider the task of sorting a list of people by last name. Today we write: List people = ... Collections.sort(people, new Comparator() { public int compare(Person x, Person y) { return x.getLastName().compareTo(y.getLastName()); } }); This is a very verbose way to write "sort people by last name"! With lambda expressions, we can make this expression more concise: Collections.sort(people, (Person x, Person y) -> x.getLastName().compareTo(y.getLastName())); However, while more concise, it is not any more abstract; it still burdens the programmer with the need to do the actual comparison (which is even worse when the sort key is a primitive). Small changes to the libraries can help here, such the static comparing method added to Comparator: Collections.sort(people, Comparator.comparing((Person p) -> p.getLastName())); This can be shortened by allowing the compiler to infer the type of the lambda parameter, and importing the comparing method via a static import: Collections.sort(people, comparing(p -> p.getLastName())); The lambda in the above expression is simply a forwarder for the existing method getLastName. We can use method references to reuse the existing method in place of the lambda expression: Collections.sort(people, comparing(Person::getLastName)); Finally, the use of an ancillary method like Collections.sort is undesirable for many reasons: it is more verbose; it can't be specialized for each data structure that implements List; and it undermines the value of the List interface since users can't easily discover the static sort method when inspecting the documentation for List. Default methods provide a more object-oriented solution for this problem, where we've added a sort() method to List: people.sort(comparing(Person::getLastName)); Which also reads much more like to the problem statement in the first place: sort the people list by last name. If we add a default method reversed() to Comparator, which produces a Comparator that uses the same sort key but in reverse order, we can just as easily express a descending sort: people.sort(comparing(Person::getLastName).reversed()); 13. Summary Java SE 8 adds a relatively small number of new language features -- lambda expressions, method references, default and static methods in interfaces, and more widespread use of type inference. Taken together, though, they enable programmers to express their intent more clearly and concisely with less boilerplate, and enable the development of more powerful, parallel-friendly libraries. ================================================ FILE: 3-0-java-core/3-6-3-crazy-regex/README.MD ================================================ # Crazy Regex ### Pre-conditions ❗ You're supposed to know how to work regex and be able to build Patterns and Matchers ### Objectives * **build Patterns to extract** necessary parts from text ✅ * **manipulate** extracted text with **Matcher** object ✅ ### Regular expressions - sequence of characters that define a search pattern for text --- There 2 peace pf puzzle: * Literal characters - I want to match literally the character I specified (like 'a') * Meta characters - I want to match any character of this kind (more generic/abstract thing) Single char * \\d -> 0-9 * \\D -> negate of \\d * \\w -> A-Za-z0-9 * \\W -> negate of \\w * \\s -> whitespace, tab * \\S -> negate of \\s * . -> anything but newline * \\. -> literal dot Quantifiers - modify single characters how many of them you want match in a row * \* -> Occurs zero or more times * \+ -> 1 or more * ? -> zero or one * {min, max} -> some range * {n} -> precise quantity Position * ^ -> beginning * $ -> end * \\b -> word boundary --- Character class -> is the thing that appears in between []. For example [abc] -> match 'a' or 'b' or 'c'. Another example [-.] -> match dash or period. Here . is not meta character anymore and ^ are special characters inside [] * [0-5] -> match all numbers from 0 to 5. [^0-5] -> match anything that NOT 0-5 BUT it works like meta character only when it on first position, otherwise - its literal, [a^bc] - like this --- Capturing Groups - whenever u do regex search it matches whole result as a group 0. * \\d{3}-\\d{3}-\\d{4} -> 212-555-1234 = GROUP 0 Parentheses can capture a subgroup: \\d{3}-(\\d{3})-(\\d{4}) where 212-555-1234 = GROUP 0, 555 = GROUP 1, 1234 = GROUP 2 We can refer to this groups by $1 ($ when we want to replace) and \1 (within regex itself referring to capture group it's called back reference) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##
================================================ FILE: 3-0-java-core/3-6-3-crazy-regex/pom.xml ================================================ 3-0-java-core com.bobocode 1.0-SNAPSHOT 4.0.0 3-6-3-crazy-regex ================================================ FILE: 3-0-java-core/3-6-3-crazy-regex/src/main/java/com/bobocode/se/CrazyRegex.java ================================================ package com.bobocode.se; import com.bobocode.util.ExerciseNotCompletedException; import java.util.regex.Pattern; /** * {@link CrazyRegex} is an exercise class. Each method returns Pattern class which * should be created using regex expression. Every method that is not implemented yet * throws {@link ExerciseNotCompletedException} *

* TODO: remove exception and implement each method of this class using {@link Pattern} * * @author Andriy Paliychuk */ public class CrazyRegex { /** * A Pattern that that finds all words "Curiosity" in text * * @return a pattern that looks for the word "Curiosity" */ public Pattern findSpecificWord() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds first word in text * * @return a pattern that looks for the first word in text */ public Pattern findFirstWord() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds last word in text * * @return a pattern that looks for the last word in text */ public Pattern findLastWord() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds all numbers in text. When we have "555-555", "(555)555" and "30th" in text * our pattern must grab all that numbers: * "555" - four times, and one "30" * * @return a pattern that looks for numbers */ public Pattern findAllNumbers() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds all dates. For instance: "1971-11-23" * * @return a pattern that looks for dates */ public Pattern findDates() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds different variations of word "color". * We are looking for: "color", "colour", "colors", "colours" * * @return a pattern that looks for different variations of word "color" */ public Pattern findDifferentSpellingsOfColor() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds all zip codes in text. * Zip code is a 5-digit number without any characters or special symbols. * For example: 72300 * * @return a pattern that looks for zip codes */ public Pattern findZipCodes() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds different variations of word "link". * We are looking for: "lynk", "link", "l nk", "l(nk" * * @return a pattern that looks for different variations of word "link" */ public Pattern findDifferentSpellingsOfLink() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds phone numbers. * For example: "555-555-5555" * * @return a pattern that looks for phone numbers */ public Pattern findSimplePhoneNumber() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds numbers with following requirements: * - inside the number can be only digits from 0 to 5 * - length 3 * * @return a pattern that looks for numbers with length 3 and digits from 0 to 5 in the middle */ public Pattern findNumbersFromZeroToFiveWithLengthThree() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds all words in text that have length 5 * * @return a pattern that looks for the words that have length 5 */ public Pattern findAllWordsWithFiveLength() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds words and numbers with following constraints: * - not shorter than two symbols * - not longer than three symbols * * @return a pattern that looks for words and numbers that not shorter 2 and not longer 3 */ public Pattern findAllLettersAndDigitsWithLengthThree() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds all words that begin with capital letter * * @return a pattern that looks for the words that begin with capital letter */ public Pattern findAllWordsWhichBeginWithCapitalLetter() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds only the following abbreviation: * - AK, AL, AR, AZ, CA, CO, CT, PR, PA, PD * * @return a pattern that looks for the abbreviations above */ public Pattern findAbbreviation() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds all open braces * * @return a pattern that looks for all open braces */ public Pattern findAllOpenBraces() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds everything inside [] * * @return a pattern that looks for everything inside [] */ public Pattern findOnlyResources() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds all https links in note.txt * * @return a pattern that looks for all https links in note.txt */ public Pattern findOnlyLinksInNote() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds all http links in nasa.json * * @return a pattern that looks for all http links in nasa.json */ public Pattern findOnlyLinksInJson() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds all .com, .net and .edu emails * * @return a pattern that looks for all .com, .net and .edu emails */ public Pattern findAllEmails() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds the following examples of phone numbers: * - 555-555-5555 * - 555.555.5555 * - (555)555-5555 * * @return a pattern that looks for phone numbers patterns above */ public Pattern findAllPatternsForPhoneNumbers() { throw new ExerciseNotCompletedException(); } /** * A Pattern that finds only duplicates * * @return a pattern that looks for duplicates */ public Pattern findOnlyDuplicates() { throw new ExerciseNotCompletedException(); } /** * You have a text where all names recorded as first name, last name. * Create matcher and use method replaceAll to record that names as: * - last name first name * * @return String where all names recorded as last name first name */ public String replaceFirstAndLastNames(String names) { throw new ExerciseNotCompletedException(); } /** * You have a text with phone numbers. * Create matcher and use method replaceAll to replace last digits: * - 555-XXX-XXXX * * @return String where in all phone numbers last 7 digits replaced to X */ public String replaceLastSevenDigitsOfPhoneNumberToX(String phones) { throw new ExerciseNotCompletedException(); } /** * You have a text with resources and links to those resources: * - [Bobocode](https://www.bobocode.com) * Create matcher and use method replaceAll to get the following result: * - Bobocode * * @return String where all resources embraced in href */ public String insertLinksAndResourcesIntoHref(String links) { throw new ExerciseNotCompletedException(); } } ================================================ FILE: 3-0-java-core/3-6-3-crazy-regex/src/test/java/com/bobocode/se/CrazyRegexTest.java ================================================ package com.bobocode.se; import lombok.SneakyThrows; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; import static java.util.stream.Collectors.joining; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; /** * A test class for {@link CrazyRegex}. * * @author Andriy Paliychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CrazyRegexTest { private final CrazyRegex crazyRegex = new CrazyRegex(); private final String text; private final String json; public CrazyRegexTest() { this.text = readWholeFile("note.txt"); this.json = readWholeFile("nasa.json"); } @Test @Order(1) void findSpecificWord() { String result = regexChecker(crazyRegex.findSpecificWord(), json); assertThat(result).isEqualTo("\nCuriosity\nCuriosity\nCuriosity"); } @Test @Order(2) void findFirstWord() { String result = regexChecker(crazyRegex.findFirstWord(), text); assertThat(result).isEqualTo("\nThe"); } @Test @Order(3) void findLastWord() { String result = regexChecker(crazyRegex.findLastWord(), text); assertThat(result).isEqualTo("\nfish"); } @Test @Order(4) void findAllNumbers() { String result = regexChecker(crazyRegex.findAllNumbers(), text); assertThat(result).isEqualTo("\n01001\n03148\n02132\n412\n555\n1212\n412\n555" + "\n1234\n412\n555\n1234\n646\n555\n1234\n1"); } @Test @Order(5) void findDates() { String result = regexChecker(crazyRegex.findDates(), json); assertThat(result).isEqualTo("\n2015-05-30\n2012-08-06\n2011-11-26\n2015-05-30\n2012-08-06\n" + "2011-11-26\n2015-05-30\n2012-08-06\n2011-11-26"); } @Test @Order(6) void findDifferentSpellingsOfColor() { String result = regexChecker(crazyRegex.findDifferentSpellingsOfColor(), text); assertThat(result).isEqualTo("\ncolors\ncolours\ncolour"); } @Test @Order(7) void findZipCodes() { String result = regexChecker(crazyRegex.findZipCodes(), text); assertThat(result).isEqualTo("\n 01001 \n 03148 \n 02132 "); } @Test @Order(8) void findDifferentSpellingsOfLink() { String result = regexChecker(crazyRegex.findDifferentSpellingsOfLink(), text); assertThat(result).isEqualTo("\nlynk\nlink\nl nk\nl(nk"); } @Test @Order(9) void findSimplePhoneNumber() { String result = regexChecker(crazyRegex.findSimplePhoneNumber(), text); assertThat(result).isEqualTo("\n412-555-1234"); } @Test @Order(10) void findNumbersFromZeroToFiveWithLengthThree() { String result = regexChecker(crazyRegex.findNumbersFromZeroToFiveWithLengthThree(), text); assertThat(result).isEqualTo("\n010\n031\n021\n412\n555\n121\n412" + "\n555\n123\n412\n555\n123\n555\n123"); } @Test @Order(11) void findAllWordsWithFiveLength() { String result = regexChecker(crazyRegex.findAllWordsWithFiveLength(), json); assertThat(result).isEqualTo("\nFront\nrover\nFront\nrover\nrover"); } @Test @Order(12) void findAllLettersAndDigitsWithLengthThree() { String result = regexChecker(crazyRegex.findAllLettersAndDigitsWithLengthThree(), text); assertThat(result).isEqualTo("\nThe\nof\nthe\nand\nthe\nnot\nThe\nis\ndon\nyou\nnk\nnk\nThe\nCA\nAK\nPA\n412" + "\n555\ncom\n412\n555\n412\n555\n646\n555\nof\ncom\nnet\nor\nnyu\nedu\n1Z\naaa\nOf\nwww\ncom\ncom\nwww\ncom" + "\nis\nis\nam\nnot\nnot\nwhy\nwhy\nam\nok\ncat\ncat\ndog\ndog"); } @Test @Order(13) void findAllWordsWhichBeginWithCapitalLetter() { String result = regexChecker(crazyRegex.findAllWordsWhichBeginWithCapitalLetter(), json); assertThat(result).isEqualTo("\nFront\nHazard\nAvoidance\nCamera" + "\nCuriosity\nFront\nHazard\nAvoidance\nCamera\nCuriosity\nRear\nHazard\nAvoidance\nCamera\nCuriosity"); } @Test @Order(14) void findAbbreviation() { String result = regexChecker(crazyRegex.findAbbreviation(), text); assertThat(result).isEqualTo("\nCA\nAK\nPA"); } @Test @Order(15) void findAllOpenBraces() { String result = regexChecker(crazyRegex.findAllOpenBraces(), text); assertThat(result).isEqualTo("\n{{{\n{{\n{"); } @Test @Order(16) void findOnlyResources() { String result = regexChecker(crazyRegex.findOnlyResources(), text); assertThat(result).isEqualTo("\nGoogle\nStackOverflow\nYoutube"); } @Test @Order(17) void findOnlyLinksInNote() { String result = regexChecker(crazyRegex.findOnlyLinksInNote(), text); assertThat(result).isEqualTo("\nhttps://www.google.com\nhttps://stackoverflow.com\nhttps://www.youtube.com"); } @Test @Order(18) void findOnlyLinksInJson() { String result = regexChecker(crazyRegex.findOnlyLinksInJson(), json); assertThat(result).isEqualTo( "\nhttp://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/fcam/FLB_486265257EDR_F0481570FHAZ00323M_.JPG\n" + "http://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/fcam/FRB_486265257EDR_F0481570FHAZ00323M_.JPG\n" + "http://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/rcam/RLB_486265291EDR_F0481570RHAZ00323M_.JPG" ); } @Test @Order(19) void findAllEmails() { String result = regexChecker(crazyRegex.findAllEmails(), text); assertThat(result).isEqualTo("\njohnsmith@yahoo.com\nterek.koval@gmail.com\nterek@koval.net" + "\nterek.koval@nyu.edu"); } @Test @Order(20) void findAllPatternsForPhoneNumbers() { String result = regexChecker(crazyRegex.findAllPatternsForPhoneNumbers(), text); assertThat(result).isEqualTo("\n(412)555-1212\n412-555-1234\n646.555.1234"); } @Test @Order(21) void findOnlyDuplicates() { String result = regexChecker(crazyRegex.findOnlyDuplicates(), text); assertThat(result).isEqualTo("\nis is\ntext text\ndouble double\nI I\nnot not\nwhy why" + "\ncat cat\ndog\ndog\nfish fish"); } @Test @Order(22) void replaceFirstAndLastNames() { String names = "Tarasenko, Nazar ... Petrashyk, Petro ... Zlepko, Andrii"; String result = crazyRegex.replaceFirstAndLastNames(names); assertThat(result).isEqualTo("Nazar Tarasenko ... Petro Petrashyk ... Andrii Zlepko"); } @Test @Order(23) void replaceLastSevenDigitsOfPhoneNumberToX() { String phones = "(948)333-5656 1235-889-7897 111.747.6236"; String result = crazyRegex.replaceLastSevenDigitsOfPhoneNumberToX(phones); assertThat(result).isEqualTo("948-XXX-XXXX 1235-XXX-XXXX 111-XXX-XXXX"); } @Test @Order(24) void insertLinksAndResourcesIntoHref() { String links = "[Bobocode](https://www.bobocode.com)" + "\n[LinkedIn](https://www.linkedin.com)" + "\n[Netflix](https://www.netflix.com)"; String result = crazyRegex.insertLinksAndResourcesIntoHref(links); assertThat(result).isEqualTo( "Bobocode\n" + "LinkedIn\n" + "Netflix" ); } private String regexChecker(Pattern pattern, String str2WorkWith) { Matcher matcher = pattern.matcher(str2WorkWith); StringBuilder stringBuilder = new StringBuilder(); while (matcher.find()) { if (matcher.group().length() != 0) { stringBuilder.append("\n").append(matcher.group()); } } return stringBuilder.toString(); } @SneakyThrows private String readWholeFile(String fileName) { Path filePath = Paths.get(CrazyRegex.class.getClassLoader() .getResource(fileName) .toURI()); try (Stream fileLinesStream = Files.lines(filePath)) { return fileLinesStream.collect(joining("\n")); } } } ================================================ FILE: 3-0-java-core/3-6-3-crazy-regex/src/test/resources/nasa.json ================================================ {"photos":[ {"id":102693, "sol":1000, "camera":{"id":20,"name":"FHAZ","rover_id":5,"full_name":"Front Hazard Avoidance Camera"}, "img_src":"http://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/fcam/FLB_486265257EDR_F0481570FHAZ00323M_.JPG", "earth_date":"2015-05-30", "rover":{"id":5,"name":"Curiosity","landing_date":"2012-08-06","launch_date":"2011-11-26","status":"active"} }, {"id":102694, "sol":1000, "camera":{"id":20,"name":"FHAZ","rover_id":5,"full_name":"Front Hazard Avoidance Camera"}, "img_src":"http://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/fcam/FRB_486265257EDR_F0481570FHAZ00323M_.JPG", "earth_date":"2015-05-30", "rover":{"id":5,"name":"Curiosity","landing_date":"2012-08-06","launch_date":"2011-11-26","status":"active"} }, {"id":102850, "sol":1000, "camera":{"id":21,"name":"RHAZ","rover_id":5,"full_name":"Rear Hazard Avoidance Camera"}, "img_src":"http://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/rcam/RLB_486265291EDR_F0481570RHAZ00323M_.JPG", "earth_date":"2015-05-30", "rover":{"id":5,"name":"Curiosity","landing_date":"2012-08-06","launch_date":"2011-11-26","status":"active"} } ]} ================================================ FILE: 3-0-java-core/3-6-3-crazy-regex/src/test/resources/note.txt ================================================ The colors of the rainbow have many colours and the rainbow does not have a single colour The lynk is quite a link don't you think? l nk l(nk The Collin Richardson CA 01001 AK 03148 PA 02132 (412)555-1212 johnsmith@yahoo.com 412-555-1234 412 555-1234 646.555.1234 I know email addresses of fascinating people like terek.koval@gmail.com terek@koval.net or terek.koval@nyu.edu 1Z aaa **** *** {{{ {{ { Of [Google](https://www.google.com)[StackOverflow](https://stackoverflow.com)[Youtube](https://www.youtube.com) This is is some text text with double double words some where I I I am not not sure why why I am typing ok? cat cat dog dog fish fish ================================================ FILE: 3-0-java-core/3-6-4-random-field-comparator/README.MD ================================================ # Random Field Comparator #### Improve your reflection-related skills implementing a random field comparator 💪 ### Objectives * implement a logic of choosing a random field to use it for comparison of objects of provided type ✅ * implement a mechanism to check if field type is `Comparable` ✅ * implement a method `compare` that compares two objects by randomly-provided field ✅ * extend a method `compare` to manage null field values following condition when null value greater than a non-null value ✅ * implement method `getComparingFieldName` that retrieves the name of randomly-chosen comparing field✅ * implement method `toString` ✅ --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 3-0-java-core/3-6-4-random-field-comparator/pom.xml ================================================ 3-0-java-core com.bobocode 1.0-SNAPSHOT 4.0.0 3-6-4-random-field-comparator ================================================ FILE: 3-0-java-core/3-6-4-random-field-comparator/src/main/java/com/bobocode/se/RandomFieldComparator.java ================================================ package com.bobocode.se; import com.bobocode.util.ExerciseNotCompletedException; import java.util.Comparator; /** * A generic comparator that is comparing a random field of the given class. The field is either primitive or * {@link Comparable}. It is chosen during comparator instance creation and is used for all comparisons. *

* If no field is available to compare, the constructor throws {@link IllegalArgumentException} * * @param the type of the objects that may be compared by this comparator *

* TODO: to get the most out of your learning, visit our website *

* * @author Stanislav Zabramnyi */ public class RandomFieldComparator implements Comparator { public RandomFieldComparator(Class targetType) { throw new ExerciseNotCompletedException(); // todo: implement this constructor; } /** * Compares two objects of the class T by the value of the field that was randomly chosen. It allows null values * for the fields, and it treats null value greater than a non-null value. * * @param o1 * @param o2 * @return positive int in case of first parameter {@param o1} is greater than second one {@param o2}, * zero if objects are equals, * negative int in case of first parameter {@param o1} is less than second one {@param o2}. */ @Override public int compare(T o1, T o2) { throw new ExerciseNotCompletedException(); // todo: implement this method; } /** * Returns the name of the randomly-chosen comparing field. */ public String getComparingFieldName() { throw new ExerciseNotCompletedException(); // todo: implement this method; } /** * Returns a statement "Random field comparator of class '%s' is comparing '%s'" where the first param is the name * of the type T, and the second parameter is the comparing field name. * * @return a predefined statement */ @Override public String toString() { throw new ExerciseNotCompletedException(); // todo: implement this method; } } ================================================ FILE: 3-0-java-core/3-6-4-random-field-comparator/src/test/java/com/bobocode/se/RandomFieldComparatorTest.java ================================================ package com.bobocode.se; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.lang.reflect.Field; import java.util.Arrays; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import lombok.SneakyThrows; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; @TestMethodOrder(OrderAnnotation.class) class RandomFieldComparatorTest { private final RandomFieldComparator randomFieldComparator = new RandomFieldComparator<>(Account.class); @Test @Order(1) @DisplayName("Constructor throws an exception when parameter is null") void classDoesNotApplyNullInConstructor() { assertThrows(NullPointerException.class, () -> new RandomFieldComparator<>(null)); } @Test @Order(2) @SneakyThrows @DisplayName("Constructor throws an exception when the target type has no Comparable fields") void constructorThrowsExceptionIfNoComparableFieldsInProvidedType() { assertThrows(IllegalArgumentException.class, () -> new RandomFieldComparator<>(ClassWithNotComparableField.class)); } @Test @Order(3) @DisplayName("Method 'compare' throws an exception when any parameter is null") void compareWhenFirstParameterAreNull() { assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(null, new Account())); assertThrows(NullPointerException.class, () -> randomFieldComparator.compare(new Account(), null)); } @Test @Order(4) @DisplayName("Method 'compare' returns 0 when field values of both objects are null") void compareWhenBothFieldValuesIsNull() { setFieldToCompare("lastName", Account.class); int compareResult = randomFieldComparator.compare(new Account(), new Account()); assertThat(compareResult).isZero(); } @Test @Order(5) @DisplayName("Method compare returns positive int when the first field value is null") void compareWhenFieldValuesOfFirstObjectIsNull() { Account emptyAccount = new Account(); Account account = new Account("Sibma", "LoinKing", "simba-bimba@gmail.com", 14); setFieldToCompare("email", Account.class);//set field to compare explicitly as there are int field which has default value 0 int compareResult = randomFieldComparator.compare(emptyAccount, account); assertThat(compareResult).isPositive(); } @Test @Order(6) @DisplayName("Method compare returns negative int when the second field value is null") void compareWhenFieldValuesOfSecondObjectIsNull() { Account account = new Account("Mufasa", "LoinKing", "simba-bimba@gmail.com", 47); Account emptyAccount = new Account(); setFieldToCompare("firstName", Account.class); int compareResult = randomFieldComparator.compare(account, emptyAccount); assertThat(compareResult).isNegative(); } @Test @Order(7) @SneakyThrows @DisplayName("Method 'compare' returns positive int when the first value is greater") void compareWhenFieldValueOfFirstObjectIsGreater() { var fieldToCompareName = "firstName"; Account account1 = new Account(); Account account2 = new Account(); Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); fieldToCompareAccount.setAccessible(true); fieldToCompareAccount.set(account1, "Bob"); fieldToCompareAccount.set(account2, "Alice"); setFieldToCompare(fieldToCompareName, Account.class); int compareResult = randomFieldComparator.compare(account1, account2); assertThat(compareResult).isPositive(); } @Test @Order(8) @SneakyThrows @DisplayName("Method 'compare' returns negative int when the first value is smaller") void compareWhenFieldValueOfSecondObjectIsGreater() { var fieldToCompareName = "firstName"; Account account1 = new Account(); Account account2 = new Account(); Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); fieldToCompareAccount.setAccessible(true); fieldToCompareAccount.set(account1, "Alice"); fieldToCompareAccount.set(account2, "Bob"); setFieldToCompare(fieldToCompareName, Account.class); int compareResult = randomFieldComparator.compare(account1, account2); assertThat(compareResult).isNegative(); } @Test @Order(9) @SneakyThrows @DisplayName("Method 'compare' returns zero when the field values are equal") void compareWhenFieldValuesOfObjectsAreEqual() { var fieldToCompareName = "firstName"; Account account1 = new Account(); Account account2 = new Account(); Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); fieldToCompareAccount.setAccessible(true); fieldToCompareAccount.set(account1, "Carol"); fieldToCompareAccount.set(account2, "Carol"); setFieldToCompare(fieldToCompareName, Account.class); int compareResult = randomFieldComparator.compare(account1, account2); assertThat(compareResult).isZero(); } @Test @Order(10) @SneakyThrows @DisplayName("Method 'compare' returns positive int when the first primitive value is greater") void comparePrimitivesWhenFieldValueOfFirstObjectIsGreater() { var fieldToCompareName = "age"; Account account1 = new Account(); Account account2 = new Account(); Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); fieldToCompareAccount.setAccessible(true); fieldToCompareAccount.setInt(account1, 7); fieldToCompareAccount.setInt(account2, 3); setFieldToCompare(fieldToCompareName, Account.class); int compareResult = randomFieldComparator.compare(account1, account2); assertThat(compareResult).isPositive(); } @Test @Order(11) @SneakyThrows @DisplayName("Method 'compare' returns zero when the primitive field values are equal") void comparePrimitivesWhenFieldValuesOfObjectsAreEqual() { var fieldToCompareName = "age"; Account account1 = new Account(); Account account2 = new Account(); Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); fieldToCompareAccount.setAccessible(true); fieldToCompareAccount.setInt(account1, 15); fieldToCompareAccount.setInt(account2, 15); setFieldToCompare(fieldToCompareName, Account.class); int compareResult = randomFieldComparator.compare(account1, account2); assertThat(compareResult).isZero(); } @Test @Order(12) @SneakyThrows @DisplayName("Method 'compare' returns negative int when the first primitive value is smaller") void comparePrimitivesWhenFieldValueOfSecondObjectIsGreater() { var fieldToCompareName = "age"; Account account1 = new Account(); Account account2 = new Account(); Field fieldToCompareAccount = account1.getClass().getDeclaredField(fieldToCompareName); fieldToCompareAccount.setAccessible(true); fieldToCompareAccount.setInt(account1, 4); fieldToCompareAccount.setInt(account2, 8); setFieldToCompare(fieldToCompareName, Account.class); int compareResult = randomFieldComparator.compare(account1, account2); assertThat(compareResult).isNegative(); } @Test @Order(13) @SneakyThrows @DisplayName("Method 'getComparingFieldName' returns the name of randomly-chosen field to be compared") void getComparingFieldName() { var fieldToCompareName = "lastName"; setFieldToCompare(fieldToCompareName, Account.class); assertEquals(fieldToCompareName, randomFieldComparator.getComparingFieldName()); } @Test @Order(14) @SneakyThrows @DisplayName("Method toString is properly overridden") void toStringOverriding() { var expectedString = "Random field comparator of class 'Account' is comparing 'email'"; var fieldToCompareName = "email"; setFieldToCompare(fieldToCompareName, Account.class); assertEquals(expectedString, randomFieldComparator.toString()); } @SneakyThrows private void setFieldToCompare(String fieldName, Class classType) { Field fieldToCompare = Arrays.stream(randomFieldComparator.getClass().getDeclaredFields()) .filter(f -> f.getType().equals(Field.class)) .findAny() .orElseThrow(); fieldToCompare.setAccessible(true); fieldToCompare.set(randomFieldComparator, classType.getDeclaredField(fieldName)); } private static class EmptyClass { } @AllArgsConstructor private static class ClassWithNotComparableField { private Object field; } @NoArgsConstructor @AllArgsConstructor private static class Account { private String firstName; private String lastName; private String email; private int age; } } ================================================ FILE: 3-0-java-core/README.md ================================================ # Java Core Build strong core skills needed for enterprise Java development ================================================ FILE: 3-0-java-core/pom.xml ================================================ 4.0.0 com.bobocode java-fundamentals-exercises 1.0-SNAPSHOT 3-0-java-core pom 3-6-1-file-reader 3-6-2-file-stats 3-6-3-crazy-regex 3-6-4-random-field-comparator com.bobocode java-fundamentals-util 1.0-SNAPSHOT compile ================================================ FILE: 4-0-object-oriented-programming/4-3-1-flight-search/README.MD ================================================ # Flight search Improve your OOP skills and learn the power on polymorphism by inverting the compile-time dependency 💪 ### Task `FlighService` and package `service` represent a component of the system that holds a business logic. `FlightDao` and package `data` represent a component of the system that implements data access layer. Your job is to implement the *todo* section of those classes **following OOP design principles.** ### Pre-conditions ❗️ You're supposed to be familiar polymorphism and Java interfaces ### Objectives * **implement `FlightDao`** (data access object) methods using a simple `HashMap` ✅ * **implement service method** that allows to **register flight** using DAO ✅ * **implement service method** that allows **search for a flight** using DAO ✅ * **invert the dependency** between service and DAO by adding an interface ✅ ### Related materials ℹ️ * [SOLID](https://en.wikipedia.org/wiki/SOLID) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 4-0-object-oriented-programming/4-3-1-flight-search/pom.xml ================================================ 4-0-object-oriented-programming com.bobocode 1.0-SNAPSHOT 4.0.0 4-3-1-flight-search ================================================ FILE: 4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/data/FlightDao.java ================================================ package com.bobocode.oop.data; import com.bobocode.util.ExerciseNotCompletedException; import java.util.HashSet; import java.util.Set; /** * {@link FlightDao} represents a Data Access Object (DAO) for flights. The implementation is simplified, so it just * uses {@link HashSet} to store flight numbers. *

* todo: 1. Implement a method {@link FlightDao#register(String)} that store new flight number into the set * todo: 2. Implement a method {@link FlightDao#findAll()} that returns a set of all flight numbers */ public class FlightDao { private Set flights = new HashSet<>(); /** * Stores a new flight number * * @param flightNumber a flight number to store * @return {@code true} if a flight number was stored, {@code false} otherwise */ public boolean register(String flightNumber) { throw new ExerciseNotCompletedException();// todo: implement this method } /** * Returns all stored flight numbers * * @return a set of flight numbers */ public Set findAll() { throw new ExerciseNotCompletedException();// todo: implement this method } } ================================================ FILE: 4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/factory/FlightServiceFactory.java ================================================ package com.bobocode.oop.factory; import com.bobocode.oop.service.FlightService; import com.bobocode.util.ExerciseNotCompletedException; /** * {@link FlightServiceFactory} is used to create an instance of {@link FlightService} *

* todo: 1. Implement method {@link FlightServiceFactory#creteFlightService()} */ public class FlightServiceFactory { /** * Create a new instance of {@link FlightService} * * @return FlightService */ public FlightService creteFlightService() { throw new ExerciseNotCompletedException(); } } ================================================ FILE: 4-0-object-oriented-programming/4-3-1-flight-search/src/main/java/com/bobocode/oop/service/FlightService.java ================================================ package com.bobocode.oop.service; import com.bobocode.util.ExerciseNotCompletedException; import java.util.List; /** * {@link FlightService} provides an API that allows to manage flight numbers *

* todo: 1. Using {@link com.bobocode.oop.data.FlightDao} implement method {@link FlightService#registerFlight(String)} * todo: 2. Using {@link com.bobocode.oop.data.FlightDao} implement method {@link FlightService#searchFlights(String)} */ public class FlightService { /** * Adds a new flight number * * @param flightNumber a flight number to add * @return {@code true} if a flight number was added, {@code false} otherwise */ public boolean registerFlight(String flightNumber) { throw new ExerciseNotCompletedException(); } /** * Returns all flight numbers that contains a provided key. * * @param query a search query * @return a list of found flight numbers */ public List searchFlights(String query) { throw new ExerciseNotCompletedException(); } } ================================================ FILE: 4-0-object-oriented-programming/4-3-1-flight-search/src/test/java/com/bobocode/oop/FlightServiceTest.java ================================================ package com.bobocode.oop; import com.bobocode.oop.factory.FlightServiceFactory; import com.bobocode.oop.service.FlightService; import org.junit.jupiter.api.Test; import java.util.List; import static org.junit.jupiter.api.Assertions.*; public class FlightServiceTest { private FlightService flightService = new FlightServiceFactory().creteFlightService(); @Test public void testRegisterFlight() { boolean registered = flightService.registerFlight("PR344"); assertTrue(registered); } @Test public void testRegisterSameFlightTwice() { flightService.registerFlight("RB122"); boolean registeredSecondTime = flightService.registerFlight("RB122"); assertFalse(registeredSecondTime); } @Test public void testSearchExistingFlightByFullNumber() { flightService.registerFlight("OL234"); flightService.registerFlight("KM23234"); flightService.registerFlight("LTE114"); flightService.registerFlight("BRT14"); List foundFlights = flightService.searchFlights("LTE114"); assertEquals(1, foundFlights.size()); assertEquals("LTE114", foundFlights.get(0)); } @Test public void testSearchNonExistingFlight() { List foundFlights = flightService.searchFlights("XXX"); assertNotNull(foundFlights); } @Test public void testSearchFlights() { flightService.registerFlight("OR1214"); flightService.registerFlight("BTR14"); flightService.registerFlight("BMK198"); flightService.registerFlight("RLR198"); List foundFlights = flightService.searchFlights("R1"); assertTrue(foundFlights.contains("OR1214")); assertTrue(foundFlights.contains("BTR14")); assertTrue(foundFlights.contains("RLR198")); assertEquals(3, foundFlights.size()); } } ================================================ FILE: 4-0-object-oriented-programming/README.md ================================================ # Java Object-Oriented Programming Build strong object-oriented programming skills needed for enterprise Java development 💪 ================================================ FILE: 4-0-object-oriented-programming/pom.xml ================================================ 4.0.0 pom 4-3-1-flight-search com.bobocode java-fundamentals-exercises 1.0-SNAPSHOT 4-0-object-oriented-programming com.bobocode java-fundamentals-util 1.0-SNAPSHOT compile ================================================ FILE: 5-0-functional-programming/5-0-1-lambda-functions-map/README.MD ================================================ # Lambda Functions Map Start learning functional programming in Java by writing simple math functions using Lambdas 💪 ### Objectives * implement **abs** (absolute) function using lambda ✅ * implement **sgn** (signum) function using lambda ✅ * implement **increment** function using lambda ✅ * implement **decrement** function using lambda ✅ * implement **square** function using lambda ✅ * add all those functions to the function map ✅ --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 5-0-functional-programming/5-0-1-lambda-functions-map/pom.xml ================================================ 5-0-functional-programming com.bobocode 1.0-SNAPSHOT 4.0.0 5-0-1-lambda-functions-map ================================================ FILE: 5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/FunctionMap.java ================================================ package com.bobocode.fp; import com.bobocode.fp.exception.InvalidFunctionNameException; import java.util.HashMap; import java.util.Map; import java.util.function.Function; /** * {@link FunctionMap} is an API that allows you to store and retrieve functions by string name. {@link FunctionMap} * is stored in a {@link HashMap}, where the key is a function name, and the value is a {@link Function} instance. * * @author Taras Boychuk */ public class FunctionMap { private Map> functionMap; FunctionMap() { functionMap = new HashMap<>(); } public void addFunction(String name, Function function) { functionMap.put(name, function); } public Function getFunction(String name) { if (functionMap.containsKey(name)) { return functionMap.get(name); } else { throw new InvalidFunctionNameException(name); } } } ================================================ FILE: 5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/Functions.java ================================================ package com.bobocode.fp; /** * An util class that provides a factory method for creating an instance of a {@link FunctionMap} filled with a list * of functions. *

* TODO: implement a method and verify it by running {@link FunctionsTest} *

* TODO: if you find this exercise valuable and you want to get more like it, * please support us on Patreon * * @author Taras Boychuk */ public class Functions { private Functions() { } /** * A static factory method that creates an integer function map with basic functions: * - abs (absolute value) * - sgn (signum function) * - increment * - decrement * - square * * @return an instance of {@link FunctionMap} that contains all listed functions */ public static FunctionMap intFunctionMap() { FunctionMap intFunctionMap = new FunctionMap<>(); // todo: according to the javadoc add functions using lambda expression return intFunctionMap; } } ================================================ FILE: 5-0-functional-programming/5-0-1-lambda-functions-map/src/main/java/com/bobocode/fp/exception/InvalidFunctionNameException.java ================================================ package com.bobocode.fp.exception; public class InvalidFunctionNameException extends RuntimeException { public InvalidFunctionNameException(String functionName) { super("Function " + functionName + " doesn't exist."); } } ================================================ FILE: 5-0-functional-programming/5-0-1-lambda-functions-map/src/test/java/com/bobocode/fp/FunctionsTest.java ================================================ package com.bobocode.fp; import org.junit.jupiter.api.*; import java.util.function.Function; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class FunctionsTest { private FunctionMap integerFunctionMap; @BeforeEach public void init() { integerFunctionMap = Functions.intFunctionMap(); } @Test @Order(7) void squareFunction() { Function squareFunction = integerFunctionMap.getFunction("square"); int result = squareFunction.apply(5); assertThat(result).isEqualTo(25); } @Test @Order(1) void absFunction() { Function absFunction = integerFunctionMap.getFunction("abs"); int result = absFunction.apply(-192); assertThat(result).isEqualTo(192); } @Test @Order(5) void incrementFunction() { Function incrementFunction = integerFunctionMap.getFunction("increment"); int result = incrementFunction.apply(399); assertThat(result).isEqualTo(400); } @Test @Order(6) void destDecrementFunction() { Function decrementFunction = integerFunctionMap.getFunction("decrement"); int result = decrementFunction.apply(800); assertThat(result).isEqualTo(799); } @Test @Order(2) void signFunctionOnNegativeValue() { Function signFunction = integerFunctionMap.getFunction("sgn"); int result = signFunction.apply(-123); assertThat(result).isEqualTo(-1); } @Test @Order(3) void signFunctionOnPositiveValue() { Function signFunction = integerFunctionMap.getFunction("sgn"); int result = signFunction.apply(23); assertThat(result).isEqualTo(1); } @Test @Order(4) void signFunctionOnZero() { Function signFunction = integerFunctionMap.getFunction("sgn"); int result = signFunction.apply(0); assertThat(result).isEqualTo(0); } } ================================================ FILE: 5-0-functional-programming/5-0-2-stream-sum-of-squares/README.MD ================================================ # Stream sum of squares Learn functional programming by refactoring a piece of code using Stream API 💪 ### Objectives * **refactor imperative-style code** with for loop using Stream API ✅ * **create a `IntStream`** based on given the first, and the last values ✅ * **transform each element** of the stream into its square value ✅ * **calculate a sum** of all elements in the stream ✅ --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 5-0-functional-programming/5-0-2-stream-sum-of-squares/pom.xml ================================================ 5-0-functional-programming com.bobocode 1.0-SNAPSHOT 4.0.0 5-0-2-stream-sum-of-squares ================================================ FILE: 5-0-functional-programming/5-0-2-stream-sum-of-squares/src/main/java/com/bobocode/fp/SumOfSquares.java ================================================ package com.bobocode.fp; import com.bobocode.fp.exception.InvalidRangeException; /** * This class allow to calculate a sum of squares of integer number in a certain range. It was implemented using * OO approach. Your job is to refactor it using functional approach. E.g. avoid using mutable variables * * @author Taras Boychuk */ public class SumOfSquares { public static void main(String[] args) { System.out.println("Sum of squares from 5 to 10 is " + calculateSumOfSquaresInRange(5, 10)); } /** * This method calculates the sum of squares of integer in the range * * @param startInclusive first element in range * @param endInclusive last element in range * @return the sum of squares of each element in the range */ static int calculateSumOfSquaresInRange(int startInclusive, int endInclusive) { if (endInclusive < startInclusive) { throw new InvalidRangeException(); } // todo: refactor using functional approach – instead of using for loop, use IntStream.rangeClose() int sumOfSquares = 0; for (int i = startInclusive; i <= endInclusive; i++) { sumOfSquares += i * i; } return sumOfSquares; } } ================================================ FILE: 5-0-functional-programming/5-0-2-stream-sum-of-squares/src/main/java/com/bobocode/fp/exception/InvalidRangeException.java ================================================ package com.bobocode.fp.exception; public class InvalidRangeException extends RuntimeException { } ================================================ FILE: 5-0-functional-programming/5-0-2-stream-sum-of-squares/src/test/java/com/bobocode/fp/SumOfSquaresTest.java ================================================ package com.bobocode.fp; import com.bobocode.fp.exception.InvalidRangeException; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; /** * A test class for {@link SumOfSquares} * * @author Taras Boychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class SumOfSquaresTest { @Test @Order(1) void calculateSumOfSquaresOfZero() { int sumOfSquares = SumOfSquares.calculateSumOfSquaresInRange(0, 0); assertThat(sumOfSquares).isZero(); } @Test @Order(2) void calculateSumOfSquaresOfOne() { int sumOfSquares = SumOfSquares.calculateSumOfSquaresInRange(0, 1); assertThat(sumOfSquares).isEqualTo(1); } @Test @Order(3) void calculateSumOfSquares() { int sumOfSquares = SumOfSquares.calculateSumOfSquaresInRange(1, 5); // 1*1 + 2*2 + 3*3 + 4*4 + 5*5 = 55 assertThat(sumOfSquares).isEqualTo(55); } @Test @Order(4) void calculateSumOfSquaresOnNegative() { int sumOfSquares = SumOfSquares.calculateSumOfSquaresInRange(-4, -2); // -4*(-4) + -3*(-3) + -2*(-2) = 29 assertThat(sumOfSquares).isEqualTo(29); } @Test @Order(5) void calculateWithInvalidRange() { assertThatExceptionOfType(InvalidRangeException.class) .isThrownBy(() -> SumOfSquares.calculateSumOfSquaresInRange(4, 1)); } } ================================================ FILE: 5-0-functional-programming/5-1-1-crazy-lambdas/README.md ================================================ # Crazy Lambdas Build hard-core skills implementing crazy lambdas 💪 ### Tutorial Java is an OOP language, so it always works with classes and **doesn't support standalone functions**. In case you want to **pass some function as a method parameter**, or **store some code into a variable**, you should use a *Functional Interface* and a *Lambda expression*. * A *Functional Interface (FI)* represents a **function signature**. It contains only one abstract method. * A *Lambda expression* represents a **function body**. Is an anonymous function that implements the abstract method of the functional interface The purpose of the lambda and functional interfaces is to **make it easier to create function objects** and provide an **ability to use some functional programming technics in Java.** A typical example is interface `Comparator`: ```java accounts.sort(new Comparator() { @Override public int compare(Account o1, Account o2) { return o1.getFirstName().compareTo(o2.getFirstName()); } }); ``` It can be easily simplified using lambda expression: ```java accounts.sort((a1, a2) -> a1.getFirstName().compareTo(a2.getFirstName())); ``` In case you are calling some existing method inside the lambda, you can reference that method instead of actually calling it. This technique is called *Method Reference*. Combining it with usefull default method `comparing()` it can help you to simplify the code even more: ```java accounts.sort(comparing(Account::getFirstName)); ``` ### Best practices * use **lambdas instead of anonymous classes** * **avoid lambda parameter types**, unless it can improve code readability * **keep lambda expression small** (1 line is the best option) * **always use `@FunctionalInterface` annotation** for custom functional interfaces * **prefer standard predefined functional interfaces** (`java.util.function`) * create a **custom FI**, in case it has some **specific contract**, and you can **benefit from self-descriptive name** and **default methods** * **use special FI for primitives** (e.g. `IntToDoubleFunction` instead of `Function`) * **prefer method reference** in all cases where it helps to improve readability ### Related materials :information_source: * [State of lambda (JSR 335)](http://htmlpreview.github.io/?https://github.com/bobocode-projects/resources/blob/master/java8/lambda/sotl.html) * [Modern Java in Action](https://amzn.to/2KwUKW5) :green_book: * Passing code with behaviour parameterization - Chapter 2 * Lambda expressions - Chapter 3 * [Effective Java 3rd Edition](https://amzn.to/3mYA0U1) :blue_book: * Prefer Lambdas to anonymous classes – Item 42 * Prefer method reference to lambdas - Item 43 * Favor the use of standard functional interfaces - Item 44 ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/tNizKD1JbwM/0.jpg)](https://www.youtube.com/watch?v=tNizKD1JbwM) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##
================================================ FILE: 5-0-functional-programming/5-1-1-crazy-lambdas/pom.xml ================================================ 5-0-functional-programming com.bobocode 1.0-SNAPSHOT 4.0.0 5-1-1-crazy-lambdas ================================================ FILE: 5-0-functional-programming/5-1-1-crazy-lambdas/src/main/java/com/bobocode/fp/CrazyLambdas.java ================================================ package com.bobocode.fp; import com.bobocode.util.ExerciseNotCompletedException; import java.math.BigDecimal; import java.util.Comparator; import java.util.Map; import java.util.function.*; /** * {@link CrazyLambdas} is an exercise class. Each method returns a functional interface and it should be implemented * using either lambda or a method reference. Every method that is not implemented yet throws * {@link ExerciseNotCompletedException}. *

* TODO: remove exception and implement each method of this class using lambda or method reference *

* TODO: to get the most out of your learning, visit our website *

* * @author Taras Boychuk */ public class CrazyLambdas { /** * Returns {@link Supplier} that always supply "Hello" * * @return a string supplier */ public static Supplier helloSupplier() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Predicate} of string that checks if string is empty * * @return a string predicate */ public static Predicate isEmptyPredicate() { throw new ExerciseNotCompletedException(); } /** * Return a {@link Function} that accepts {@link String} and returns that string repeated n time, where n is passed * as function argument * * @return function that repeats Strings */ public static BiFunction stringMultiplier() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Function} that converts a {@link BigDecimal} number into a {@link String} that start with * a dollar sign and then gets a value * * @return function that converts adds dollar sign */ public static Function toDollarStringFunction() { throw new ExerciseNotCompletedException(); } /** * Receives two parameter that represent a range and returns a {@link Predicate} that verifies if string * length is in the specified range. E.g. min <= length < max * * @param min min length * @param max max length * @return a string predicate */ public static Predicate lengthInRangePredicate(int min, int max) { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Supplier} of random integers * * @return int supplier */ public static IntSupplier randomIntSupplier() { throw new ExerciseNotCompletedException(); } /** * Returns an {@link IntUnaryOperator} that receives an int as a bound parameter, and returns a random int * * @return int operation */ public static IntUnaryOperator boundedRandomIntSupplier() { throw new ExerciseNotCompletedException(); } /** * Returns {@link IntUnaryOperator} that calculates an integer square * * @return square operation */ public static IntUnaryOperator intSquareOperation() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link LongBinaryOperator} sum operation. * * @return binary sum operation */ public static LongBinaryOperator longSumOperation() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link ToIntFunction} that converts string to integer. * * @return string to int converter */ public static ToIntFunction stringToIntConverter() { throw new ExerciseNotCompletedException(); } /** * Receives int parameter n, and returns a {@link Supplier} that supplies {@link IntUnaryOperator} * that is a function f(x) = n * x * * @param n a multiplier * @return a function supplier */ public static Supplier nMultiplyFunctionSupplier(int n) { throw new ExerciseNotCompletedException(); } /** * Returns a {@link UnaryOperator} that accepts str to str function and returns the same function composed with trim * * @return function that composes functions with trim() function */ public static UnaryOperator> composeWithTrimFunction() { throw new ExerciseNotCompletedException(); } /** * Receives a {@link Runnable} parameter, and returns a {@link Supplier}. The thread will be started only * when you call supplier method {@link Supplier#get()} * * @param runnable the code you want to tun in new thread * @return a thread supplier */ public static Supplier runningThreadSupplier(Runnable runnable) { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Consumer} that accepts {@link Runnable} as a parameter and runs in a new thread. * * @return a runnable consumer */ public static Consumer newThreadRunnableConsumer() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Function} that accepts an instance of {@link Runnable} and returns a {@link Supplier} of a * started {@link Thread} that is created from a given {@link Runnable} * * @return a function that transforms runnable into a thread supplier */ public static Function> runnableToThreadSupplierFunction() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link BiFunction} that has two parameters. First is {@link IntUnaryOperator} which is some integer function. * Second is {@link IntPredicate} which is some integer condition. And the third is {@link IntUnaryOperator} which is * a new composed function that uses provided predicate (second parameter of binary function) to verify its input * parameter. If predicate returns {@code true} it applies a provided integer function * (first parameter of binary function) and returns a result value, otherwise it returns an element itself. * * @return a binary function that receiver predicate and function and compose them to create a new function */ public static BiFunction functionToConditionalFunction() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link BiFunction} which first parameter is a {@link Map} where key is a function name, and value is some * {@link IntUnaryOperator}, and second parameter is a {@link String} which is a function name. If the map contains a * function by a given name then it is returned by high order function otherwise an identity() is returned. * * @return a high-order function that fetches a function from a function map by a given name or returns identity() */ public static BiFunction, String, IntUnaryOperator> functionLoader() { throw new ExerciseNotCompletedException(); } /** * Returns a comparator of type T that is comparing values extracted using the provided mapper function. *

* E.g. imagine you need to compare accounts by their balance values. *

{@code
     * Comparator balanceComparator = comparing(Account::getBalance);
     * }
*

* PLEASE NOTE, that @{@link Comparator} is a functional interface, and you should manually write a lambda expression * to implement it. * * @param mapper a mapper function that allows to map an object to a comparable value * @return a comparator instance */ public static > Comparator comparing(Function mapper) { throw new ExerciseNotCompletedException(); } /** * Returns a comparator of type T that uses a provided comparator to compare objects, and only if they are equal * it's comparing values extracted using the provided mapper function. *

* E.g. suppose you want to compare accounts by balance, but in case two people have the same balance you want to * compare their first names: *

{@code
     * Comparator accountComparator = thenComparing(balanceComparator, Account::getFirstName);
     * }
*

* * @param comparator an initial comparator * @param mapper a mapper function that is used to extract values when initial comparator returns zero * @return a comparator instance */ public static > Comparator thenComparing( Comparator comparator, Function mapper) { throw new ExerciseNotCompletedException(); } /** * Returns {@link Supplier} of {@link Supplier} of {@link Supplier} of {@link String} "WELL DONE!". * * @return a supplier instance */ public static Supplier>> trickyWellDoneSupplier() { throw new ExerciseNotCompletedException(); } } ================================================ FILE: 5-0-functional-programming/5-1-1-crazy-lambdas/src/test/java/com/bobocode/fp/CrazyLambdasTest.java ================================================ package com.bobocode.fp; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import java.math.BigDecimal; import java.util.*; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.*; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; /** * A test class for {@link CrazyLambdas}. * * @author Taras Boychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CrazyLambdasTest { @Test @Order(1) void helloSupplier() { Supplier helloSupplier = CrazyLambdas.helloSupplier(); assertEquals("Hello", helloSupplier.get()); } @Test @Order(2) void isEmptyPredicate() { Predicate isEmptyPredicate = CrazyLambdas.isEmptyPredicate(); boolean nonEmptyStringResult = isEmptyPredicate.test("fasdfa"); boolean emptyStringResult = isEmptyPredicate.test(""); assertFalse(nonEmptyStringResult); assertTrue(emptyStringResult); } @Test @Order(3) void stringMultiplier() { BiFunction stringMultiplier = CrazyLambdas.stringMultiplier(); String threeTimesHi = stringMultiplier.apply("Hi", 3); String twoTimesHello = stringMultiplier.apply("Hello", 2); assertEquals("HiHiHi", threeTimesHi); assertEquals("HelloHello", twoTimesHello); } @Test @Order(4) void toDollarStringFunction() { Function toDollarStringFunction = CrazyLambdas.toDollarStringFunction(); String tenDollarStr = toDollarStringFunction.apply(BigDecimal.TEN.setScale(2)); assertEquals("$10.00", tenDollarStr); } @Test @Order(5) void lengthInRangePredicate() { Predicate lengthInRangePredicate = CrazyLambdas.lengthInRangePredicate(4, 10); boolean twoLetterStringResult = lengthInRangePredicate.test("Hi"); boolean fourLetterStringResult = lengthInRangePredicate.test("Hola"); boolean fiveLetterStringResult = lengthInRangePredicate.test("Amigo"); boolean eightLetterStringResult = lengthInRangePredicate.test("Lalaland"); boolean thirteenLetterStringResult = lengthInRangePredicate.test("Lambda rocks!"); assertFalse(twoLetterStringResult); assertTrue(fourLetterStringResult); assertTrue(fiveLetterStringResult); assertTrue(eightLetterStringResult); assertFalse(thirteenLetterStringResult); } @Test @Order(6) void randomIntSupplier() { IntSupplier randomIntSupplier = CrazyLambdas.randomIntSupplier(); int firstValue = randomIntSupplier.getAsInt(); int secondValue = randomIntSupplier.getAsInt(); assertNotEquals(firstValue, secondValue); } @Test @Order(7) void boundedRandomIntSupplier() { IntUnaryOperator boundedRandomIntSupplier = CrazyLambdas.boundedRandomIntSupplier(); int randomIntLessThan10 = boundedRandomIntSupplier.applyAsInt(10); int randomIntLessThan100 = boundedRandomIntSupplier.applyAsInt(100); int randomIntLessThan1000 = boundedRandomIntSupplier.applyAsInt(1000); int randomIntLessThan10000 = boundedRandomIntSupplier.applyAsInt(1000); assertTrue(randomIntLessThan10 < 10); assertTrue(randomIntLessThan100 < 100); assertTrue(randomIntLessThan1000 < 1000); assertTrue(randomIntLessThan10000 < 10000); } @Test @Order(8) void intSquareOperation() { IntUnaryOperator squareOperation = CrazyLambdas.intSquareOperation(); int squareOfFour = squareOperation.applyAsInt(4); int squareOfZero = squareOperation.applyAsInt(0); assertEquals(16, squareOfFour); assertEquals(0, squareOfZero); } @Test @Order(9) void longSumOperation() { LongBinaryOperator sumOperation = CrazyLambdas.longSumOperation(); long sumOfSevenAndEight = sumOperation.applyAsLong(7, 8); long sumOfTenAndZero = sumOperation.applyAsLong(10, 0); long sumOfFiveAndMinusTen = sumOperation.applyAsLong(5, -10); assertEquals(15, sumOfSevenAndEight); assertEquals(10, sumOfTenAndZero); assertEquals(-5, sumOfFiveAndMinusTen); } @Test @Order(10) void stringToIntConverter() { ToIntFunction stringToIntConverter = CrazyLambdas.stringToIntConverter(); int num = stringToIntConverter.applyAsInt("234"); int negativeNum = stringToIntConverter.applyAsInt("-122"); assertEquals(234, num); assertEquals(-122, negativeNum); } @Test @Order(11) void nMultiplyFunctionSupplier() { Supplier fiveMultiplyFunctionSupplier = CrazyLambdas.nMultiplyFunctionSupplier(5); IntUnaryOperator multiplyByFiveOperation = fiveMultiplyFunctionSupplier.get(); int result = multiplyByFiveOperation.applyAsInt(11); // 11 * 5 = 55 assertEquals(55, result); } @Test @Order(12) void composeWithTrimFunction() { UnaryOperator> composeWithTrimFunction = CrazyLambdas.composeWithTrimFunction(); Function toLowerWithTrim = composeWithTrimFunction.apply(String::toLowerCase); Function threeTimesRepeatWithTrim = composeWithTrimFunction.apply(s -> s.repeat(3)); String hey = toLowerWithTrim.apply(" Hey "); String threeTimesHi = threeTimesRepeatWithTrim.apply(" Hi "); assertEquals("hey", hey); assertEquals("HiHiHi", threeTimesHi); } @Test @Order(13) void runningThreadSupplier() throws InterruptedException { Queue concurrentLinkedQueue = new ConcurrentLinkedQueue<>(); Supplier runningThreadSupplier = CrazyLambdas.runningThreadSupplier(() -> concurrentLinkedQueue.add(25)); // supplier does not create and start a thread before you call get() assertEquals(0, concurrentLinkedQueue.size()); Thread runningThread = runningThreadSupplier.get(); // new thread has been started runningThread.join(); assertEquals(1, concurrentLinkedQueue.size()); assertEquals(25, concurrentLinkedQueue.element().intValue()); } @Test @Order(14) void newThreadRunnableConsumer() throws InterruptedException { Consumer newThreadRunnableConsumer = CrazyLambdas.newThreadRunnableConsumer(); Queue concurrentLinkedQueue = new ConcurrentLinkedQueue<>(); newThreadRunnableConsumer.accept(() -> concurrentLinkedQueue.add(50)); Thread.sleep(500); // don't do that in real code assertEquals(1, concurrentLinkedQueue.size()); assertEquals(50, concurrentLinkedQueue.element().intValue()); } @Test @Order(15) void runnableToThreadSupplierFunction() throws InterruptedException { Function> runnableSupplierFunction = CrazyLambdas.runnableToThreadSupplierFunction(); Queue concurrentLinkedQueue = new ConcurrentLinkedQueue<>(); Supplier threadSupplier = runnableSupplierFunction.apply(() -> concurrentLinkedQueue.add(200)); assertEquals(0, concurrentLinkedQueue.size()); // supplier does not create and start a thread before you call get() Thread thread = threadSupplier.get();// new thread has been started thread.join(); assertEquals(1, concurrentLinkedQueue.size()); assertEquals(200, concurrentLinkedQueue.element().intValue()); } @Test @Order(16) void functionToConditionalFunction() { BiFunction intFunctionToConditionalIntFunction = CrazyLambdas.functionToConditionalFunction(); IntUnaryOperator abs = intFunctionToConditionalIntFunction.apply(a -> -a, a -> a < 0); assertEquals(5, abs.applyAsInt(-5)); assertEquals(0, abs.applyAsInt(0)); assertEquals(5, abs.applyAsInt(5)); } @Test @Order(17) void functionLoader() { BiFunction, String, IntUnaryOperator> functionLoader = CrazyLambdas.functionLoader(); Map functionMap = new HashMap<>(); functionMap.put("increment", x -> x + 1); functionMap.put("square", x -> x * x); IntUnaryOperator incrementFunction = functionLoader.apply(functionMap, "increment"); IntUnaryOperator squareFunction = functionLoader.apply(functionMap, "square"); IntUnaryOperator identityFunction = functionLoader.apply(functionMap, "none"); assertEquals(5, incrementFunction.applyAsInt(4)); assertEquals(9, squareFunction.applyAsInt(3)); assertEquals(10, identityFunction.applyAsInt(10)); } @Test @Order(18) void comparing() { var strLengthComparator = CrazyLambdas.comparing(String::length); var stringList = new ArrayList<>(List.of("Me", "I", "All of us", "They", "She")); stringList.sort(strLengthComparator); assertThat(stringList).isEqualTo(List.of("I", "Me", "She", "They", "All of us")); } @Test @Order(19) void thenComparing() { var strLengthComparator = Comparator.comparing(String::length); var lengthThenNaturalComparator = CrazyLambdas.thenComparing(strLengthComparator, s -> s); var stringList = new ArrayList<>(List.of("Me", "I", "All of us", "They", "She", "He")); stringList.sort(lengthThenNaturalComparator); assertThat(stringList).isEqualTo(List.of("I", "He", "Me", "She", "They", "All of us")); } @Test @Order(20) void trickyWellDoneSupplier() { Supplier>> wellDoneSupplier = CrazyLambdas.trickyWellDoneSupplier(); String wellDoneStr = wellDoneSupplier.get().get().get(); assertEquals("WELL DONE!", wellDoneStr); } } ================================================ FILE: 5-0-functional-programming/5-2-1-crazy-streams/README.MD ================================================ # Crazy Streams Take your Stream API skills to the next level 💪 ### Task `CrazyStreams` provides an API with various statistic methods for a list of accounts. Your job is to implement each method of that class using **Stream API**. To verify your implementation, run `CrazyStreamsTest.java` ### Related materials ℹ️ * [Modern Java in Action (chapters 4 - 6)](https://www.amazon.com/dp/1617293563/ref=cm_sw_r_tw_dp_XN3TPAASB945V8XQPW5F) 📕 ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/o1H6kMlCQ74/0.jpg)](https://www.youtube.com/watch?v=o1H6kMlCQ74) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 5-0-functional-programming/5-2-1-crazy-streams/pom.xml ================================================ 5-0-functional-programming com.bobocode 1.0-SNAPSHOT 4.0.0 5-2-1-crazy-streams ================================================ FILE: 5-0-functional-programming/5-2-1-crazy-streams/src/main/java/com/bobocode/fp/CrazyStreams.java ================================================ package com.bobocode.fp; import com.bobocode.model.Account; import com.bobocode.util.ExerciseNotCompletedException; import lombok.AllArgsConstructor; import java.math.BigDecimal; import java.time.Month; import java.util.*; /** * {@link CrazyStreams} is an exercise class. Each method represent some operation with a collection of accounts that * should be implemented using Stream API. Every method that is not implemented yet throws * {@link ExerciseNotCompletedException}. *

* TODO: remove exception throwing and implement each method using Stream API *

* TODO: to get the most out of your learning, visit our website *

* * @author Taras Boychuk */ @AllArgsConstructor public class CrazyStreams { private Collection accounts; /** * Returns {@link Optional} that contains an {@link Account} with the max value of balance * * @return account with max balance wrapped with optional */ public Optional findRichestPerson() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link List} of {@link Account} that have a birthday month equal to provided. * * @param birthdayMonth a month of birth * @return a list of accounts */ public List findAccountsByBirthdayMonth(Month birthdayMonth) { throw new ExerciseNotCompletedException(); } /** * Returns a map that separates all accounts into two lists - male and female. Map has two keys {@code true} indicates * male list, and {@code false} indicates female list. * * @return a map where key is true or false, and value is list of male, and female accounts */ public Map> partitionMaleAccounts() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Map} that stores accounts grouped by its email domain. A map key is {@link String} which is an * email domain like "gmail.com". And the value is a {@link List} of {@link Account} objects with a specific email domain. * * @return a map where key is an email domain and value is a list of all account with such email */ public Map> groupAccountsByEmailDomain() { throw new ExerciseNotCompletedException(); } /** * Returns a number of letters in all first and last names. * * @return total number of letters of first and last names of all accounts */ public int getNumOfLettersInFirstAndLastNames() { throw new ExerciseNotCompletedException(); } /** * Returns a total balance of all accounts. * * @return total balance of all accounts */ public BigDecimal calculateTotalBalance() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link List} of {@link Account} objects sorted by first and last names. * * @return list of accounts sorted by first and last names */ public List sortByFirstAndLastNames() { throw new ExerciseNotCompletedException(); } /** * Checks if there is at least one account with provided email domain. * * @param emailDomain * @return true if there is an account that has an email with provided domain */ public boolean containsAccountWithEmailDomain(String emailDomain) { throw new ExerciseNotCompletedException(); } /** * Returns account balance by its email. Throws {@link EntityNotFoundException} with message * "Cannot find Account by email={email}" if account is not found. * * @param email account email * @return account balance */ public BigDecimal getBalanceByEmail(String email) { throw new ExerciseNotCompletedException(); } /** * Collects all existing accounts into a {@link Map} where a key is account id, and the value is {@link Account} instance * * @return map of accounts by its ids */ public Map collectAccountsById() { throw new ExerciseNotCompletedException(); } /** * Filters accounts by the year when an account was created. Collects account balances by its emails into a {@link Map}. * The key is {@link Account#email} and the value is {@link Account#balance} * * @param year the year of account creation * @return map of account by its ids the were created in a particular year */ public Map collectBalancesByEmailForAccountsCreatedOn(int year) { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Map} where key is {@link Account#lastName} and values is a {@link Set} that contains first names * of all accounts with a specific last name. * * @return a map where key is a last name and value is a set of first names */ public Map> groupFirstNamesByLastNames() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Map} where key is a birthday month, and value is a {@link String} that stores comma and space * -separated first names (e.g. "Polly, Dylan, Clark"), of all accounts that have the same birthday month. * * @return a map where a key is a birthday month and value is comma-separated first names */ public Map groupCommaSeparatedFirstNamesByBirthdayMonth() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Map} where key is a {@link Month} of {@link Account#creationDate}, and value is total balance * of all accounts that have the same value creation month. * * @return a map where key is a creation month and value is total balance of all accounts created in that month */ public Map groupTotalBalanceByCreationMonth() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Map} where key is a letter {@link Character}, and value is a number of its occurrences in * {@link Account#firstName}. * * @return a map where key is a letter and value is its count in all first names */ public Map getCharacterFrequencyInFirstNames() { throw new ExerciseNotCompletedException(); } /** * Returns a {@link Map} where key is a letter {@link Character}, and value is a number of its occurrences ignoring * case, in all {@link Account#firstName} and {@link Account#lastName} that are equal or longer than nameLengthBound. * Inside the map, all letters should be stored in lower case. * * @return a map where key is a letter and value is its count ignoring case in all first and last names */ public Map getCharacterFrequencyIgnoreCaseInFirstAndLastNames(int nameLengthBound) { throw new ExerciseNotCompletedException(); } } ================================================ FILE: 5-0-functional-programming/5-2-1-crazy-streams/src/main/java/com/bobocode/fp/exception/EntityNotFoundException.java ================================================ package com.bobocode.fp.exception; public class EntityNotFoundException extends RuntimeException { public EntityNotFoundException(String message) { super(message); } } ================================================ FILE: 5-0-functional-programming/5-2-1-crazy-streams/src/test/java/com/bobocode/fp/CrazyStreamsTest.java ================================================ package com.bobocode.fp; import com.bobocode.fp.exception.EntityNotFoundException; import com.bobocode.model.Account; import com.bobocode.model.Sex; import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import java.math.BigDecimal; import java.time.LocalDate; import java.time.Month; import java.util.*; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; /** * A test class for {@link CrazyStreams}. *

* Please note that the helper methods of this test class do not use Stream API intentionally. You should try to find * a stream-based solutions for {@link CrazyStreams} by yourself. * * @author Taras Boychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class CrazyStreamsTest { private CrazyStreams streams; private static List accounts = Arrays.asList( new Account(1L, "Justin", "Butler", "justin.butler@gmail.com", LocalDate.parse("2003-04-17"), Sex.MALE, LocalDate.parse("2016-06-13"), BigDecimal.valueOf(172966)), new Account(2L, "Olivia", "Cardenas", "cardenas@mail.com", LocalDate.parse("1930-01-19"), Sex.FEMALE, LocalDate.parse("2014-06-21"), BigDecimal.valueOf(38029)), new Account(3L, "Nolan", "Donovan", "nolandonovan@gmail.com", LocalDate.parse("1925-04-19"), Sex.MALE, LocalDate.parse("2011-03-10"), BigDecimal.valueOf(13889)), new Account(4L, "Lucas", "Lynn", "lucas.lynn@yahoo.com", LocalDate.parse("1987-05-25"), Sex.MALE, LocalDate.parse("2009-03-05"), BigDecimal.valueOf(16980)) ); @BeforeEach void setUp() { streams = new CrazyStreams(accounts); } @Test @Order(1) void findRichestPerson() { Optional expectedPerson = Optional.of(accounts.get(0)); Optional actualRichestPerson = streams.findRichestPerson(); assertEquals(expectedPerson, actualRichestPerson); } @Test @Order(2) void findAccountsByBirthdayMonth() { List expectedList = getExpectedList(); List aprilAccounts = streams.findAccountsByBirthdayMonth(Month.APRIL); assertEquals(expectedList, aprilAccounts); } @Test @Order(3) void separateMaleAccounts() { Map> expectedAccountMap = getExpectedMaleMap(); Map> maleToAccountsMap = streams.partitionMaleAccounts(); assertEquals(expectedAccountMap, maleToAccountsMap); } private Map> getExpectedMaleMap() { Map> expectedMap = new HashMap<>(2); expectedMap.put(Boolean.TRUE, Arrays.asList(accounts.get(0), accounts.get(2), accounts.get(3))); expectedMap.put(Boolean.FALSE, Arrays.asList(accounts.get(1))); return expectedMap; } private List getExpectedList() { return Arrays.asList(accounts.get(0), accounts.get(2)); } @Test @Order(4) void groupAccountsByEmailDomain() { Map> expectedEmailMap = getExpectedEmailMap(); Map> emailDomainToAccountsMap = streams.groupAccountsByEmailDomain(); assertEquals(expectedEmailMap, emailDomainToAccountsMap); } private Map> getExpectedEmailMap() { Map> expectedEmailMap = new HashMap<>(); expectedEmailMap.put("gmail.com", Arrays.asList(accounts.get(0), accounts.get(2))); expectedEmailMap.put("mail.com", Arrays.asList(accounts.get(1))); expectedEmailMap.put("yahoo.com", Arrays.asList(accounts.get(3))); return expectedEmailMap; } @Test @Order(5) void getNumOfLettersInFirstAndLastNames() { int numOfLettersInFirstAndLastNames = streams.getNumOfLettersInFirstAndLastNames(); assertEquals(47, numOfLettersInFirstAndLastNames); } @Test @Order(6) void calculateTotalBalance() { BigDecimal totalBalance = streams.calculateTotalBalance(); assertEquals(BigDecimal.valueOf(241864), totalBalance); } @Test @Order(7) void sortByFirstAndLastNames() { List sortedList = streams.sortByFirstAndLastNames(); assertEquals(1L, sortedList.get(0).getId().longValue()); assertEquals(4L, sortedList.get(1).getId().longValue()); assertEquals(3L, sortedList.get(2).getId().longValue()); assertEquals(2L, sortedList.get(3).getId().longValue()); } @Test @Order(8) void containsAccountWithEmailDomain() { assertTrue(streams.containsAccountWithEmailDomain("gmail.com")); assertTrue(streams.containsAccountWithEmailDomain("yahoo.com")); assertFalse(streams.containsAccountWithEmailDomain("ukr.net")); } @Test @Order(9) void getBalanceByEmail() { Account account = accounts.get(1); BigDecimal balance = streams.getBalanceByEmail(account.getEmail()); assertEquals(account.getBalance(), balance); } @Test @Order(10) void getBalanceByEmailThrowsException() { String fakeEmail = "fake@mail.com"; try { streams.getBalanceByEmail(fakeEmail); fail("Should throw exception"); } catch (Exception e) { assertTrue(e instanceof EntityNotFoundException); assertEquals(String.format("Cannot find Account by email=%s", fakeEmail), e.getMessage()); } } @Test @Order(11) void collectAccountsById() { Map idToAccountMap = streams.collectAccountsById(); assertEquals(accounts.get(0), idToAccountMap.get(1L)); assertEquals(accounts.get(1), idToAccountMap.get(2L)); assertEquals(accounts.get(2), idToAccountMap.get(3L)); assertEquals(accounts.get(3), idToAccountMap.get(4L)); } @Test @Order(12) void collectBalancesByEmailForAccountsCreatedOn() { Account account = accounts.get(3); Map emailToBalanceMap = streams.collectBalancesByEmailForAccountsCreatedOn(account.getCreationDate().getYear()); assertEquals(Map.of(account.getEmail(), account.getBalance()), emailToBalanceMap); } @Test @Order(13) void groupFirstNamesByLastNames() { Map> lastToFirstNamesMap = streams.groupFirstNamesByLastNames(); assertEquals(4, lastToFirstNamesMap.size()); assertEquals(Set.of("Justin"), lastToFirstNamesMap.get("Butler")); assertEquals(Set.of("Olivia"), lastToFirstNamesMap.get("Cardenas")); assertEquals(Set.of("Nolan"), lastToFirstNamesMap.get("Donovan")); assertEquals(Set.of("Lucas"), lastToFirstNamesMap.get("Lynn")); } @Test @Order(14) void groupCommaSeparatedFirstNamesByBirthdayMonth() { Map birthdayMonthToFirstNamesMap = streams.groupCommaSeparatedFirstNamesByBirthdayMonth(); assertEquals(3, birthdayMonthToFirstNamesMap.size()); assertEquals("Olivia", birthdayMonthToFirstNamesMap.get(Month.JANUARY)); assertEquals("Justin, Nolan", birthdayMonthToFirstNamesMap.get(Month.APRIL)); assertEquals("Lucas", birthdayMonthToFirstNamesMap.get(Month.MAY)); } @Test @Order(15) void groupTotalBalanceByCreationMonth() { Map totalBalanceByAccountCreationMonth = streams.groupTotalBalanceByCreationMonth(); assertEquals(2, totalBalanceByAccountCreationMonth.size()); assertEquals(BigDecimal.valueOf(210995), totalBalanceByAccountCreationMonth.get(Month.JUNE)); assertEquals(BigDecimal.valueOf(30869), totalBalanceByAccountCreationMonth.get(Month.MARCH)); } @Test @Order(16) void getCharacterFrequencyInFirstNames() { Map characterFrequencyInFirstAndLastNames = streams.getCharacterFrequencyInFirstNames(); assertEquals(3, characterFrequencyInFirstAndLastNames.get('a').longValue()); assertEquals(1, characterFrequencyInFirstAndLastNames.get('c').longValue()); assertEquals(3, characterFrequencyInFirstAndLastNames.get('i').longValue()); assertEquals(1, characterFrequencyInFirstAndLastNames.get('J').longValue()); assertEquals(1, characterFrequencyInFirstAndLastNames.get('L').longValue()); assertEquals(2, characterFrequencyInFirstAndLastNames.get('l').longValue()); assertEquals(2, characterFrequencyInFirstAndLastNames.get('u').longValue()); } @MethodSource("getCharacterFrequencyIgnoreCaseInFirstAndLastNamesArgs") @ParameterizedTest @Order(17) void getCharacterFrequencyIgnoreCaseInFirstAndLastNames(int nameLengthBound, Map resultMap) { var characterFrequencyInFirstAndLastNames = streams.getCharacterFrequencyIgnoreCaseInFirstAndLastNames(nameLengthBound); assertThat(characterFrequencyInFirstAndLastNames).isEqualTo(resultMap); } private static Stream getCharacterFrequencyIgnoreCaseInFirstAndLastNamesArgs() { return Stream.of( Arguments.arguments(2, buildMap(accounts, 2)), Arguments.arguments(5, buildMap(accounts, 5)), Arguments.arguments(7, buildMap(accounts, 7)) ); } private static Map buildMap(List accounts, int nameLengthBound) { var resultMap = new HashMap(); for (var a : accounts) { processName(resultMap, a.getFirstName(), nameLengthBound); processName(resultMap, a.getLastName(), nameLengthBound); } return resultMap; } private static void processName(Map resultMap, String name, int nameLengthBound) { if (name.length() >= nameLengthBound) { var chars = name.toLowerCase().toCharArray(); for (Character c : chars) { if (resultMap.putIfAbsent(c, 1L) != null) { resultMap.compute(c, (k, counter) -> counter + 1L); } } } } } ================================================ FILE: 5-0-functional-programming/5-3-1-crazy-optionals/README.md ================================================ # Crazy Optionals Improve your Optional API skills 💪 ### Task `CrazyOptionals` class consists of static methods that require implementation. Your job is to implement the *todo* section of that class using **Optional API**. To verify your implementation, run `CrazyOptionalsTest.java` ### Related materials :information_source: * [Optional у Java 9 ](https://youtu.be/YSZVyOHLbjk) * [Stream API. Optional. Курс Enterprise Java ](https://youtu.be/OD0hIs1cGmY) ### Exercise overview 🇺🇦 [![Watch the video](https://img.youtube.com/vi/yyhD0QZGMRs/0.jpg)](https://www.youtube.com/watch?v=yyhD0QZGMRs) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 5-0-functional-programming/5-3-1-crazy-optionals/pom.xml ================================================ 5-0-functional-programming com.bobocode 1.0-SNAPSHOT 4.0.0 5-3-1-crazy-optionals ================================================ FILE: 5-0-functional-programming/5-3-1-crazy-optionals/src/main/java/com/bobocode/fp/CrazyOptionals.java ================================================ package com.bobocode.fp; import com.bobocode.data.Accounts; import com.bobocode.fp.exception.AccountNotFoundException; import com.bobocode.fp.function.AccountProvider; import com.bobocode.fp.function.AccountService; import com.bobocode.fp.function.CreditAccountProvider; import com.bobocode.model.Account; import com.bobocode.model.CreditAccount; import com.bobocode.util.ExerciseNotCompletedException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.math.BigDecimal; import java.util.List; import java.util.Optional; import java.util.OptionalDouble; /** * {@link CrazyOptionals} is an exercise class. Each method represents some operation with a {@link Account} and * should be implemented using Optional API. Every method that is not implemented yet throws * {@link ExerciseNotCompletedException}. *

* TODO: remove exception and implement each method of this class using Optional API *

* TODO: to get the most out of your learning, visit our website *

* * @author Taras Boychuk */ public class CrazyOptionals { /** * Creates an instance of {@link Optional} using a text parameter * * @param text * @return optional object that holds text */ public static Optional optionalOfString(@Nullable String text) { throw new ExerciseNotCompletedException(); } /** * Adds a provided amount of money to the balance of an optional account. * * @param accountProvider * @param amount money to deposit */ public static void deposit(AccountProvider accountProvider, BigDecimal amount) { throw new ExerciseNotCompletedException(); } /** * Creates an instance of {@link Optional} using an account parameter * * @param account * @return optional object that holds account */ public static Optional optionalOfAccount(@Nonnull Account account) { throw new ExerciseNotCompletedException(); } /** * Returns the {@link Account} got from {@link AccountProvider}. If account provider does not provide an account, * returns a defaultAccount * * @param accountProvider * @param defaultAccount * @return account from provider or defaultAccount */ public static Account getAccount(AccountProvider accountProvider, Account defaultAccount) { throw new ExerciseNotCompletedException(); } /** * Passes account to {@link AccountService#processAccount(Account)} when account is provided. * Otherwise calls {@link AccountService#processWithNoAccount()} * * @param accountProvider * @param accountService */ public static void processAccount(AccountProvider accountProvider, AccountService accountService) { throw new ExerciseNotCompletedException(); } /** * Returns account provided by {@link AccountProvider}. If no account is provided it generates one using {@link Accounts} * Please note that additional account should not be generated if {@link AccountProvider} returned one. * * @param accountProvider * @return provided or generated account */ public static Account getOrGenerateAccount(AccountProvider accountProvider) { throw new ExerciseNotCompletedException(); } /** * Retrieves a {@link BigDecimal} balance using null-safe mappings. * * @param accountProvider * @return optional balance */ public static Optional retrieveBalance(AccountProvider accountProvider) { throw new ExerciseNotCompletedException(); } /** * Returns an {@link Account} provided by {@link AccountProvider}. If no account is provided, * throws {@link AccountNotFoundException} with a message "No Account provided!" * * @param accountProvider * @return provided account */ public static Account getAccount(AccountProvider accountProvider) { throw new ExerciseNotCompletedException(); } /** * Retrieves a {@link BigDecimal} credit balance using null-safe mappings. * * @param accountProvider * @return optional credit balance */ public static Optional retrieveCreditBalance(CreditAccountProvider accountProvider) { throw new ExerciseNotCompletedException(); } /** * Retrieves an {@link Account} with gmail email using {@link AccountProvider}. If no account is provided or it gets * not gmail then returns {@link Optional#empty()} * * @param accountProvider * @return optional gmail account */ public static Optional retrieveAccountGmail(AccountProvider accountProvider) { throw new ExerciseNotCompletedException(); } /** * Retrieves account using {@link AccountProvider} and fallbackProvider. In case main provider does not provide an * {@link Account} then account should ge retrieved from fallbackProvider. In case both provider returns no account * then {@link java.util.NoSuchElementException} should be thrown. * * @param accountProvider * @param fallbackProvider * @return account got from either accountProvider or fallbackProvider */ public static Account getAccountWithFallback(AccountProvider accountProvider, AccountProvider fallbackProvider) { throw new ExerciseNotCompletedException(); } /** * Returns an {@link Accounts} with the highest balance. Throws {@link java.util.NoSuchElementException} if input * list is empty * * @param accounts * @return account with the highest balance */ public static Account getAccountWithMaxBalance(List accounts) { throw new ExerciseNotCompletedException(); } /** * Returns the lowest balance values or empty if accounts list is empty * * @param accounts * @return the lowest balance values */ public static OptionalDouble findMinBalanceValue(List accounts) { throw new ExerciseNotCompletedException(); } /** * Finds an {@link Account} with max balance and processes it using {@link AccountService#processAccount(Account)} * * @param accounts * @param accountService */ public static void processAccountWithMaxBalance(List accounts, AccountService accountService) { throw new ExerciseNotCompletedException(); } /** * Calculates a sum of {@link CreditAccount#getCreditBalance()} of all accounts * * @param accounts * @return total credit balance */ public static double calculateTotalCreditBalance(List accounts) { throw new ExerciseNotCompletedException(); } } ================================================ FILE: 5-0-functional-programming/5-3-1-crazy-optionals/src/main/java/com/bobocode/fp/exception/AccountNotFoundException.java ================================================ package com.bobocode.fp.exception; public class AccountNotFoundException extends RuntimeException { public AccountNotFoundException(String message) { super(message); } } ================================================ FILE: 5-0-functional-programming/5-3-1-crazy-optionals/src/main/java/com/bobocode/fp/function/AccountProvider.java ================================================ package com.bobocode.fp.function; import com.bobocode.model.Account; import java.util.Optional; @FunctionalInterface public interface AccountProvider { Optional getAccount(); } ================================================ FILE: 5-0-functional-programming/5-3-1-crazy-optionals/src/main/java/com/bobocode/fp/function/AccountService.java ================================================ package com.bobocode.fp.function; import com.bobocode.model.Account; @FunctionalInterface public interface AccountService { void processAccount(Account account); default void processWithNoAccount(){ /* No operation */ } } ================================================ FILE: 5-0-functional-programming/5-3-1-crazy-optionals/src/main/java/com/bobocode/fp/function/CreditAccountProvider.java ================================================ package com.bobocode.fp.function; import com.bobocode.model.CreditAccount; import java.util.Optional; @FunctionalInterface public interface CreditAccountProvider { Optional getAccount(); } ================================================ FILE: 5-0-functional-programming/5-3-1-crazy-optionals/src/test/java/com/bobocode/fp/CrazyOptionalsTest.java ================================================ package com.bobocode.fp; import com.bobocode.data.Accounts; import com.bobocode.fp.exception.AccountNotFoundException; import com.bobocode.fp.function.AccountService; import com.bobocode.model.Account; import com.bobocode.model.CreditAccount; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.mockito.Mockito; import java.math.BigDecimal; import java.util.*; import static java.util.Comparator.comparing; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; /** * A test class for {@link CrazyOptionals}. * * @author Taras Boychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class CrazyOptionalsTest { @Test @Order(1) void optionalOfStringShouldAcceptNull() { Optional optionalString = CrazyOptionals.optionalOfString(null); assertEquals(Optional.empty(), optionalString); } @Test @Order(2) void optionalOfStringShouldHoldSting() { Optional optionalString = CrazyOptionals.optionalOfString("Hello"); assertEquals("Hello", optionalString.get()); } @Test @Order(3) void depositWhenAccountExistsShouldUpdateBalance() { Account account = Accounts.generateAccount(); BigDecimal balanceBeforeUpdate = account.getBalance(); BigDecimal amountToAdd = BigDecimal.valueOf(1000); CrazyOptionals.deposit(() -> Optional.of(account), amountToAdd); assertEquals(balanceBeforeUpdate.add(amountToAdd), account.getBalance()); } @Test @Order(4) void depositWhenOptionalIsEmptyShouldDoNothing() { CrazyOptionals.deposit(Optional::empty, BigDecimal.ZERO); } @Test @Order(5) void optionalOfAccountShouldNotAcceptNull() { assertThrows(NullPointerException.class, () -> CrazyOptionals.optionalOfAccount(null)); } @Test @Order(6) void optionalOfAccountShouldHoldAccount() { Account account = Accounts.generateAccount(); Optional optionalAccount = CrazyOptionals.optionalOfAccount(account); assertEquals(account, optionalAccount.get()); } @Test @Order(7) void getAccountShouldReturnAccountGotFromProvider() { Account providedAccount = Accounts.generateAccount(); Account defaultAccount = Accounts.generateAccount(); Account receivedAccount = CrazyOptionals.getAccount(() -> Optional.of(providedAccount), defaultAccount); assertEquals(providedAccount, receivedAccount); } @Test @Order(8) void getAccountWhenNoAccountIsProvidedShouldReturnDefaultAccount() { Account defaultAccount = Accounts.generateAccount(); Account receivedAccount = CrazyOptionals.getAccount(Optional::empty, defaultAccount); assertEquals(defaultAccount, receivedAccount); } @Test @Order(9) void processAccountWhenAccountIsProvidedShouldPassAccountToService() { Account account = Accounts.generateAccount(); BigDecimal initialBalance = account.getBalance(); BigDecimal bonus = BigDecimal.valueOf(1000); CrazyOptionals.processAccount(() -> Optional.of(account), a -> a.setBalance(account.getBalance().add(bonus))); assertEquals(initialBalance.add(bonus), account.getBalance()); } @Test @Order(10) void processAccountWhenNoAccountProvidedShouldProcessWithNoAccount() { Optional optionalAccountSpy = spy(Optional.empty()); AccountService accountService = mock(AccountService.class); CrazyOptionals.processAccount(() -> optionalAccountSpy, accountService); verify(accountService, times(1)).processWithNoAccount(); verify(accountService, never()).processAccount(any()); } @Test @Order(11) void processAccountShouldUseNullSafeDeclarativeIfStatement() { Optional optionalAccountSpy = spy(Optional.empty()); CrazyOptionals.processAccount(() -> optionalAccountSpy, account -> { }); verify(optionalAccountSpy, times(1)).ifPresentOrElse(any(), any()); } @Test @Order(12) void getOrGenerateAccountShouldReturnAccountGotFromProvider() { Account providedAccount = Accounts.generateAccount(); Account receivedAccount = CrazyOptionals.getOrGenerateAccount(() -> Optional.of(providedAccount)); assertEquals(providedAccount, receivedAccount); } @Test @Order(13) void getOrGenerateAccountWhenNoAccountIsProvidedShouldGenerateAccount() { Account receivedAccount = CrazyOptionals.getOrGenerateAccount(Optional::empty); assertNotNull(receivedAccount); } @Test @Order(14) void getOrGenerateAccountWhenNoAccountIsProvidedShouldUseLazyInitialization() { Optional optionalAccountSpy = spy(Optional.empty()); CrazyOptionals.getOrGenerateAccount(() -> optionalAccountSpy); verify(optionalAccountSpy, never()).orElse(any()); verify(optionalAccountSpy, never()).get(); verify(optionalAccountSpy, times(1)).orElseGet(any()); } @Test @Order(15) void retrieveBalanceWhenAccountIsNotProvidedShouldReturnEmptyOptional() { Optional balance = CrazyOptionals.retrieveBalance(Optional::empty); assertFalse(balance.isPresent()); } @Test @Order(16) void retrieveBalanceWhenBalanceIsNullShouldReturnEmptyOptional() { Account account = Accounts.generateAccount(); account.setBalance(null); Optional balance = CrazyOptionals.retrieveBalance(() -> Optional.of(account)); assertFalse(balance.isPresent()); } @Test @Order(17) void retrieveBalanceShouldReturnOptionalBalance() { Account account = Accounts.generateAccount(); Optional balance = CrazyOptionals.retrieveBalance(() -> Optional.of(account)); assertEquals(account.getBalance(), balance.get()); } @Test @Order(18) void retrieveBalanceShouldUseNullSafeMapping() { Account account = Accounts.generateAccount(); Optional optionalAccountSpy = spy(Optional.of(account)); CrazyOptionals.retrieveBalance(() -> optionalAccountSpy); verify(optionalAccountSpy, never()).get(); verify(optionalAccountSpy, never()).orElse(any()); verify(optionalAccountSpy, never()).orElseGet(any()); verify(optionalAccountSpy, times(1)).map(any()); } @Test @Order(19) void getAccountShouldReturnProvidedAccount() { Account account = Accounts.generateAccount(); Account receivedAccount = CrazyOptionals.getAccount(() -> Optional.of(account)); assertEquals(account, receivedAccount); } @Test @Order(20) void getAccountWhenNoAccountIsProvidedShouldThrowAccountNotFoundException() { AccountNotFoundException exception = assertThrows(AccountNotFoundException.class, () -> CrazyOptionals.getAccount(Optional::empty)); assertEquals("No Account provided!", exception.getMessage()); } @Test @Order(21) void retrieveCreditBalanceWhenAccountIsNotProvidedShouldReturnEmptyOptional() { Optional creditBalance = CrazyOptionals.retrieveCreditBalance(Optional::empty); assertFalse(creditBalance.isPresent()); } @Test @Order(22) void retrieveCreditBalanceWhenBalanceIsNullShouldReturnEmptyOptional() { CreditAccount account = Accounts.generateCreditAccount(); account.setCreditBalance(null); Optional creditBalance = CrazyOptionals.retrieveCreditBalance(() -> Optional.of(account)); assertFalse(creditBalance.isPresent()); } @Test @Order(23) void retrieveCreditBalanceShouldReturnOptionalBalance() { CreditAccount account = Accounts.generateCreditAccount(); Optional creditBalance = CrazyOptionals.retrieveCreditBalance(() -> Optional.of(account)); assertEquals(account.getCreditBalance(), creditBalance); } @Test @Order(24) void retrieveCreditBalanceShouldUseNullSafeMapping() { CreditAccount creditAccount = Accounts.generateCreditAccount(); Optional optionalCreditAccountSpy = spy(Optional.of(creditAccount)); CrazyOptionals.retrieveCreditBalance(() -> optionalCreditAccountSpy); verify(optionalCreditAccountSpy, never()).get(); verify(optionalCreditAccountSpy, never()).orElse(any()); verify(optionalCreditAccountSpy, never()).orElseGet(any()); verify(optionalCreditAccountSpy, times(1)).flatMap(any()); } @Test @Order(25) void retrieveAccountGmailWhenNoAccountProvidedShouldReturnEmptyOptional() { Optional optionalGmailAccount = CrazyOptionals.retrieveAccountGmail(Optional::empty); assertEquals(Optional.empty(), optionalGmailAccount); } @Test @Order(26) void retrieveAccountGmailWhenAccountEmailIsNotGmailShouldReturnEmptyOptional() { Account account = Accounts.generateCreditAccount(); account.setEmail("bobby@yahoo.com"); Optional optionalGmailAccount = CrazyOptionals.retrieveAccountGmail(() -> Optional.of(account)); assertEquals(Optional.empty(), optionalGmailAccount); } @Test @Order(27) void retrieveAccountGmailWhenEmailIsGmailShouldReturnEmail() { Account account = Accounts.generateCreditAccount(); account.setEmail("johnny@gmail.com"); Optional optionalGmailAccount = CrazyOptionals.retrieveAccountGmail(() -> Optional.of(account)); assertEquals(account, optionalGmailAccount.get()); } @Test @Order(28) void retrieveAccountGmailShouldUseNullSafeFiltering() { Account account = Accounts.generateAccount(); account.setEmail("johnny@gmail.com"); Optional optionalAccountSpy = spy(Optional.of(account)); CrazyOptionals.retrieveAccountGmail(() -> optionalAccountSpy); verify(optionalAccountSpy, never()).get(); verify(optionalAccountSpy, never()).orElse(any()); verify(optionalAccountSpy, times(1)).filter(any()); } @Test @Order(29) void getAccountWithFallbackShouldBeRetrievedFromMainProvider() { Account account = Accounts.generateAccount(); Account fallbackAccount = Accounts.generateAccount(); Account retrievedAccount = CrazyOptionals.getAccountWithFallback(() -> Optional.of(account), () -> Optional.of(fallbackAccount)); assertEquals(account, retrievedAccount); } @Test @Order(30) void getAccountWithFallbackWhenNoAccountIsProvidedByMainProviderShouldUseFallback() { Account fallbackAccount = Accounts.generateAccount(); Account retrievedAccount = CrazyOptionals.getAccountWithFallback(Optional::empty, () -> Optional.of(fallbackAccount)); assertEquals(fallbackAccount, retrievedAccount); } @Test @Order(31) void getAccountWithFallbackWhenNoAccountShouldThrowException() { assertThrows(NoSuchElementException.class, () -> CrazyOptionals.getAccountWithFallback(Optional::empty, Optional::empty)); } @Test @Order(32) void getAccountWithFallbackShouldUseNullSafeFallbackStrategy() { Optional optionalAccountSpy = spy(Optional.empty()); CrazyOptionals.getAccountWithFallback(() -> optionalAccountSpy, () -> Optional.of(Accounts.generateAccount())); verify(optionalAccountSpy, times(1)).isPresent(); verify(optionalAccountSpy, never()).isEmpty(); verify(optionalAccountSpy, never()).get(); verify(optionalAccountSpy, never()).orElse(any()); verify(optionalAccountSpy, never()).orElseGet(any()); verify(optionalAccountSpy, times(1)).or(any()); } @Test @Order(33) void getAccountWithMaxBalance() { List accounts = Accounts.generateAccountList(5); Account richestAccount = getMaxAccount(accounts, comparing(Account::getBalance)); Account receivedAccount = CrazyOptionals.getAccountWithMaxBalance(accounts); assertEquals(richestAccount, receivedAccount); } @Test @Order(34) void getAccountWithMaxBalanceWhenListIsEmptyShouldThrowException() { assertThrows(NoSuchElementException.class, () -> CrazyOptionals.getAccountWithMaxBalance(Collections.emptyList())); } @Test @Order(35) void findMinBalanceValueShouldReturnCorrectDoubleValue() { List accounts = Accounts.generateAccountList(5); Account accountWithLowestBalance = getMaxAccount(accounts, comparing(Account::getBalance).reversed()); double expectedBalance = accountWithLowestBalance.getBalance().doubleValue(); OptionalDouble optionalMinBalance = CrazyOptionals.findMinBalanceValue(accounts); assertEquals(expectedBalance, optionalMinBalance.getAsDouble(), 0.001); } @Test @Order(36) void findMinBalanceValueWhenListIsEmptyShouldReturnOptionalEmpty() { OptionalDouble optionalMinBalance = CrazyOptionals.findMinBalanceValue(Collections.emptyList()); assertEquals(OptionalDouble.empty(), optionalMinBalance); } @Test @Order(37) void processAccountWithMaxBalance() { List accounts = Accounts.generateAccountList(5); Account richestAccount = getMaxAccount(accounts, comparing(Account::getBalance)); AccountService accountServiceSpy = Mockito.spy(AccountService.class); CrazyOptionals.processAccountWithMaxBalance(accounts, accountServiceSpy); verify(accountServiceSpy, times(1)).processAccount(richestAccount); } @Test @Order(38) void calculateTotalCreditBalanceShouldCalculateCorrectTotal() { List accounts = Accounts.generateCreditAccountList(5); double expectedTotal = calculateTotalCreditBalance(accounts); double calculatedTotal = CrazyOptionals.calculateTotalCreditBalance(accounts); assertEquals(expectedTotal, calculatedTotal, 0.001); } @Test @Order(39) void calculateTotalCreditBalanceWhenListIsEmptyShouldReturnZero() { double calculatedTotal = CrazyOptionals.calculateTotalCreditBalance(Collections.emptyList()); assertEquals(0.0, calculatedTotal, 0.001); } private Account getMaxAccount(List accounts, Comparator comparator) { return accounts.stream() .max(comparator) .orElseThrow(); } private double calculateTotalCreditBalance(List accounts) { return accounts.stream() .map(CreditAccount::getCreditBalance) .flatMap(Optional::stream) .mapToDouble(BigDecimal::doubleValue) .sum(); } } ================================================ FILE: 5-0-functional-programming/5-4-1-fun-prime-numbers/README.MD ================================================ # Fun Prime Numbers Improve your functional programming skills implementing various operations based on an integer stream of Prime Numbers 💪 ### Pre-conditions ❗ You're supposed to be familiar with Lambdas, Stream API and Optional API ### Objectives * create an **infinite stream** of integers with a **custom increment** ✅ * **find matching elements** within a stream ✅ * **limit** the number of elements within a stream ✅ * **calculate a sum** of elements of an `IntStream` ✅ * transform a stream of primitives into a stream of `Integer` objects ("**Stream boxing**") ✅ --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 5-0-functional-programming/5-4-1-fun-prime-numbers/pom.xml ================================================ 5-0-functional-programming com.bobocode 1.0-SNAPSHOT 4.0.0 5-4-1-fun-prime-numbers ================================================ FILE: 5-0-functional-programming/5-4-1-fun-prime-numbers/src/main/java/com/bobocode/fp/OOSumOfPrimes.java ================================================ package com.bobocode.fp; /** * This examples demonstrates how to calculate the sum of prime numbers using Object-Oriented approach. * Please note that in order to perform this calculation in OOP-style we have to use mutable variables * (e.g. sumOfPrimes, i, primes). Implementing the same functionality in a functional style using Steam API and lambdas, * you will not create and/or modify variables. * * @author Taras Boychuk */ public class OOSumOfPrimes { public static void main(String[] args) { int sumOfPrimes = 0; int primes = 0; for (int i = 0; primes <= 20; i++) { if (isPrime(i)) { System.out.println(primes + " : " + i); sumOfPrimes += i; primes++; } } System.out.println("Sum of first 20 primes: " + sumOfPrimes); } private static boolean isPrime(int n) { for (int i = 2; i < n; i++) { if (n % i == 0) { return false; } } return true; } } ================================================ FILE: 5-0-functional-programming/5-4-1-fun-prime-numbers/src/main/java/com/bobocode/fp/PrimeNumbers.java ================================================ package com.bobocode.fp; import com.bobocode.util.ExerciseNotCompletedException; import java.util.List; import java.util.Map; import java.util.function.IntConsumer; import java.util.stream.IntStream; /** * {@link PrimeNumbers} provides an API to work with prime numbers. The implementation is based on the * {@link java.util.stream.IntStream} of prime numbers. That stream is used in all public methods on this class. *

* See {@link OOSumOfPrimes} for a reference * TODO: implement each method according to the javadoc and verify it by running {@link PrimeNumbersTest} *

* TODO: if you find this exercise valuable and you want to get more like it, * please support us on Patreon * * @author Taras Boychuk */ public class PrimeNumbers { private PrimeNumbers() { } /** * Generates an infinite int stream of prime numbers. * The stream values are 2, 3, 5,... and so on. * * @return an infinite int stream of prime numbers */ public static IntStream stream() { throw new ExerciseNotCompletedException(); // todo: create an infinite stream of ints, then filter prime numbs } /** * Generates an int stream of a certain amount of prime numbers. * It is based on the {@link PrimeNumbers#stream()} but specifies the exact size of the stream. * * @return an int stream of prime numbers with a specified size */ public static IntStream stream(int size) { throw new ExerciseNotCompletedException(); // todo: use the prev to generate a stream method but limit its size } /** * Calculates the sum on first n prime numbers. * E.g. if n = 5, the result should be 2 + 3 + 5 + 7 + 11 = 28 * * @param n the number of first prime numbers * @return the sum of n prime numbers */ public static int sum(int n) { throw new ExerciseNotCompletedException(); // todo: use prev method and calculate the sum } /** * Collects n first prime numbers. * * @return a list of collected prime numbers */ public static List list(int n) { throw new ExerciseNotCompletedException(); // todo: collect prime numbers into the list } /** * Find a prime number by index and then applies a provided consumer passing found prime number * * @param idx the position of a prime number (index), starting from 0 * @param consumer a logic that should be applied to the found prime number */ public static void processByIndex(int idx, IntConsumer consumer) { throw new ExerciseNotCompletedException(); // todo: find an element in the stream by index and process it } /** * Creates a list of n prime numbers and returns a map where all of those prime numbers are groped. The key represents * an amount of digits and the value is a corresponding list of all prime numbers. *

* So if you will call this method for with argument 20, you will receive the following map: * {1=[2, 3, 5, 7], 2=[11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]} * * @param n – the amount of prime numbers * @return a map with prime number grouped by the amount of digits */ public static Map> groupByAmountOfDigits(int n) { throw new ExerciseNotCompletedException(); // todo: group n prime numbers by the amount of digits } } ================================================ FILE: 5-0-functional-programming/5-4-1-fun-prime-numbers/src/test/java/com/bobocode/fp/PrimeNumbersTest.java ================================================ package com.bobocode.fp; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; /** * A test class for {@link PrimeNumbers}. * * @author Taras Boychuk */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) class PrimeNumbersTest { @Order(1) @Test void stream() { var primeNumbersStream = PrimeNumbers.stream(); var primeList = primeNumbersStream .limit(5) .boxed() .collect(toList()); assertThat(primeList).isEqualTo(List.of(2, 3, 5, 7, 11)); } @Order(2) @Test void streamN() { var primeNumbersStream = PrimeNumbers.stream(8); var primeList = primeNumbersStream .boxed() .collect(toList()); assertThat(primeList).isEqualTo(List.of(2, 3, 5, 7, 11, 13, 17, 19)); } @Order(3) @ParameterizedTest @CsvSource({"0, 0", "1, 2", "2, 5", "3, 10", "4, 17", "5, 28", "10, 129", "20, 639"}) void sum(int n, int sumOfPrimes) { int result = PrimeNumbers.sum(n); assertThat(result).isEqualTo(sumOfPrimes); } @Order(4) @ParameterizedTest @MethodSource("collectArgs") void collect(int n, List primeNumbersList) { List result = PrimeNumbers.list(n); assertThat(result).isEqualTo(primeNumbersList); } @Order(5) @ParameterizedTest @CsvSource({"0, 2", "1, 3", "2, 5", "3, 7", "10, 31", "20, 73", "279, 1811"}) void processByIndexFindsCorrectPrimeNumber(int index, int primeNumber) { var list = new ArrayList<>(); PrimeNumbers.processByIndex(index, list::add); assertThat(list.get(0)).isEqualTo(primeNumber); } static Stream collectArgs() { return Stream.of( arguments(1, List.of(2)), arguments(2, List.of(2, 3)), arguments(3, List.of(2, 3, 5)), arguments(4, List.of(2, 3, 5, 7)), arguments(5, List.of(2, 3, 5, 7, 11)), arguments(10, List.of(2, 3, 5, 7, 11, 13, 17, 19, 23, 29)) ); } @Order(6) @ParameterizedTest @MethodSource("groupByAmountOfDigitsArgs") void groupByAmountOfDigits(int count, Map> digitsToPrimesMap) { var result = PrimeNumbers.groupByAmountOfDigits(count); assertThat(result).isEqualTo(digitsToPrimesMap); } static Stream groupByAmountOfDigitsArgs() { return Stream.of( arguments(1, Map.of(1, List.of(2))), arguments(10, Map.of( 1, List.of(2, 3, 5, 7), 2, List.of(11, 13, 17, 19, 23, 29) )), arguments(20, Map.of( 1, List.of(2, 3, 5, 7), 2, List.of(11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71) )), arguments(30, Map.of( 1, List.of(2, 3, 5, 7), 2, List.of(11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97), 3, List.of(101, 103, 107, 109, 113) ) ) ); } } ================================================ FILE: 5-0-functional-programming/README.md ================================================ # Java Functional Programming   ### Master Java functional features and write more consice and easy to parallel code 💪   ### Why should you care? ⚡️ Nowadays **every Java developer uses some functional programming features in an everyday job**. So make sure you've checked out the rest of the materials in this module and build strong skills using these language features! The main idea is to keep Java OO language but **enable some functional programming features**. It does two things: * makes the code more concise * allows easier parallelization Java SE 8+ provides a rich API that enables functional programming features based on * Functional Interfaces * Lambdas * Stream API * Optional API ### At the end of this module you will be able to Write this ```java public List findAllGmailAccounts(List accounts) { return accounts.stream() .filter(a -> a.getEmail().endsWith("@gmail.com")) .collect(toList()); } ``` instead of this ```java public List findAllGmailAccounts(List accounts) { List gmailAccounts = new ArrayList<>(); for (Account account : accounts) { if (account.getEmail().endsWith("@gmail.com")) { gmailAccounts.add(account); } } return gmailAccounts; } ``` Among other you will be able to * use **Funtional Interfaces** and **Lambdas** in order to **pass around functions** like first-class citizens ✅ * **process data collections** in a **concise** and **easy way to understand** using **Stream API** ✅ * write **null-safe code** using **Optional API** ✅ ### Learn or skip ? Think you're cool enough to skip this topic? 😎 Hand on a sec...☝️ Can you easily understand and write lambdas like this? ```java runnable -> () -> { Thread t = new Thread(runnable); t.start(); return t; }; ``` or stream pipelines like this ? 🧐 ```java accounts.stream() .flatMap(a -> Stream.of(a.getFirstName(), a.getLastName())) .map(String::toLowerCase) .flatMapToInt(String::chars) .mapToObj(c -> (char) c) .collect(groupingBy(identity(), counting())); ``` No worries if you don't! Be patient, **do the exercises in this module**, and you will be able to do not only this 👆. **You will understand and use functional programming techniques far beyond your expectations** 🔥 ================================================ FILE: 5-0-functional-programming/pom.xml ================================================ 4.0.0 pom 5-1-1-crazy-lambdas 5-2-1-crazy-streams 5-3-1-crazy-optionals 5-0-1-lambda-functions-map 5-0-2-stream-sum-of-squares 5-4-1-fun-prime-numbers com.bobocode java-fundamentals-exercises 1.0-SNAPSHOT 5-0-functional-programming com.bobocode java-fundamentals-util 1.0-SNAPSHOT ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/lambdas/tutorial/CrazyLambdaExample.java ================================================ package com.bobocode.lambdas.tutorial; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; /** * An example of crazy lambda that is hard but still important to understand */ public class CrazyLambdaExample { public static void main(String[] args) { Supplier, Consumer>> stringPredicateToConsumerFunctionSupplier = getStringPredicateToConsumerFunctionSupplier(); Function, Consumer> stringPredicateConsumerFunction = stringPredicateToConsumerFunctionSupplier.get(); Consumer stringIsEmptyChecker = stringPredicateConsumerFunction.apply(String::isEmpty); stringIsEmptyChecker.accept(""); } /** * Returns the {@link Supplier} instance that supplies {@link Function} that receives a string {@link Predicate} as * a parameter and returns string {@link Consumer} * * @return an instance of supplier */ private static Supplier, Consumer>> getStringPredicateToConsumerFunctionSupplier() { return () -> stringPredicate -> str -> System.out.println(stringPredicate.test(str)); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/lambdas/tutorial/FunctionComposition.java ================================================ package com.bobocode.lambdas.tutorial; import java.util.Objects; import java.util.function.Function; import java.util.function.IntUnaryOperator; import java.util.function.Predicate; /** * One of functional programming techniques if a function composition. Having two different functions f(x) and g(x) in * math you would write f(g(x)) to compose those function. The same you can do in Java using method * {@link IntUnaryOperator#compose} or {@link IntUnaryOperator#andThen(IntUnaryOperator)} *

* In fact most predefined functional interfaces have similar capabilities. * E.g. {@link Function#compose(Function)} or {@link Predicate#and(Predicate)} */ public class FunctionComposition { public static void main(String[] args) { printSquareOfDoubleUsingFunctionComposition(); printStringIsBlankUsingPredicateComposition(); } private static void printSquareOfDoubleUsingFunctionComposition() { IntUnaryOperator squareFunction = a -> a * a; // s(x) = x * x IntUnaryOperator doublerFunction = a -> 2 * a; // d(x) = 2 * x IntUnaryOperator squareOfDoubleFunction = squareFunction.compose(doublerFunction); // s(d(x)) System.out.println("square(double(3)) = " + squareOfDoubleFunction.applyAsInt(3)); } private static void printStringIsBlankUsingPredicateComposition() { Predicate isEmptyPredicate = String::isEmpty; Predicate isNotNullPredicate = Objects::nonNull; Predicate isNotBlank = isNotNullPredicate.and(isEmptyPredicate.negate()); String str = "Hi"; System.out.println("String \"" + str + "\" is not blank? " + isNotBlank.test(str)); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/lambdas/tutorial/LambdaAndMethodReference.java ================================================ package com.bobocode.lambdas.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.List; import java.util.function.Consumer; /** * Method reference is a shorthand for lambda expression that could be use in some cases for better readability. */ public class LambdaAndMethodReference { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); printAccountsUsingLambda(accounts); printAccountsUsingMethodReference(accounts); } private static void printAccountsUsingLambda(List accounts) { processAccounts(accounts, a -> System.out.println(a)); } private static void printAccountsUsingMethodReference(List accounts) { processAccounts(accounts, System.out::println); } private static void processAccounts(List accounts, Consumer consumer) { for (Account account : accounts){ consumer.accept(account); } } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/lambdas/tutorial/LambdaComparatorExample.java ================================================ package com.bobocode.lambdas.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.Comparator; import java.util.List; import static java.util.Comparator.comparing; public class LambdaComparatorExample { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); sortUsingAnonymousClass(accounts); sortUsingLambda(accounts); sortUsingMethodReference(accounts); } private static void sortUsingAnonymousClass(List accounts) { accounts.sort(new Comparator() { @Override public int compare(Account o1, Account o2) { return o1.getFirstName().compareTo(o2.getFirstName()); } }); } private static void sortUsingLambda(List accounts) { accounts.sort((a1, a2) -> a1.getFirstName().compareTo(a2.getFirstName())); } private static void sortUsingMethodReference(List accounts) { accounts.sort(comparing(Account::getFirstName)); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/lambdas/tutorial/LambdaRunnableExample.java ================================================ package com.bobocode.lambdas.tutorial; public class LambdaRunnableExample { public static void main(String[] args) { sayHelloFromNewThread(); sayHelloFromNewThreadUsingLambda(); } private static void sayHelloFromNewThread() { Runnable runnable = new Runnable() { @Override public void run() { System.out.println("Hello from new Thread!"); } }; Thread t = new Thread(runnable); t.start(); } private static void sayHelloFromNewThreadUsingLambda() { Runnable runnable = () -> System.out.println("Hello Lambda!"); Thread t = new Thread(runnable); t.start(); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/lambdas/tutorial/MethodRefToUpperCaseExample.java ================================================ package com.bobocode.lambdas.tutorial; import java.util.function.Function; import java.util.function.Supplier; import java.util.function.UnaryOperator; /** * A list of different examples of method reference usage. */ public class MethodRefToUpperCaseExample { public static void main(String[] args) { String s = "Method reference insights"; printUpperStringUsingFunction(s); printUpperStringUsingUnaryOperation(s); printUpperStringUsingStringSupplier(s); } /** * This is the unbound method reference example. Since we used unbound method reference, the {@link Function} can * receive a string as an input parameter. */ private static void printUpperStringUsingFunction(String s) { Function upperCaseFunction = String::toUpperCase; System.out.println(upperCaseFunction.apply(s)); } /** * This is the unbound method reference example. Since toUpperCase() receives and returns the same type * {@link String}, we can easily replace {@link Function} with {@link UnaryOperator} */ private static void printUpperStringUsingUnaryOperation(String s) { UnaryOperator upperCaseOperator = String::toUpperCase; System.out.println(upperCaseOperator.apply(s)); } /** * This is the bound method reference example. We actually reference a method that is bound to concrete * string instance. Since we reference a concrete string, there is no input parameter, only an output. In this case * we can use {@link Supplier} */ private static void printUpperStringUsingStringSupplier(String s) { Supplier stringSupplier = s::toUpperCase; System.out.println(stringSupplier.get()); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/lambdas/tutorial/MethodReferenceExamples.java ================================================ package com.bobocode.lambdas.tutorial; import java.util.function.IntBinaryOperator; import java.util.function.IntUnaryOperator; import java.util.function.UnaryOperator; /** * A list of method reference examples. */ public class MethodReferenceExamples { public static void main(String[] args) { printAbsUsingMethodReference(-23); printSumUsingMethodReference(25, 50); printUpperStringUsingMethodReference("Lambda is awesome!"); } private static void printAbsUsingMethodReference(int a) { IntUnaryOperator absOperator = Math::abs; int result = absOperator.applyAsInt(a); System.out.println("abs(" + a + ") = " + result); } private static void printSumUsingMethodReference(int a, int b) { IntBinaryOperator sumOperator = Math::addExact; int result = sumOperator.applyAsInt(a, b); System.out.println("\n" + a + " + " + b + " = " + result); } private static void printUpperStringUsingMethodReference(String s) { UnaryOperator upperOperation = String::toUpperCase; System.out.println("\n" + s + " -> " + upperOperation.apply(s)); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/lambdas/tutorial/PredefinedInterfacePrimitives.java ================================================ package com.bobocode.lambdas.tutorial; import java.util.concurrent.ThreadLocalRandom; import java.util.function.DoubleSupplier; import java.util.function.IntToDoubleFunction; import java.util.function.LongBinaryOperator; /** * Always use special classes for Primitives */ public class PredefinedInterfacePrimitives { public static void main(String[] args) { printRandomDoubleUsingSupplier(); printLongSumUsingBinaryOperation(124124L, 132134L); printSqrtUsingFunction(25); } private static void printRandomDoubleUsingSupplier() { DoubleSupplier doubleSupplier = () -> ThreadLocalRandom.current().nextDouble(); System.out.println("Random double: " + doubleSupplier.getAsDouble()); } private static void printLongSumUsingBinaryOperation(long a, long b) { LongBinaryOperator sumOperator = Long::sum; System.out.println("\n" + a + " " + b + " = " + sumOperator.applyAsLong(a, b)); } private static void printSqrtUsingFunction(int a) { IntToDoubleFunction sqrtFunction = Math::sqrt; System.out.println("\nsqrt(" + a + ") = " + sqrtFunction.applyAsDouble(a)); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/lambdas/tutorial/PredefinedInterfacesExamples.java ================================================ package com.bobocode.lambdas.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.concurrent.ThreadLocalRandom; import java.util.function.*; import java.util.stream.IntStream; /** * A list of predefined interfaces examples. */ public class PredefinedInterfacesExamples { public static void main(String[] args) { printEmailUsingAccountConsumer(); printARandomNumberUsingIntegerSupplier(); calculate3xValueUsingIntegerFunction(); checkIfNumberIsPositiveUsingIntegerPredicate(); verifyGoogleEmailUsingAccountPredicate(); printPrimeNumbersUsingIntegerPredicate(); } private static void printARandomNumberUsingIntegerSupplier() { IntSupplier integerSupplier = () -> ThreadLocalRandom.current().nextInt(1000); System.out.println("\nNext random value: " + integerSupplier.getAsInt()); } private static void printEmailUsingAccountConsumer() { Consumer accountConsumer = acc -> System.out.println("\nAccount email: " + acc.getEmail()); Account account = Accounts.generateAccount(); accountConsumer.accept(account); } private static void calculate3xValueUsingIntegerFunction() { IntUnaryOperator tripleFunction = n -> 3 * n; int a = 12; System.out.println("\n3 * " + a + " = " + tripleFunction.applyAsInt(a)); } private static void checkIfNumberIsPositiveUsingIntegerPredicate() { IntPredicate isPositive = n -> n > 0; int b = ThreadLocalRandom.current().nextInt(); System.out.println("\n" + b + " is " + (isPositive.test(b) ? "positive" : "negative")); } private static void verifyGoogleEmailUsingAccountPredicate() { Account account = Accounts.generateAccount(); Predicate isGmailUser = a -> a.getEmail().endsWith("@gmail.com"); System.out.println("\n" + account.getEmail() + " is " + (isGmailUser.test(account) ? "" : "not") + " a Google email."); } private static void printPrimeNumbersUsingIntegerPredicate() { IntPredicate isPrime = n -> IntStream.range(2, n).noneMatch(i -> n % i == 0); System.out.println(); IntStream.rangeClosed(1, 25) .forEach(i -> System.out.printf("%3d %10s\n", i, (isPrime.test(i) ? " is prime" : ""))); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/optionals/tutorial/OptionalCreation.java ================================================ package com.bobocode.optionals.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.Optional; public class OptionalCreation { public static void main(String[] args) { Account account = Accounts.generateAccount(); Optional optionalAccount = Optional.of(account); Optional optionalNullableAccount = Optional.ofNullable(account); Optional optionalEmptyAccount = Optional.empty(); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/optionals/tutorial/OptionalExamples.java ================================================ package com.bobocode.optionals.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.Optional; import java.util.function.Supplier; public class OptionalExamples { private static Account DEFAULT_ACCOUNT = Accounts.generateAccount(); public static void main(String[] args) { Account account = Accounts.generateAccount(); Optional optionalAccount = Optional.of(account); printAccountUsingWrongApproach(optionalAccount); printAccountUsingRightApproach(optionalAccount); printAccountOrDefault(optionalAccount); printAccountOrRandomLazily(optionalAccount); } /** * It is not correct to process to process {@link Optional} values in the old imperative way, as it is shown below */ private static void printAccountUsingWrongApproach(Optional optionalAccount) { if (optionalAccount.isPresent()) { Account account = optionalAccount.get(); System.out.println(account); } } /** * The right way is to pass a consumer that will be used in case {@link Optional} value is not empty */ private static void printAccountUsingRightApproach(Optional optionalAccount) { optionalAccount.ifPresent(System.out::println); } /** * or add a default value. Now you're safely trying to get an account instance, because in case it's null * you'll get a default value */ private static void printAccountOrDefault(Optional optionalAccount) { Account account = optionalAccount.orElse(DEFAULT_ACCOUNT); System.out.println(account); } /** * The version with {@link Supplier} should be used in case getting default value requires * additional resources. In this case default instance will be only created if optional account is empty */ private static void printAccountOrRandomLazily(Optional optionalAccount) { Account account = optionalAccount.orElseGet(() -> Accounts.generateAccount()); System.out.println(account); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/optionals/tutorial/OptionalImperativeVsDeclarativeCheck.java ================================================ package com.bobocode.optionals.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.Optional; public class OptionalImperativeVsDeclarativeCheck { public static void main(String[] args) { Account account = Accounts.generateAccount(); Optional optionalAccount = Optional.of(account); printAccount(account); printOptionalAccount(optionalAccount); } /** * An if statement is a classic example of imperative check */ private static void printAccount(Account account) { if (account != null) { System.out.println(account); } else { System.out.println("No such element exists!"); } } /** * This is an example of declarative check. You don't write an if statement yourself, it is performed inside the * method ifPresentOrElse() */ private static void printOptionalAccount(Optional optionalAccount) { optionalAccount.ifPresentOrElse(System.out::println, () -> System.out.println("No such element exists!")); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/optionals/tutorial/OptionalMapping.java ================================================ package com.bobocode.optionals.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import com.bobocode.model.CreditAccount; import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Optional; public class OptionalMapping { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); List creditAccounts = Collections.singletonList(new CreditAccount(BigDecimal.valueOf(1000))); Optional optionalAccount = accounts.stream().findAny(); printBalanceUsingNullCheck(optionalAccount.get()); printBalanceUsingOptionalMapping(optionalAccount); printBalanceOfFirstGoogleAccount(accounts); printCreditBalanceOfFirstGoogleAccount(creditAccounts); } /** * Hierarchical relation with potential null value, like an account that can be null, has a balance * field that also can be null */ private static void printBalanceUsingNullCheck(Account account) { if (account != null) { if (account.getBalance() != null) { System.out.println(account.getBalance()); } } } /** * When you want to access a nullable value of account, you can use method map(), that receives a mapper which * transform your nullable value into an optional value */ private static void printBalanceUsingOptionalMapping(Optional optionalAccount) { optionalAccount.map(Account::getBalance).ifPresent(System.out::println); } /** * An example, when you process a stream and find an optional element, with an relation that can be null */ private static void printBalanceOfFirstGoogleAccount(List accounts) { accounts.stream() .filter(a -> a.getEmail().endsWith("google.com")) .findFirst() .map(Account::getBalance) .ifPresent(System.out::println); } /** * When you use map() it wraps a value within Optional container, and if that field is already an {@link Optional}, * you get Optional>. To avoid this use flatMap() */ private static void printCreditBalanceOfFirstGoogleAccount(List creditAccounts) { creditAccounts.stream() .filter(a -> a.getEmail().endsWith("google.com")) .filter(a -> a.getBalance().compareTo(BigDecimal.valueOf(90000)) > 0) .findFirst() .flatMap(CreditAccount::getCreditBalance) .ifPresent(System.out::println); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/optionals/tutorial/OptionalPrimitives.java ================================================ package com.bobocode.optionals.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.math.BigDecimal; import java.util.List; import java.util.OptionalDouble; public class OptionalPrimitives { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); OptionalDouble minBalance = findMinBalance(accounts); // TODO: NEVER USE Optional System.out.println("Min balance is " + minBalance.orElse(0)); } /** * Always use special classes for optional primitives. See also {@link java.util.OptionalInt}, * {@link java.util.OptionalLong} * */ private static OptionalDouble findMinBalance(List accounts) { return accounts.stream() .map(Account::getBalance) .mapToDouble(BigDecimal::doubleValue) .min(); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/optionals/tutorial/OptionalReturningMethod.java ================================================ package com.bobocode.optionals.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.List; import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; public class OptionalReturningMethod { public static void main(String[] args) { List accounts = Accounts.generateAccountList(20); Optional luckyGuy = findLuckyGuy(accounts); printResult(luckyGuy); } /** * If in some cases a method cannot produce a retuning value, use an {@link Optional} * as a returning-value in favor of null */ private static Optional findLuckyGuy(List accounts) { int luckyIndex = ThreadLocalRandom.current().nextInt(accounts.size() * 3); if (luckyIndex < accounts.size()) { return Optional.of(accounts.get(luckyIndex)); } else { // return null; // TODO: NEVER EVER RETURN null FROM AN OPTIONAL-RETURNING METHOD return Optional.empty(); } } /** * Instead of using imperative if-else statement, use ifPresentOrElse() method */ private static void printResult(Optional optionalAccount) { optionalAccount.ifPresentOrElse(System.out::println, () -> System.out.println("No luck! Try again...")); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/optionals/tutorial/OptionalSearchByEmailExample.java ================================================ package com.bobocode.optionals.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.List; import java.util.Optional; public class OptionalSearchByEmailExample { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); Account account = getAccountByEmail(accounts, "jsushi@gmail.com"); printAccount(account); } private static Optional findAccountByEmail(List accounts, String email) { return accounts.stream() .filter(a -> a.getEmail().equals(email)) .findAny(); } private static Account getAccountByEmail(List accounts, String email) { return findAccountByEmail(accounts, email) .orElseThrow(NoSuchElementException::new); } /** * Wrong way of using Optional */ private static Account getAccountByEmailImperatively(List accounts, String email) { Optional optionalAccount = findAccountByEmail(accounts, email); if (optionalAccount.isPresent()) { return optionalAccount.get(); } else { throw new NoSuchElementException(); } } private static void printAccount(Account account) { System.out.println("This account belongs to " + account.getFirstName() + " " + account.getLastName()); } static class NoSuchElementException extends RuntimeException { } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/optionals/tutorial/OptionalStream.java ================================================ package com.bobocode.optionals.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.CreditAccount; import java.util.List; import java.util.Optional; public class OptionalStream { public static void main(String[] args) { List creditAccounts = Accounts.generateCreditAccountList(10); printExistingCreditBalancesJava8(creditAccounts); printExistingCreditBalancesJava9(creditAccounts); } private static void printExistingCreditBalancesJava8(List creditAccounts) { creditAccounts.stream() .map(CreditAccount::getCreditBalance) .filter(Optional::isPresent) .map(Optional::get) .forEach(System.out::println); } private static void printExistingCreditBalancesJava9(List creditAccounts) { creditAccounts.stream() .map(CreditAccount::getCreditBalance) .flatMap(Optional::stream) .forEach(System.out::println); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/optionals/tutorial/README.MD ================================================ # Optional tutorial This is the tutorial on `java.util.Optional` ### Pre-conditions ❗ You're supposed to be familiar with OOP, have basic knowledge of JDK, and be able to write Java code. ### Related exercises 💪 * ### See also 🔽 * [Tutorial on Lamdbas](to do) * [Tutorial on Stream API](to do) ## When writing a **method that is not able to return a value** in some cases, there are three options: * return `null` * throw an exception * return `Optinonal` Returning `null` is obliously the most **error prone** approach. If you want to be safe, you should somehow **force clients** of your method **to handle the possible lack of value**. For that purpose you can use **checked exceptoins**. Although this approach is safe it has two disatvantages: thowing exceptoin is an **expansive operatoin**, and it brings **addtional boilerplate**. The better approach is to use `Optional`, which is **safe** and **concise**. `Optional` is a **like a collection that can hold at most one value**. It provides usefull methods to **replace imperative old-style null-checks**. It also contains convenient **methods for working with Stream API**. In general, Optional API follows the similar functional-style approach as streams. ### Best practices * **prefer Optional-returning method** in case the lack of value is possible * **return Optional from methods** (e.g. getter), **do not wrap instance fields** * prefer methods like `Optional#ifPresent()` over imperative `if` statements * **never return `null`** from an Optional-returning method * always use special supplier-based method `Optional#orElseGet()` in case it is expensive to compute default value * do not wrap with optional arrays, collections, streams, and maps * **use special classes for primitive types** (e.g. `OptionalInt`) * **avoid using Optional for performance-critical cases** ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/ImperativeVsDeclarativeFiltering.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.ArrayList; import java.util.List; import static java.util.stream.Collectors.toList; public class ImperativeVsDeclarativeFiltering { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); findAllGmailAccountsImperatively(accounts); findAllGmailAccountsDeclaratively(accounts); } private static List findAllGmailAccountsImperatively(List accounts) { List gmailAccounts = new ArrayList<>(); for (Account account : accounts) { if (account.getEmail().endsWith("@gmail.com")) { gmailAccounts.add(account); } } return gmailAccounts; } private static List findAllGmailAccountsDeclaratively(List accounts) { return accounts.stream() .filter(a -> a.getEmail().endsWith("@gmail.com")) .collect(toList()); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/ImperativeVsDeclarativeMax.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.List; import static java.util.Comparator.comparing; public class ImperativeVsDeclarativeMax { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); printAccountWithMaxBalanceImperatively(accounts); printAccountWithMaxBalanceDeclaratively(accounts); } private static void printAccountWithMaxBalanceDeclaratively(List accounts) { accounts.stream() .max(comparing(Account::getBalance)) .ifPresentOrElse(System.out::println, () -> System.out.println("No accounts found!")); } private static void printAccountWithMaxBalanceImperatively(List accounts) { if (accounts.size() > 0) { Account accountWithMaxBalance = accounts.get(0); for (Account account : accounts) { if (account.getBalance().compareTo(accountWithMaxBalance.getBalance()) > 0) { accountWithMaxBalance = account; } } System.out.println(accountWithMaxBalance); } else { System.out.println("No accounts found!"); } } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/README.MD ================================================ # Stream API tutorial This is the tutorial on Stream API and functional programming techniques ### Pre-conditions ❗ You're supposed to be familiar with OOP, have basic knowledge of JDK, and be able to write Java code. ### Related exercises 💪 * [Account analytics](to do) * [File Reader](to do) * [File Stats](to do) ### See also 🔽 * [Tutorial on Lambdas](to do) * [Tutorial on Optional](to do) ## *Stream API* provide an ability to **process sequences of data elements in a declarative way** and **simplify the task of performing operations in parallel** The simplest example is the task of filtering a collection. Using **imperative** old-style approach we specify **HOW the task should be done**. E.g. dealing with iteration, and storing each element in a new `ArrayList`. This way of processing is also called *external iteration*. *Stream API* and it's **declarative** approach allows us to specify **WHAT should be done**, without actually dealing with iteration and elements. This approach is also called *internal iteration*. Imperative style: ```java List gmailAccounts = new ArrayList<>(); for (Account account : accounts) { if (account.getEmail().endsWith("@gmail.com")) { gmailAccounts.add(account); } } ``` Declarative style using *Stream API*: ```java List gmailAccounts = accounts.stream() .filter(a -> a.getEmail().endsWith("@gmail")) .collect(toList()); ``` ### Best practices * **use streams** wheneve it can make the code clear and concise * use **clear names for lambda parameters** in stream pipelines * **use helper methods** for better readability * be carefull using a stream of chars * **prefer pure functions** for stream operations, **avoid side-effect function** * always use **static import** for better readability * **always use `joining()` collector** for `CharSequence` elements to avoid performance issues * prefer collections and not stream as method return-type * use **parallel stream only when it's critical**, when you know that it helps, and **always measure it using real data** * **ALWAYS USE SPECIAL CLASSES FOR PRIMITIVES.** (E.g. `IntStream` instead of `Stream`) ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamAdditionalFeatures.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.math.BigDecimal; import java.util.DoubleSummaryStatistics; import java.util.List; import java.util.Set; import static java.util.stream.Collectors.toSet; /** * Stream API provides a lot of useful features features that help you process data even more concise. */ public class StreamAdditionalFeatures { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); printSortedFirstNames(accounts); printThirdAndFourthFirstNames(accounts); printBalanceStatistic(accounts); printFirstNamesAndCollectEmails(accounts); } private static void printSortedFirstNames(List accounts) { System.out.println("Sorted first names: "); accounts.stream() .map(Account::getFirstName) .sorted() .forEach(System.out::println); } /** * Stream API allow you to skip some elements, and limit the number of elements in the stream */ private static void printThirdAndFourthFirstNames(List accounts) { System.out.println("\nThird and fourth first names: "); accounts.stream() .map(Account::getFirstName) .sorted() .skip(2) .limit(2) .forEach(System.out::println); } private static void printBalanceStatistic(List accounts) { DoubleSummaryStatistics balanceStatistic = accounts.stream() .map(Account::getBalance) .mapToDouble(BigDecimal::doubleValue) .summaryStatistics(); System.out.println("\nAccounts balance statistic: " + balanceStatistic); } /** * Since forEach() is a terminal operation, you can not continue working with stream when it's performed. * That's why Stream API provides another method, that has the similar capabilities but does not terminate a stream */ private static void printFirstNamesAndCollectEmails(List accounts) { System.out.println("\nAccount first names: "); Set emailsSet = accounts.stream() .peek(a -> System.out.println(a.getFirstName())) .map(Account::getEmail) .collect(toSet()); System.out.println("\nEmails set: " + emailsSet); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamBasics.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.time.Month; import java.util.List; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; /** * {@link Stream} is not a data collection by itself. It's an API that allows to perform bulk operation on top of some existing * data collection in a declarative way. *

* {@link Stream} provides a lot of useful methods to work with data sequences. All intermediate operation produce a new * stream. Those operations are performed lazily. E.g. the iteration on elements and all intermediate operations * are performed only when terminal operation is called. After that stream cannot be reused. */ public class StreamBasics { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); Stream accountStream = accounts.stream(); // you can create stream directly from a collection object List augustAccounts = accountStream. filter(a -> a.getCreationDate().getMonth().equals(Month.AUGUST)) // 'filter' is an intermediate operation .collect(toList());// 'collect' is a terminal operation System.out.println("August accounts: " + augustAccounts); // accountStream.forEach(System.out::println); // stream cannot be reused. You'll get IllegalStateException } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamCollecting.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.time.Month; import java.util.List; import java.util.Map; import static java.util.stream.Collectors.*; /** * StreamCollecting stream elements with method collect() that receives a {@link java.util.stream.Collector} instance as input * parameter. Method collect() transforms a stream into a real collection like {@link List}, or a {@link Map}. * A {@link java.util.stream.Collector} is a complex structure, that describes a collecting logic. It provides * an instruction about how elements should collected, and what collection should be used. *

* The simplest example is collecting all elements into a list. However this mechanism is very powerful, and allows * to perform various complex data transformations. E.g. "Group all account by it's birthday month" */ public class StreamCollecting { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); printAllGmailAccounts(accounts); printAccountsByItsBirthdaysMonth(accounts); printAccountFirstNamesByItsBirthdaysMonth(accounts); printFirstNamesSeparatingGmailAccountsAndOthers(accounts); printCommaSeparatedNames(accounts); printNamesSeparatedGmailAccountAndOthersAndGroupedByBirthdayMonth(accounts); } private static void printAllGmailAccounts(List accounts) { List accountEmails = accounts.stream() .map(Account::getEmail) .filter(email -> email.endsWith("gmail.com")) .collect(toList()); System.out.println("\nGmail accounts list: " + accountEmails); } private static void printAccountsByItsBirthdaysMonth(List accounts) { Map> accountsByBirthdayMonthMap = accounts.stream() .collect(groupingBy(account -> account.getBirthday().getMonth())); System.out.println("\nGroup accounts by birthday month: " + accountsByBirthdayMonthMap); } private static void printAccountFirstNamesByItsBirthdaysMonth(List accounts) { Map> accountFirstNamesBirthdayMonth = accounts.stream() .collect(groupingBy(a -> a.getBirthday().getMonth(), mapping(Account::getFirstName, toList()))); System.out.println("\nGroup account first names by birthday month: " + accountFirstNamesBirthdayMonth); } private static void printFirstNamesSeparatingGmailAccountsAndOthers(List accounts) { Map> googleEmailAccounts = accounts.stream() .collect(partitioningBy(a -> a.getEmail().endsWith("gmail.com"), mapping(Account::getFirstName, toList()))); System.out.println("\nSeparate gmail accounts from others: " + googleEmailAccounts); } /** * Please always use joining() method to avoid performance issues when concatenating {@link String} */ private static void printCommaSeparatedNames(List accounts) { String concatenatedName = accounts.stream() .map(Account::getFirstName) .collect(joining(", ")); System.out.println("\nComma-separated names: " + concatenatedName); } /** * Use streams carefully. Always keep the logic simple and clear. In case it becomes too complex, think how to * simplify it use imperative style */ private static void printNamesSeparatedGmailAccountAndOthersAndGroupedByBirthdayMonth(List accounts) { Map>> accountByNameLengthByBirthdayMonth = accounts.stream() .collect(partitioningBy(a -> a.getFirstName().length() > 4, groupingBy(a -> a.getBirthday().getMonth(), mapping(Account::getFirstName, toList())))); System.out.println("\nNames of gmail account owners and others groped by birthday month: " + accountByNameLengthByBirthdayMonth); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamFiltering.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.List; import java.util.function.Predicate; public class StreamFiltering { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); printAccountsThatHaveGoogleEmail(accounts); } /** * To filter accounts we use an instance of @{@link Predicate} that checks if an email is gmail based * @param accounts */ private static void printAccountsThatHaveGoogleEmail(List accounts) { accounts.stream() .filter(a -> a.getEmail().endsWith("gmail.com")) .forEach(System.out::println); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamMapping.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.List; import java.util.Map; import java.util.stream.Stream; import static java.util.function.Function.identity; import static java.util.stream.Collectors.counting; import static java.util.stream.Collectors.groupingBy; public class StreamMapping { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); printAccountEmails(accounts); printCharacterCounts(); } /** * StreamMapping elements, method map() receives a @{@link java.util.function.Function}. This function transforms(maps) * each element of the stream into another value (transforms each account object into its email). Method map() * produce a new stream @{@link Stream} of @{@link String} (stream of emails) */ private static void printAccountEmails(List accounts) { accounts.stream() .map(Account::getEmail) .forEach(System.out::println); } private static void printCharacterCounts() { String text = getSomeText(); Map characterCountMap = collectCharactersCountFromText(text); System.out.println(characterCountMap); } /** * Count number of occurrences for each letter in each account first and last names * flatMap() is used to transform Stream> into Stream * Not you see the problem, that Java 8 doesn't provide a primitive stream API for characters */ private static Map collectCharactersCountFromText(String text) { return text.chars() .mapToObj(a -> (char) a) .filter(s -> s != ' ') .collect(groupingBy(identity(), counting())); } private static String getSomeText() { return "Stream pipeline results may be nondeterministic or incorrect if the behavioral parameters " + "to the stream operations are stateful. A stateful lambda (or other object implementing " + "the appropriate functional interface) is one whose result depends on any state " + "which might change during the execution of the stream pipeline."; } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamParallelProcessing.java ================================================ package com.bobocode.streams.tutorial; import java.util.function.LongPredicate; import java.util.stream.LongStream; public class StreamParallelProcessing { static final long STREAM_SIZE = 100_000_000; static final int N = 10; public static void main(String[] args) { LongPredicate isDivisibleBySeven = n -> n % 7 == 0; System.out.println("Sequential processing"); performNTimes(N, () -> LongStream.range(1, STREAM_SIZE) .filter(isDivisibleBySeven) .count()); System.out.println("\nParallel processing"); performNTimes(N, () -> LongStream.range(1, STREAM_SIZE) .parallel() .filter(isDivisibleBySeven) .count()); } static void performNTimes(int n, Runnable r) { LongStream.range(0, n).forEach(i -> { long start = System.nanoTime(); r.run(); System.out.println((System.nanoTime() - start) / 1_000_000 + " ms"); } ); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamPrimitives.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.List; import java.util.stream.LongStream; import java.util.stream.Stream; public class StreamPrimitives { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); printLongValues(); printMaxBalance(accounts); } /** * {@link LongStream} is a stream of primitive long values. * It can also be boxed into a {@link Stream} of {@link Long} values. PLEASE NOTE, that you should always use * primitive streams whenever it's possible to avoid performance issues */ private static void printLongValues() { LongStream longStream = LongStream.of(1, 2, 3, 4, 5); Stream boxedLongStream = longStream.boxed(); boxedLongStream.forEach(System.out::println); } /** * This method creates a {@link Stream} of {@link Account} and then maps it to stream of doubles. To transform stream * of objects like an {@link Account} into a stream of primitive doubles you should use special mapper method * mapToDouble(), which creates an instance of {@link java.util.stream.DoubleStream} */ private static void printMaxBalance(List accounts) { double maxBalance = accounts.stream() .mapToDouble(a -> a.getBalance().doubleValue()) .max().getAsDouble(); System.out.println(maxBalance); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamReducing.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.math.BigDecimal; import java.util.List; import java.util.Optional; /** * Stream reducing is performed with method reduce(), that repeatedly process stream elements to provide a single value */ public class StreamReducing { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); printTotalBalance(accounts); printMaxBalance(accounts); } private static void printTotalBalance(List accounts) { BigDecimal totalAmount = accounts.stream() .map(Account::getBalance) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total balance is $" + totalAmount); } /** * Please note that reduce() was use just an example. The better way to find max balance is to use * accounts.stream().max(comparing(Account::getBalance)) */ private static void printMaxBalance(List accounts) { Optional maxBalanceValue = accounts.stream() .map(Account::getBalance) .reduce(BigDecimal::max); maxBalanceValue.ifPresent(balance -> System.out.println("Max balance is $" + balance)); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamSideEffectFilteringExample.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.ArrayList; import java.util.List; public class StreamSideEffectFilteringExample { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); List gmailAccounts = new ArrayList<>(); accounts.stream() .filter(a -> a.getEmail().endsWith("@gmail.com")) .forEach(gmailAccounts::add); } } ================================================ FILE: 5-0-functional-programming/src/main/java/com/bobocode/streams/tutorial/StreamWhileExample.java ================================================ package com.bobocode.streams.tutorial; import com.bobocode.data.Accounts; import com.bobocode.model.Account; import java.util.List; public class StreamWhileExample { public static void main(String[] args) { List accounts = Accounts.generateAccountList(10); printAllEmails(accounts); printAllEmailsWhileGmail(accounts); printAllEmailsWhileNotGmail(accounts); } private static void printAllEmails(List accounts) { System.out.println("Whole emails list:"); accounts.stream() .map(Account::getEmail) .forEach(System.out::println); } private static void printAllEmailsWhileGmail(List accounts) { System.out.println("\nAll while gmail:"); accounts.stream() .takeWhile(a -> a.getEmail().endsWith("@gmail.com")) .map(Account::getEmail) .forEach(System.out::println); } private static void printAllEmailsWhileNotGmail(List accounts) { System.out.println("\nAll while not gmail:"); accounts.stream() .dropWhile(a -> a.getEmail().endsWith("@gmail.com")) .map(Account::getEmail) .forEach(System.out::println); } } ================================================ FILE: 6-0-test-driven-development/6-1-1-stack/README.md ================================================ # Stack exercise :muscle: Improve your TDD skill implementing Stack ### Task **Stack** is last in, first out (LIFO) collection of elements. Your job is to implement the interface `Stack` using TDD discipline ### Pre-conditions :heavy_exclamation_mark: You're supposed to know [The Three laws of TDD](https://github.com/bobocode-projects/java-fundamentals-exercises#the-three-laws-of-tdd) relink, be familiar with Stack data structure, and be able to write Java code ### How to start :question: * Just clone the repository and start implementing `Stack` interface following *three laws of TDD* * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) * Don't worry if you got stuck, checkout the [exercise/completed](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/exercise/completed) branch and see the final implementation ### Related materials :information_source: * [Як виробити звичку писати тести? (Bobocode channel )](https://youtu.be/L_CiX9C51BI) * [Stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) * [The Three Laws of TDD](https://www.youtube.com/watch?v=qkblc5WRn-U&t=3476s) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##

================================================ FILE: 6-0-test-driven-development/6-1-1-stack/pom.xml ================================================ 6-0-test-driven-development com.bobocode 1.0-SNAPSHOT 4.0.0 6-1-1-stack ================================================ FILE: 6-0-test-driven-development/6-1-1-stack/src/main/java/com/bobocode/tdd/LinkedStack.java ================================================ package com.bobocode.tdd; import com.bobocode.util.ExerciseNotCompletedException; public class LinkedStack implements Stack { @Override public void push(T element) { throw new ExerciseNotCompletedException(); // todo } @Override public T pop() { throw new ExerciseNotCompletedException(); // todo } @Override public int size() { throw new ExerciseNotCompletedException(); // todo } @Override public boolean isEmpty() { throw new ExerciseNotCompletedException(); // todo } } ================================================ FILE: 6-0-test-driven-development/6-1-1-stack/src/main/java/com/bobocode/tdd/Stack.java ================================================ package com.bobocode.tdd; /** * * Stack is a data structure that follows "last in, first out" rule (LIFO). */ public interface Stack { /** * Adds an element to the begining of the stack. * * @param element the element to add */ void push(T element); /** * Retrieves and removes stack head. * * @return an element that was retrieved from the head or null if stack is empty */ T pop(); /** * Returns a size of the stack. * * @return an integer value that is a size of stack */ int size(); /** * Checks if the stack is empty. * * @return {@code true} if the stack is empty, returns {@code false} if it's not */ boolean isEmpty(); } ================================================ FILE: 6-0-test-driven-development/6-1-1-stack/src/test/java/com/bobocode/tdd/StackTest.java ================================================ package com.bobocode.tdd; public class StackTest { } ================================================ FILE: 6-0-test-driven-development/6-1-2-linked-list/README.MD ================================================ # Linked List exercise 💪 Improve your TDD skills implementing LinkedList ### Task `List` is an API that represents a well-known data structure. Your job is to implement the *todo* section of the class `LinkedList` **following TDD rules**. Please note, that your implementation should be based on **singly liked nodes.** It means that you should create your own class `Node` that will hold list elements. To verify your implementation check if your `LinkedListTest.java` provides full **(100%) code coverage** and compare it with **completed exercise** ### Pre-conditions❗ You're supposed to know [The Three laws of TDD](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/6-0-test-driven-development#the-three-laws-of-tdd), be familiar with Linked List data structure, and be able to write Java code ### How to start❓ * Just clone the repository and create a branch **exercise/your_username** if you want your code to be reviewed * Start implementing the **todo** section and verify your changes by running tests * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) * Don't worry if you got stuck, checkout the **exercise/completed** branch and see the final implementation ### Related materials ℹ * [Linked Lists]() todo: add link on LinkedList materials * [Як виробити звичку писати тести? (Bobocode channel )](https://youtu.be/L_CiX9C51BI) * [The Three Laws of TDD](https://www.youtube.com/watch?v=qkblc5WRn-U&t=3476s) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##
================================================ FILE: 6-0-test-driven-development/6-1-2-linked-list/pom.xml ================================================ 6-0-test-driven-development com.bobocode 1.0-SNAPSHOT 4.0.0 6-1-2-linked-list ================================================ FILE: 6-0-test-driven-development/6-1-2-linked-list/src/main/java/com/bobocode/tdd/LinkedList.java ================================================ package com.bobocode.tdd; import com.bobocode.util.ExerciseNotCompletedException; /** * {@link LinkedList} is a list implementation that is based on singly linked generic nodes. A node is implemented as * inner static class {@link Node}. * * @param generic type parameter */ public class LinkedList implements List { /** * This method creates a list of provided elements * * @param elements elements to add * @param generic type * @return a new list of elements the were passed as method parameters */ public static List of(T... elements) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Adds an element to the end of the list. * * @param element element to add */ @Override public void add(T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Adds a new element to the specific position in the list. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index an index of new element * @param element element to add */ @Override public void add(int index, T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Changes the value of an list element at specific position. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index an position of element to change * @param element a new element value */ @Override public void set(int index, T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Retrieves an elements by its position index. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index element index * @return an element value */ @Override public T get(int index) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns the first element of the list. Operation is performed in constant time O(1) * * @return the first element of the list * @throws java.util.NoSuchElementException if list is empty */ @Override public T getFirst() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns the last element of the list. Operation is performed in constant time O(1) * * @return the last element of the list * @throws java.util.NoSuchElementException if list is empty */ @Override public T getLast() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Removes an elements by its position index. In case provided index in out of the list bounds it * throws {@link IndexOutOfBoundsException} * * @param index element index * @return deleted element */ @Override public T remove(int index) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Checks if a specific exists in he list * * @return {@code true} if element exist, {@code false} otherwise */ @Override public boolean contains(T element) { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Checks if a list is empty * * @return {@code true} if list is empty, {@code false} otherwise */ @Override public boolean isEmpty() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Returns the number of elements in the list * * @return number of elements */ @Override public int size() { throw new ExerciseNotCompletedException(); // todo: implement this method } /** * Removes all list elements */ @Override public void clear() { throw new ExerciseNotCompletedException(); // todo: implement this method } } ================================================ FILE: 6-0-test-driven-development/6-1-2-linked-list/src/main/java/com/bobocode/tdd/List.java ================================================ package com.bobocode.tdd; public interface List { void add(T element); void add(int index, T element); void set(int index, T element); T get(int index); T getFirst(); T getLast(); T remove(int index); boolean contains(T element); boolean isEmpty(); int size(); void clear(); } ================================================ FILE: 6-0-test-driven-development/6-1-2-linked-list/src/test/java/com/bobocode/tdd/LinkedListTest.java ================================================ package com.bobocode.tdd; public class LinkedListTest { } ================================================ FILE: 6-0-test-driven-development/6-1-3-binary-search-tree/README.md ================================================ # Binary Search Tree exercise :muscle: Improve your TDD skill implementing Binary Search Tree ### Task **Binary Search Tree (BST)** is an ordered (sorted) data structure. Your job is to implement the interface `BinarySearchTree` by practicing TDD discipline ### Pre-conditions :heavy_exclamation_mark: You're supposed to know [The Three laws of TDD](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/6-0-test-driven-development#the-three-laws-of-tdd), be familiar with [Binary Search Tree](https://en.wikipedia.org/wiki/Binary_search_tree) data structure, and understand [Recursion](https://en.wikipedia.org/wiki/Recursion_(computer_science)) ### How to start :question: * Just clone the repository and start implementing `BinarySearchTree` interface following *three laws of TDD* * If you don't have enough knowledge about this domain, check out the [links below](#related-materials-information_source) * Don't worry if you got stuck, checkout branch **completed** and see the final implementation ### Related materials :information_source: * [Як виробити звичку писати тести? ](https://youtu.be/L_CiX9C51BI) * [Binary Search Tree](https://en.wikipedia.org/wiki/Binary_search_tree) * [The Three Laws of TDD](https://www.youtube.com/watch?v=qkblc5WRn-U&t=3476s) --- #### 🆕 First time here? – [See Introduction](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) ##
================================================ FILE: 6-0-test-driven-development/6-1-3-binary-search-tree/pom.xml ================================================ 6-0-test-driven-development com.bobocode 1.0-SNAPSHOT 4.0.0 6-1-3-binary-search-tree ================================================ FILE: 6-0-test-driven-development/6-1-3-binary-search-tree/src/main/java/com/bobocode/tdd/BinarySearchTree.java ================================================ package com.bobocode.tdd; import java.util.function.Consumer; public interface BinarySearchTree { /** * insert an element * @return true if element did not exist in the tree and was inserted successfully */ boolean insert(T element); /** * @return true if tree contains element */ boolean contains(T element); /** * @return number of elements in the tree */ int size(); /** * @return max. number of transition between root node and any other node; 0 - if tree is empty or contains 1 element */ int depth(); /** * traverse the tree in element's natural order * @param consumer accepts ref. to node during traversing */ void inOrderTraversal(Consumer consumer); } ================================================ FILE: 6-0-test-driven-development/6-1-3-binary-search-tree/src/main/java/com/bobocode/tdd/RecursiveBinarySearchTree.java ================================================ package com.bobocode.tdd; import com.bobocode.util.ExerciseNotCompletedException; import java.util.function.Consumer; public class RecursiveBinarySearchTree implements BinarySearchTree { public static RecursiveBinarySearchTree of(T... elements) { throw new ExerciseNotCompletedException(); } @Override public boolean insert(T element) { throw new ExerciseNotCompletedException(); } @Override public boolean contains(T element) { throw new ExerciseNotCompletedException(); } @Override public int size() { throw new ExerciseNotCompletedException(); } @Override public int depth() { throw new ExerciseNotCompletedException(); } @Override public void inOrderTraversal(Consumer consumer) { throw new ExerciseNotCompletedException(); } } ================================================ FILE: 6-0-test-driven-development/6-1-3-binary-search-tree/src/test/java/com/bobocode/tdd/BinarySearchTreeTest.java ================================================ package com.bobocode.tdd; class BinarySearchTreeTest { } ================================================ FILE: 6-0-test-driven-development/README.md ================================================ # Test-Driven Development Build strong TDD skills needed for enterprise Java development 💪 ## ### The three laws of TDD 1. You should not write production code until you have written a failing unit test 2. You should not write more of a unit test than is sufficient to fail 3. You should not write more production code than is sufficient to pass the failing test ================================================ FILE: 6-0-test-driven-development/pom.xml ================================================ java-fundamentals-exercises com.bobocode 1.0-SNAPSHOT 4.0.0 6-0-test-driven-development pom 6-1-1-stack 6-1-2-linked-list 6-1-3-binary-search-tree com.bobocode java-fundamentals-util 1.0-SNAPSHOT compile ================================================ FILE: CONTRIBUTING.MD ================================================ # Contributing to Java Fundamentals Course Thank you for taking the time to contribute to the open-source education! 👏 Before moving forward, please **make sure that you understand what is an exercise and how this repository is organized.** The best place to learn about that is [Introduction module](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) 🤓 ## Branching strategy In this repository we maintain **two key branches** [main](https://github.com/bobocode-projects/java-fundamentals-exercises) and [completed](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/completed). Since the `main` branch stores the exercises, and the `completed` stores the completed solution, the latter is always ahead of the first one. Here's the rule: #### The [Completed Solution PR](https://github.com/bobocode-projects/java-fundamentals-exercises/pull/33) should always show only those changes that participats implement in the scope of the exercises❗️ It means two things: * any changes that are related to the exercise task, the tests, or the project configuration should be added first to the `main` branch and then merged from `main` to `completed` * only changes related to the completed solution should be added to the `completed` branch ## Creating a Pull Request ### PR with a small fix If case you found a mistake and want to provide a fix, you can easily create a PR with corresponding changes. Please make sure that you have read [the branching strategy](#branching-strategy) so your PR targets to the right place. ### Double pull request In case you need to **change both the exercise and the completed solution**, you will need to create **two pull requests.** In order to follow [the branching strategy](#branching-strategy), you will need to do some **extra work with branches** ⚠️. Imagine you need to add a new `methodX()` to the exercise. Here's the list of actions you will need to do: 1. Create a local branch from `main` (let's call it `exercise branch`) 2. Add `methodX()` that throws `ExerciseNotCompletedException`, and a corresponding `testMethodX()` 3. Commit these changes to the `exercise branch` 4. Push the changes 5. Checkout `completed` 6. Create a local branch from `completed` (let's call it `solution branch`) 7. Merge your `exercise branch` into the `solution branch` 8. Implement `methodX()` and make sure that tests pass 9. Commit these changes to the `solution branch` 10. Push the changes **If everything is fine, then you can create two pull requests:** 11. Create a pull request from the `exercise branch` to the `main` 12. Create a pull request from the `solution branch` to the `completed` ### Branching naming convention A branch name should start from the ticket or issue number if one is available, following the short changes summary. Like this: * ```101-insane-lambdas-exercise``` In case the branch targets `completed` it should have a corresponding suffix. Like this: * ```101-insane-lambdas-exercise-completed``` ### Commits * In case you are working on the JIRA ticket, or the GitHub issue, please make sure that you always add its full number to every commit message * Please provide a descriptive messages * Use squash and rebase to avoid redundant commits ### Authors * if you created a new exercise, please specify your name in the javadoc as @author * if you did a substantial change in the class or added new tests, please add you name to the list of authors ## Submitting an issue In case you have faced **an issue that you couldn't resolve** and/or you have **a good idea how this repository can be improved**, feel free to [submit an issue](https://github.com/bobocode-projects/java-fundamentals-exercises/issues/new). Please note that **if you just have a question, you can ask it in one of the three different ways** depending on the quesiton: * add you comment to the [Completed PR](https://github.com/bobocode-projects/java-fundamentals-exercises/pull/33#issue-790887000) – if you want to ask about the completed solution * [start a new discussion](https://github.com/bobocode-projects/java-fundamentals-exercises/discussions/new) under Q&A – if you have question related to this repo * contact us via info@bobocode.com – if you have other questions ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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 http://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. ================================================ FILE: README.md ================================================ # Welcome to the Java Fundamentals Exercises Build strong fundamental skills that you will need for real-world Enterprise Java development ## Why Most people don’t know how to learn Enterprise Java efficiently. So we create an **Open-source Java Education** that helps them to **master strong skills**, learn **world best practices** and build a **successful career**. 🚀 At Bobocode we have extensive experience in both building Enterprise Java applications and organizing efficient learning. Therefore, this course covers what you need in the most efficient way. We believe that **the key to efficient learning is practice**. 💪 And as a software engineer, you should **spend as much time as you can in the IDE writing code**. At the end of the day, this is the only place where you build software... 💻 ## About this repo Java Standard Edition is huge. Computer Science is huge. Object-Oriented programming, as well as Functional programming, are also pretty big topics. So how can you learn everything you need and **don't get stuck for years learning fundamentals only?** 🤔 You're in the right place to find the answer. 😀 This repo gives you two uniques features that will help you to master fundamentals ASAP: 1. It consists of **selected topics that are must-have** for real-world enterprise Java development ⭐️ 2. It is fully based on **special training exercises** that put your practice on rails and **boost up your learning efficiency** 🚀 Go ahead and check out [Introduction module](https://github.com/bobocode-projects/java-fundamentals-exercises/tree/main/0-0-intro#introduction) 👍 ================================================ FILE: java-fundamentals-util/pom.xml ================================================ java-fundamentals-exercises com.bobocode 1.0-SNAPSHOT 4.0.0 java-fundamentals-util com.devskiller jfairy 0.6.5 ================================================ FILE: java-fundamentals-util/src/main/java/com/bobocode/data/Accounts.java ================================================ package com.bobocode.data; import com.bobocode.model.Account; import com.bobocode.model.CreditAccount; import com.bobocode.model.Sex; import com.devskiller.jfairy.Fairy; import com.devskiller.jfairy.producer.person.Person; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; public class Accounts { public static int MAX_BALANCE_VALUE = 200_000; public static Account generateAccount() { Person person = generatePerson(); Account account = convertToAccount(person); fillCommonRandomFields(account); return account; } public static CreditAccount generateCreditAccount() { Person person = generatePerson(); CreditAccount account = convertToCreditAccount(person); fillCommonRandomFields(account); account.setCreditBalance(randomBigDecimal(MAX_BALANCE_VALUE)); return account; } public static List generateAccountList(int size) { return Stream.generate(Accounts::generateAccount) .limit(size) .collect(toList()); } public static List generateCreditAccountList(int size) { return Stream.generate(Accounts::generateCreditAccount) .limit(size) .collect(toList()); } private static Person generatePerson() { Fairy fairy = Fairy.create(); return fairy.person(); } private static Account convertToAccount(Person person) { Account account = new Account(); fillAccount(account, person); return account; } private static CreditAccount convertToCreditAccount(Person person) { CreditAccount account = new CreditAccount(); fillAccount(account, person); return account; } private static void fillAccount(Account account, Person person) { account.setFirstName(person.getFirstName()); account.setLastName(person.getLastName()); account.setEmail(person.getEmail()); account.setBirthday(LocalDate.of( person.getDateOfBirth().getYear(), person.getDateOfBirth().getMonth(), person.getDateOfBirth().getDayOfMonth())); account.setSex(Sex.valueOf(person.getSex().name())); } private static void fillCommonRandomFields(Account account) { BigDecimal balance = randomBigDecimal(MAX_BALANCE_VALUE); account.setBalance(balance); account.setCreationDate(LocalDate.now()); } private static BigDecimal randomBigDecimal(int max) { return BigDecimal.valueOf(ThreadLocalRandom.current().nextInt(max)); } } ================================================ FILE: java-fundamentals-util/src/main/java/com/bobocode/model/Account.java ================================================ package com.bobocode.model; import lombok.*; import java.math.BigDecimal; import java.time.LocalDate; @NoArgsConstructor @AllArgsConstructor(access = AccessLevel.PUBLIC) @Getter @Setter @ToString @EqualsAndHashCode(of = "email") public class Account { private Long id; private String firstName; private String lastName; private String email; private LocalDate birthday; private Sex sex; private LocalDate creationDate; private BigDecimal balance = BigDecimal.ZERO; } ================================================ FILE: java-fundamentals-util/src/main/java/com/bobocode/model/CreditAccount.java ================================================ package com.bobocode.model; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import lombok.Setter; import java.math.BigDecimal; import java.util.Optional; @NoArgsConstructor @AllArgsConstructor(access = AccessLevel.PUBLIC) @Setter public class CreditAccount extends Account { private BigDecimal creditBalance; public Optional getCreditBalance() { return Optional.ofNullable(creditBalance); } } ================================================ FILE: java-fundamentals-util/src/main/java/com/bobocode/model/Sex.java ================================================ package com.bobocode.model; public enum Sex { MALE, FEMALE } ================================================ FILE: java-fundamentals-util/src/main/java/com/bobocode/util/ExerciseNotCompletedException.java ================================================ package com.bobocode.util; /** * This is a custom exception that we throw in every method which should be implemented as a part of the exercise. * If you see that it was thrown it means that you did not implement all required methods yet. */ public class ExerciseNotCompletedException extends RuntimeException { public ExerciseNotCompletedException() { super("Implement this method and remove exception OR switch to branch completed if you got stuck."); } } ================================================ FILE: lesson-demo/pom.xml ================================================ java-fundamentals-exercises com.bobocode 1.0-SNAPSHOT 4.0.0 lesson-demo com.bobocode java-fundamentals-util 1.0-SNAPSHOT ================================================ FILE: lesson-demo/src/main/java/com/bobocode/DemoApp.java ================================================ package com.bobocode; public class DemoApp { public static void main(String[] args) { } } ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you 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 # # http://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. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Maven Start Up Batch script # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ] ; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; mingw=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then export JAVA_HOME="`/usr/libexec/java_home`" else export JAVA_HOME="/Library/Java/Home" fi fi ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi if [ -z "$M2_HOME" ] ; then ## resolve links - $0 may be a link to maven's home PRG="$0" # need this for relative symlinks while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG="`dirname "$PRG"`/$link" fi done saveddir=`pwd` M2_HOME=`dirname "$PRG"`/.. # make it fully qualified M2_HOME=`cd "$M2_HOME" && pwd` cd "$saveddir" # echo Using m2 at $M2_HOME fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=`which readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if $darwin ; then javaHome="`dirname \"$javaExecutable\"`" javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" else javaExecutable="`readlink -f \"$javaExecutable\"`" fi javaHome="`dirname \"$javaExecutable\"`" javaHome=`expr "$javaHome" : '\(.*\)/bin'` JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ] ; then 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 else JAVACMD="`which java`" fi fi if [ ! -x "$JAVACMD" ] ; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=`cd "$wdir/.."; pwd` fi # end of workaround done echo "${basedir}" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")" fi } BASE_DIR=`find_maven_basedir "$(pwd)"` if [ -z "$BASE_DIR" ]; then exit 1; fi ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found .mvn/wrapper/maven-wrapper.jar" fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" else jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" fi while IFS="=" read key value; do case "$key" in (wrapperUrl) jarUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then echo "Downloading from: $jarUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if $cygwin; then wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` fi if command -v wget > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget "$jarUrl" -O "$wrapperJarPath" else wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" fi elif command -v curl > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl -o "$wrapperJarPath" "$jarUrl" -f else curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaClass=`cygpath --path --windows "$javaClass"` fi if [ -e "$javaClass" ]; then if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class ("$JAVA_HOME/bin/javac" "$javaClass") fi if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." fi ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") fi fi fi fi ########################################################################################## # End of extension ########################################################################################## export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$M2_HOME" ] && M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"` [ -n "$MAVEN_PROJECTBASEDIR" ] && MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %DOWNLOAD_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%" == "on" pause if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 com.bobocode java-fundamentals-exercises 1.0-SNAPSHOT pom 21 21 UTF-8 0-0-intro 1-0-java-basics 2-0-data-structures-and-algorithms 3-0-java-core 4-0-object-oriented-programming 5-0-functional-programming 6-0-test-driven-development java-fundamentals-util lesson-demo org.junit.jupiter junit-jupiter-engine 5.10.0 test org.junit.jupiter junit-jupiter-params 5.10.0 test org.assertj assertj-core 3.24.2 test org.projectlombok lombok 1.18.30 com.google.code.findbugs jsr305 3.0.2 org.mockito mockito-core 5.6.0 test net.bytebuddy byte-buddy net.bytebuddy byte-buddy-agent net.bytebuddy byte-buddy 1.14.9 net.bytebuddy byte-buddy-agent 1.14.9 org.mockito mockito-inline 5.2.0 test org.slf4j slf4j-simple 2.0.9 org.apache.maven.plugins maven-compiler-plugin 3.11.0 -parameters org.apache.maven.plugins maven-surefire-plugin 3.1.2