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 🇺🇦
[](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 super %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 super %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 extends %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 extends %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 super %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 extends BaseEntity> 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 extends BaseEntity> 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 🇺🇦
[](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 🇺🇦
[](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 extends Annotation> 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 🇺🇦
[](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 🇺🇦
[](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 🇺🇦
[](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 🇺🇦
[](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
================================================