diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index ecfa399..98acc8b 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -6,11 +6,15 @@
+
+
+
+
diff --git a/.idea/junitgenerator-prj-settings.xml b/.idea/junitgenerator-prj-settings.xml
new file mode 100644
index 0000000..d73e792
--- /dev/null
+++ b/.idea/junitgenerator-prj-settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modern-java/pom.xml b/modern-java/pom.xml
new file mode 100644
index 0000000..6b569dd
--- /dev/null
+++ b/modern-java/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+
+ modern-java
+ jar
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 9
+ 9
+
+
+
+
+
+
+ fpgym
+ org.gym.fp
+ 1.0
+
+
+
+ 9
+ 9
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+ junit
+ junit
+ test
+
+
+
+
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/ConcurrencyTest.java b/modern-java/src/main/java/org/gym/fp/moderjava/ConcurrencyTest.java
new file mode 100644
index 0000000..4f90cbd
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/ConcurrencyTest.java
@@ -0,0 +1,60 @@
+package org.gym.fp.moderjava;
+
+import org.gym.fp.moderjava.concurrent.Shop;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.*;
+
+import static java.lang.System.out;
+
+public class ConcurrencyTest {
+
+ public static void main(String[] args) throws Exception {
+ doExecutorsServiceTest();
+ doFutureTest();
+ }
+
+ private static void doExecutorsServiceTest() throws Exception {
+ int x = 1337;
+ ExecutorService executorService = Executors.newFixedThreadPool(2);
+ Future y = executorService.submit(() -> f(x));
+ Future z = executorService.submit(() -> g(x));
+ out.println("Result y + z = " + (y.get() + z.get()));
+ executorService.shutdown();
+ }
+
+ private static void doFutureTest() throws Exception {
+ Collection shops = getShops();
+ Future future = CompletableFuture.supplyAsync(() -> {
+ out.println("Primo completable future");
+ return 10;
+ }).thenCombine(CompletableFuture.supplyAsync(() -> {
+ out.println("Secondo completable future");
+ return 30;
+ }), (first, second) -> first + second);
+ out.println(future.get(1, TimeUnit.NANOSECONDS));
+ }
+
+ static int f(int x) {
+ return 42;
+ }
+
+ static int g(int x) {
+ return x;
+ }
+
+ static Collection getShops() {
+ return Arrays.asList(
+ new Shop("BestShop1"),
+ new Shop("BestShop2"),
+ new Shop("BestShop3"),
+ new Shop("BestShop4"),
+ new Shop("BestShop5"),
+ new Shop("BestShop6"),
+ new Shop("BestShop7"),
+ new Shop("BestShop8")
+ );
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/DslTest.java b/modern-java/src/main/java/org/gym/fp/moderjava/DslTest.java
new file mode 100644
index 0000000..70c7959
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/DslTest.java
@@ -0,0 +1,141 @@
+package org.gym.fp.moderjava;
+
+import org.gym.fp.moderjava.dsl.*;
+
+import static java.lang.System.out;
+import static org.gym.fp.moderjava.dsl.LambdaOrderBuilder.order;
+import static org.gym.fp.moderjava.dsl.MethodChainingOrderBuilder.forCustomer;
+import static org.gym.fp.moderjava.dsl.NestedFunctionOrderBuilder.at;
+import static org.gym.fp.moderjava.dsl.NestedFunctionOrderBuilder.buy;
+import static org.gym.fp.moderjava.dsl.NestedFunctionOrderBuilder.on;
+import static org.gym.fp.moderjava.dsl.NestedFunctionOrderBuilder.order;
+import static org.gym.fp.moderjava.dsl.NestedFunctionOrderBuilder.sell;
+import static org.gym.fp.moderjava.dsl.NestedFunctionOrderBuilder.stock;
+
+public class DslTest {
+
+ public static void main(String[] args) {
+ doUnacceptableDslTest();
+ doMethodChainingPatternTest();
+ doNestedFunctionsPatternTest();
+ doLambdaSequencingPatternTest();
+ doMixedPatternTest();
+ doUsingMethodReferenceDslTest();
+ }
+
+ private static void doUnacceptableDslTest() {
+ Order order = new Order();
+ order.setCustomer("BigBank");
+
+ Trade trade1 = new Trade();
+ trade1.setType(Trade.Type.BUY);
+
+ Stock stock1 = new Stock();
+ stock1.setSymbol("IBM");
+ stock1.setMarket("NYSE");
+
+ trade1.setStock(stock1);
+ trade1.setPrice(125.00);
+ trade1.setQuantity(80);
+ order.addTrade(trade1);
+
+ Trade trade2 = new Trade();
+ trade2.setType(Trade.Type.BUY);
+
+ Stock stock2 = new Stock();
+ stock2.setSymbol("GOOGLE");
+ stock2.setMarket("NASDAQ");
+
+ trade2.setStock(stock2);
+ trade2.setPrice(375.00);
+ trade2.setQuantity(50);
+ order.addTrade(trade2);
+ }
+
+ private static void doMethodChainingPatternTest() {
+ Order order = forCustomer("BigBank")
+ .buy(80)
+ .stock("IBM")
+ .on("NYSE")
+ .at(125.00)
+ .sell(50)
+ .stock("Google")
+ .on("NASDAQ")
+ .at(375.00)
+ .end();
+ out.println("Order: " + order.toString());
+ out.println("----------------------------------------");
+ }
+
+ private static void doNestedFunctionsPatternTest() {
+ Order order = order("BigBank",
+ buy(80,
+ stock("IBM", on("NYSE")),
+ at(125.00)),
+ sell(50,
+ stock("Google", on("NASDAQ")),
+ at(375.00))
+ );
+ out.println("Order: " + order.toString());
+ out.println("----------------------------------------");
+ }
+
+ private static void doLambdaSequencingPatternTest() {
+ Order order = order(o -> {
+ o.forCustomer("BigBank");
+ o.buy(t -> {
+ t.quantity(80);
+ t.price(125.00);
+ t.stock(s -> {
+ s.symbol("IBM");
+ s.market("NYSE");
+ });
+ });
+ o.sell(t -> {
+ t.quantity(50);
+ t.price(375.00);
+ t.stock(s -> {
+ s.symbol("GOOGLE");
+ s.market("NASDAQ");
+ });
+ });
+ });
+ out.println("Order: " + order.toString());
+ out.println("----------------------------------------");
+ }
+
+ private static void doMixedPatternTest() {
+ Order order = MixedBuilder.forCustomer("BigBank",
+ MixedBuilder.buy(t -> t.quantity(80)
+ .stock("IBM")
+ .on("NYSE")
+ .at(125.00)),
+ MixedBuilder.sell(t -> t.quantity(50)
+ .stock("Google")
+ .on("NASDAQ")
+ .at(375.00))
+ );
+ out.println("Order: " + order.toString());
+ out.println("----------------------------------------");
+ }
+
+ private static void doUsingMethodReferenceDslTest() {
+ Order order = MixedBuilder.forCustomer("BigBank",
+ MixedBuilder.buy(t -> t.quantity(80)
+ .stock("IBM")
+ .on("NYSE")
+ .at(125.00)),
+ MixedBuilder.sell(t -> t.quantity(50)
+ .stock("Google")
+ .on("NASDAQ")
+ .at(375.00))
+ );
+ double value = new TaxCalculator()
+ .with(Tax::regional)
+ .with(Tax::surcharge)
+ .calculate(order);
+ out.printf("Calculate value order with taxes: %.2f\n", value);
+ out.println("----------------------------------------");
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/OptionalTest.java b/modern-java/src/main/java/org/gym/fp/moderjava/OptionalTest.java
new file mode 100644
index 0000000..9557bb7
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/OptionalTest.java
@@ -0,0 +1,48 @@
+package org.gym.fp.moderjava;
+
+import org.gym.fp.moderjava.optional.Car;
+import org.gym.fp.moderjava.optional.Insurance;
+import org.gym.fp.moderjava.optional.Person;
+
+import java.util.Optional;
+
+import static java.lang.System.out;
+
+public class OptionalTest {
+
+ public static void main(String[] args) {
+ Insurance myInsurance1 = new Insurance("Allianz");
+ Car myCar1 = new Car(Optional.of(myInsurance1));
+ Person person1 = new Person(Optional.of(myCar1));
+ out.println("My insurance's name is " + getCarInsuranceName(Optional.ofNullable(person1)));
+ out.println("----------------------------------------");
+ Car myCar2 = new Car(Optional.ofNullable(null));
+ Person person2 = new Person(Optional.ofNullable(myCar2));
+ out.println("My insurance's name is " + getCarInsuranceName(Optional.ofNullable(person2)));
+ out.println("----------------------------------------");
+
+ out.println("----------------------------------------");
+ }
+
+ static String getCarInsuranceName(Optional maybePerson) {
+ return maybePerson
+ .flatMap(Person::getCar)
+ .flatMap(Car::getInsurance)
+ .map(Insurance::getName)
+ .orElse("Unknown");
+ }
+
+ static Insurance findCheapestInsurance(Person person, Car car) {
+ Insurance cheapestInsurance = new Insurance("Allianz");
+ return cheapestInsurance;
+ }
+
+ static Optional nullSafeFindCheapestInsurance(Optional person, Optional car) {
+ return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
+// if (person.isPresent() && car.isPresent()) {
+// return Optional.of(findCheapestInsurance(person.get(), car.get()));
+// }
+// return Optional.empty();
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/StreamTest.java b/modern-java/src/main/java/org/gym/fp/moderjava/StreamTest.java
new file mode 100644
index 0000000..9bd4fd6
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/StreamTest.java
@@ -0,0 +1,371 @@
+package org.gym.fp.moderjava;
+
+import org.gym.fp.moderjava.stream.*;
+
+import java.util.*;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import static java.lang.System.out;
+import static java.util.Arrays.asList;
+import static java.util.Comparator.comparingInt;
+import static java.util.stream.Collectors.*;
+
+public class StreamTest {
+ private static final String SENTENCE =
+ " Nel mezzo del cammin di nostra vita " +
+ "mi ritrovia in una selva oscura" +
+ " ché la dritta via era smarrita ";
+
+ public static void main(String[] args) {
+ doStreamFilterDemo();
+ doStreamMappingDemo();
+ doStreamFindOrMatchingDemo();
+ doStreamReducingDemo();
+ doStreamCollectingDemo();
+ doStreamParallelDemo();
+ }
+
+ private static void doStreamFilterDemo() {
+ out.println("FILTER EXAMPLE");
+ getDishes().stream()
+ .filter(Dish::isVegetarian)
+ .collect(toList())
+ .forEach(out::println);
+ out.println("----------------------------------------");
+ out.println("DISTINCT EXAMPLE");
+ getDishes().stream()
+ .filter(dish -> dish.getName().length() > 4)
+ .distinct()
+ .forEach(out::println);
+ out.println("----------------------------------------");
+ out.println("TAKEWHILE EXAMPLE");
+ getDishes().stream()
+ .takeWhile(dish -> dish.getCalories() > 40)
+ .forEach(out::println);
+ out.println("----------------------------------------");
+ out.println("DROPWHILE EXAMPLE");
+ getDishes().stream()
+ .dropWhile(dish -> dish.getCalories() > 40)
+ .forEach(out::println);
+ out.println("----------------------------------------");
+ }
+
+ private static void doStreamMappingDemo() {
+ List words = asList("Hello", "World");
+ List uniqueCharacters = words.stream()
+ .map(word -> word.split(""))
+ .flatMap(Arrays::stream)
+ .distinct()
+ .collect(toList());
+ out.println(uniqueCharacters);
+ out.println("----------------------------------------");
+ out.println("Permutations");
+ List numbers1 = asList(1, 2, 3);
+ List numbers2 = asList(3, 4);
+ List pairs1 = numbers1.stream()
+ .flatMap(i -> numbers2.stream().map(j -> new int[]{i, j}))
+ .collect(toList());
+ pairs1.forEach(pair -> out.println(Arrays.toString(pair)));
+ out.println("----------------------------------------");
+ List pairs2 = numbers1.stream()
+ .flatMap(i -> numbers2.stream().map(j -> new int[]{i, j}))
+ .filter(pair -> (pair[0] + pair[1]) % 3 == 0)
+ .collect(toList());
+ pairs2.forEach(pair -> out.println(Arrays.toString(pair)));
+ out.println("----------------------------------------");
+ }
+
+ private static void doStreamFindOrMatchingDemo() {
+ out.println("ANYMATCH EXAMPLE");
+ if (getDishes().stream().anyMatch(Dish::isVegetarian)) {
+ out.println("The menu is (somewhat) vegetarian friendly!!!");
+ }
+ out.println("----------------------------------------");
+ out.println("ALLMATCH EXAMPLE");
+ if (getDishes().stream().allMatch(dish -> dish.getCalories() < 1000)) {
+ out.println("This menù is healthy");
+ }
+ out.println("----------------------------------------");
+ out.println("NONEMATCH EXAMPLE");
+ if (getDishes().stream().noneMatch(dish -> dish.getCalories() >= 1000)) {
+ out.println("This menù is healthy");
+ }
+ out.println("----------------------------------------");
+ out.println("FINDANY EXAMPLE");
+ Optional dish = getDishes().stream()
+ .filter(Dish::isVegetarian)
+ .findAny();
+ dish.ifPresent(out::println);
+ out.println("----------------------------------------");
+ out.println("FINDFIRST EXAMPLE");
+ List someNumbers = asList(1, 2, 3, 4, 5);
+ someNumbers.stream()
+ .map(n -> n * n)
+ .filter(n -> n % 3 == 0)
+ .findFirst()
+ .ifPresent(out::println);
+ out.println("----------------------------------------");
+ }
+
+ private static void doStreamReducingDemo() {
+ out.println("REDUCING");
+ int sum = Stream.of(4, 5, 3, 9).reduce(0, Integer::sum);
+ out.println(sum);
+ out.println("----------------------------------------");
+ out.println("MAX & MIN");
+ Stream.of(4, 5, 3, 9).reduce(Integer::max).ifPresent(out::println);
+ Stream.of(4, 5, 3, 9).reduce(Integer::min).ifPresent(out::println);
+ out.println("----------------------------------------");
+ out.println("COUNT DISHES");
+ getDishes().stream()
+ .map(d -> 1)
+ .reduce(Integer::sum)
+ .ifPresent(out::println);
+ out.println("----------------------------------------");
+ out.println("PUTTING ALL IN PRACTICE");
+ Trader raoul = new Trader("Raoul", "Cambridge");
+ Trader mario = new Trader("Mario", "Milan");
+ Trader alan = new Trader("Alan", "Cambridge");
+ Trader brian = new Trader("Brian", "Cambridge");
+ List transactions = asList(
+ new Transaction(brian, 2011, 300),
+ new Transaction(raoul, 2012, 1000),
+ new Transaction(raoul, 2011, 400),
+ new Transaction(mario, 2012, 710),
+ new Transaction(mario, 2012, 700),
+ new Transaction(alan, 2012, 950)
+ );
+ out.println("1)");
+ transactions.stream()
+ .filter(t -> t.getYear() == 2011)
+ .sorted(Comparator.comparing(Transaction::getValue))
+ .collect(toList())
+ .forEach(out::println);
+ out.println("2)");
+ transactions.stream()
+ .map(Transaction::getTrader)
+ .map(Trader::getCity)
+ .distinct()
+ .collect(toList())
+ .forEach(out::println);
+ out.println("3)");
+ transactions.stream()
+ .map(Transaction::getTrader)
+ .filter(trader -> "Cambridge".equalsIgnoreCase(trader.getCity()))
+ .distinct() // anche se sul testo non è specificato che dovevano essere diversi
+ .sorted(Comparator.comparing(Trader::getName))
+ .collect(toList())
+ .forEach(out::println);
+ out.println("4)");
+ String result = transactions.stream()
+ .map(Transaction::getTrader)
+ .map(Trader::getName)
+ .distinct()
+ .sorted()
+ .reduce("", (acc, name) -> acc + name);
+ out.println(result);
+ out.println("5)");
+ boolean anyTraderInMilan = transactions.stream()
+ .map(Transaction::getTrader)
+ .anyMatch(trader -> "Milan".equalsIgnoreCase(trader.getCity()));
+ out.println(anyTraderInMilan);
+ out.println("6)");
+ transactions.stream()
+ .filter(transaction -> "Cambridge".equalsIgnoreCase(transaction.getTrader().getCity()))
+ .forEach(transaction -> out.println(transaction.getValue()));
+ out.println("7)");
+ transactions.stream()
+ .mapToInt(Transaction::getValue)
+ .max()
+ .ifPresent(out::println);
+ out.println("8)");
+ transactions.stream()
+ .mapToInt(Transaction::getValue)
+ .min()
+ .ifPresent(out::println);
+ out.println("----------------------------------------");
+ IntStream.rangeClosed(1, 100)
+ .boxed()
+ .flatMap(a -> IntStream.rangeClosed(a, 100)
+ .mapToObj(b -> new double[]{a, b, Math.sqrt(a * a + b * b)})
+ .filter(t -> t[2] % 1 == 0)
+ )
+ .limit(5)
+ .forEach(triple -> out.printf("(%.0f, %.0f, %.0f)\n", triple[0], triple[1], triple[2]));
+ out.println("----------------------------------------");
+ Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
+ .limit(20)
+ .forEach(pair -> out.printf("(%d, %d) ", pair[0], pair[1]));
+ out.println();
+ out.println("----------------------------------------");
+ }
+
+ private static void doStreamCollectingDemo() {
+ List menu = getDishes();
+ long howManyDishes = menu.stream().count();
+ out.println(howManyDishes);
+ out.println("----------------------------------------");
+ Comparator dishCaloriesComparator = comparingInt(Dish::getCalories);
+ menu.stream()
+ .collect(maxBy(dishCaloriesComparator))
+ .ifPresent(out::println);
+ out.println("----------------------------------------");
+ int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
+ out.println(totalCalories);
+ out.println("----------------------------------------");
+ double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));
+ out.println(avgCalories);
+ out.println("----------------------------------------");
+ IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
+ out.println(menuStatistics);
+ out.println("----------------------------------------");
+ String shortMenu = menu.stream()
+ .map(Dish::getName)
+ .collect(joining(", "));
+ out.println(shortMenu);
+ out.println("----------------------------------------");
+ Map> dishesByType = menu.stream().collect(groupingBy(Dish::getType));
+ out.println(dishesByType);
+ out.println("----------------------------------------");
+ Map> dishesByCaloricLevel = menu.stream().collect(groupingBy(dish -> {
+ if (dish.getCalories() <= 400) return CaloricLevel.DIET;
+ else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
+ else return CaloricLevel.FAT;
+ }));
+ out.println(dishesByCaloricLevel);
+ out.println("----------------------------------------");
+ Map> caloricDishesByType =
+ menu.stream()
+ .collect(groupingBy(Dish::getType,
+ filtering(dish -> dish.getCalories() > 500,
+ toList())));
+ out.println(caloricDishesByType);
+ out.println("----------------------------------------");
+ Map> dishNamesByType =
+ menu.stream()
+ .collect(groupingBy(Dish::getType,
+ mapping(Dish::getName, toList())));
+ out.println(dishNamesByType);
+ out.println("----------------------------------------");
+ Map> dishTags = Map.of(
+ "pork", asList("greasy", "salty"),
+ "beef", asList("salty", "roasted"),
+ "chicken", asList("fried", "crisp"),
+ "french fries", asList("greasy", "fried"),
+ "rice", asList("light", "natural"),
+ "season fruit", asList("fresh", "natural"),
+ "pizza", asList("tasty", "salty"),
+ "prawns", asList("tasty", "roasted"),
+ "salmon", asList("delicious", "fresh")
+ );
+ Map> dishNamesByType2 =
+ menu.stream()
+ .collect(groupingBy(Dish::getType,
+ flatMapping(dish -> {
+ // FIXME: The example of book is not safety
+ List tags = dishTags.get(dish.getName());
+ if (tags != null) {
+ return tags.stream();
+ }
+ return Stream.empty();
+ }, toSet())));
+ out.println(dishNamesByType2);
+ out.println("----------------------------------------");
+ Map>> dishesByTypeCaloricLevel =
+ menu.stream()
+ .collect(groupingBy(Dish::getType, groupingBy(dish -> {
+ if (dish.getCalories() <= 400)
+ return CaloricLevel.DIET;
+ else if (dish
+ .getCalories() <= 700)
+ return CaloricLevel.NORMAL;
+ else
+ return CaloricLevel.FAT;
+ })));
+ out.println(dishesByTypeCaloricLevel);
+ out.println("----------------------------------------");
+ Map> partitionedMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian));
+ out.println(partitionedMenu);
+ out.println("----------------------------------------");
+ Map>> vegetarianDishesByType =
+ menu.stream().collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));
+ out.println(vegetarianDishesByType);
+ out.println("----------------------------------------");
+ Map mostCaloricPartitionedByVegetarian =
+ menu.stream()
+ .collect(partitioningBy(Dish::isVegetarian,
+ collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get)));
+ out.println(mostCaloricPartitionedByVegetarian);
+ out.println("----------------------------------------");
+ out.println(partitionPrimes(100));
+ out.println("----------------------------------------");
+ List dishes = menu.stream()
+ .filter(Dish::isVegetarian)
+ .collect(new ToListCollector<>());
+ out.println(dishes);
+ out.println("----------------------------------------");
+ out.println(partitionPrimesWithCustomCollector(100));
+ out.println("----------------------------------------");
+ }
+
+ private static void doStreamParallelDemo() {
+ out.println("ForkJoin result (1, 100) = " + forkJoinSum(100));
+ out.println("----------------------------------------");
+ Stream stream1 = IntStream.range(0, SENTENCE.length()).mapToObj(SENTENCE::charAt);
+ out.printf("Found %d words\n", countWords(stream1));
+ out.println("----------------------------------------");
+ Spliterator spliterator = new WordCounterSpliterator(SENTENCE);
+ Stream stream2 = StreamSupport.stream(spliterator, true);
+ out.printf("Found %d words\n", countWords(stream2));
+ out.println("----------------------------------------");
+ }
+
+ private static int countWords(Stream stream) {
+ WordCounter wordCounter = stream.reduce(new WordCounter(0, true),
+ WordCounter::accumulate,
+ WordCounter::combine);
+ return wordCounter.getCounter();
+ }
+
+ private static long forkJoinSum(int n) {
+ long[] numbers = LongStream.rangeClosed(1, n).toArray();
+ ForkJoinTask task = new ForkJoinSumCalculator(numbers);
+ return new ForkJoinPool().invoke(task);
+ }
+
+ public static Map> partitionPrimes(int n) {
+ return IntStream.rangeClosed(2, n).boxed()
+ .collect(partitioningBy(candidate -> isPrime(candidate)));
+ }
+
+ public static boolean isPrime(int candidate) {
+ return IntStream.rangeClosed(2, candidate - 1)
+ .limit((long) Math.floor(Math.sqrt(candidate)) - 1)
+ .noneMatch(i -> candidate % i == 0);
+ }
+
+ static Map> partitionPrimesWithCustomCollector(int n) {
+ return IntStream.rangeClosed(2, n).boxed().collect(new PrimeNumbersCollector());
+ }
+
+ enum CaloricLevel {DIET, NORMAL, FAT}
+
+ private static List getDishes() {
+ List dishes = asList(
+ new Dish("pork", 300, Dish.Type.MEAT),
+ new Dish("salad", 50, Dish.Type.VEGETARIAN),
+ new Dish("chicken", 100, Dish.Type.MEAT),
+ new Dish("chicken", 100, Dish.Type.MEAT),
+ new Dish("tomato", 30, Dish.Type.VEGETARIAN),
+ new Dish("tunny", 120, Dish.Type.FISH),
+ new Dish("potato", 70, Dish.Type.VEGETARIAN)
+ );
+ return Collections.unmodifiableList(dishes);
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/concurrent/Shop.java b/modern-java/src/main/java/org/gym/fp/moderjava/concurrent/Shop.java
new file mode 100644
index 0000000..8475b6c
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/concurrent/Shop.java
@@ -0,0 +1,39 @@
+package org.gym.fp.moderjava.concurrent;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.ToString;
+
+import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+
+@ToString
+@AllArgsConstructor
+public class Shop {
+
+ @Getter
+ private String name;
+
+ public double getPrice(String product) {
+ return calculatePrice(product);
+ }
+
+ public Future getPriceAsync(String product) {
+ return CompletableFuture.supplyAsync(() -> calculatePrice(product));
+ }
+
+ private double calculatePrice(String product) {
+ delay();
+ return new Random().nextDouble() * product.charAt(0) + product.charAt(1);
+ }
+
+ static void delay() {
+ try {
+ Thread.sleep(1000L);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/GroupingBuilder.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/GroupingBuilder.java
new file mode 100644
index 0000000..65aadbd
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/GroupingBuilder.java
@@ -0,0 +1,29 @@
+package org.gym.fp.moderjava.dsl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collector;
+
+import static java.util.stream.Collectors.groupingBy;
+
+public class GroupingBuilder {
+ private final Collector super T, ?, Map> collector;
+
+ public GroupingBuilder(Collector super T, ?, Map> collector) {
+ this.collector = collector;
+ }
+
+ public Collector super T, ?, Map> get() {
+ return collector;
+ }
+
+ public GroupingBuilder, J> after(Function super T, ? extends J> classifier) {
+ return new GroupingBuilder<>(groupingBy(classifier, collector));
+ }
+
+ public static GroupingBuilder, K> groupOn(Function super T, ? extends K> classifier) {
+ return new GroupingBuilder<>(groupingBy(classifier));
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/LambdaOrderBuilder.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/LambdaOrderBuilder.java
new file mode 100644
index 0000000..c75e32e
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/LambdaOrderBuilder.java
@@ -0,0 +1,65 @@
+package org.gym.fp.moderjava.dsl;
+
+import java.util.function.Consumer;
+
+public class LambdaOrderBuilder {
+ private Order order = new Order();
+
+ public static Order order(Consumer consumer) {
+ LambdaOrderBuilder builder = new LambdaOrderBuilder();
+ consumer.accept(builder);
+ return builder.order;
+ }
+
+ public void forCustomer(String customer) {
+ order.setCustomer(customer);
+ }
+
+ public void buy(Consumer consumer) {
+ trade(consumer, Trade.Type.BUY);
+ }
+
+ public void sell(Consumer consumer) {
+ trade(consumer, Trade.Type.SELL);
+ }
+
+ private void trade(Consumer consumer, Trade.Type type) {
+ TradeBuilder builder = new TradeBuilder();
+ builder.trade.setType(type);
+ consumer.accept(builder);
+ order.addTrade(builder.trade);
+ }
+
+ public static class TradeBuilder {
+ private Trade trade = new Trade();
+
+ public void quantity(int quantity) {
+ trade.setQuantity(quantity);
+ }
+
+ public void price(double price) {
+ trade.setPrice(price);
+ }
+
+ public void stock(Consumer consumer) {
+ StockBuilder builder = new StockBuilder();
+ consumer.accept(builder);
+ trade.setStock(builder.stock);
+ }
+
+ }
+
+ public static class StockBuilder {
+ private Stock stock = new Stock();
+
+ public void symbol(String symbol) {
+ stock.setSymbol(symbol);
+ }
+
+ public void market(String market) {
+ stock.setMarket(market);
+ }
+
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/MethodChainingOrderBuilder.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/MethodChainingOrderBuilder.java
new file mode 100644
index 0000000..bf76c09
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/MethodChainingOrderBuilder.java
@@ -0,0 +1,31 @@
+package org.gym.fp.moderjava.dsl;
+
+public class MethodChainingOrderBuilder {
+ public final Order order = new Order();
+
+ private MethodChainingOrderBuilder(String customer) {
+ order.setCustomer(customer);
+ }
+
+ public static MethodChainingOrderBuilder forCustomer(String customer) {
+ return new MethodChainingOrderBuilder(customer);
+ }
+
+ public TradeBuilder buy(int quantity) {
+ return new TradeBuilder(this, Trade.Type.BUY, quantity);
+ }
+
+ public TradeBuilder sell(int quantity) {
+ return new TradeBuilder(this, Trade.Type.SELL, quantity);
+ }
+
+ public MethodChainingOrderBuilder addTrade(Trade trade) {
+ order.addTrade(trade);
+ return this;
+ }
+
+ public Order end() {
+ return order;
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/MixedBuilder.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/MixedBuilder.java
new file mode 100644
index 0000000..573a6f4
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/MixedBuilder.java
@@ -0,0 +1,68 @@
+package org.gym.fp.moderjava.dsl;
+
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+public class MixedBuilder {
+
+ public static Order forCustomer(String customer, TradeBuilder... builders) {
+ Order order = new Order();
+ order.setCustomer(customer);
+ Stream.of(builders).forEach(b -> order.addTrade(b.trade));
+ return order;
+ }
+
+ public static TradeBuilder buy(Consumer consumer) {
+ return buildTrade(consumer, Trade.Type.BUY);
+ }
+
+ public static TradeBuilder sell(Consumer consumer) {
+ return buildTrade(consumer, Trade.Type.SELL);
+ }
+
+ private static TradeBuilder buildTrade(Consumer consumer, Trade.Type type) {
+ TradeBuilder builder = new TradeBuilder();
+ builder.trade.setType(type);
+ consumer.accept(builder);
+ return builder;
+ }
+
+ public static class TradeBuilder {
+ private Trade trade = new Trade();
+
+ public TradeBuilder quantity(int quantity) {
+ trade.setQuantity(quantity);
+ return this;
+ }
+
+ public TradeBuilder at(double price) {
+ trade.setPrice(price);
+ return this;
+ }
+
+ public StockBuilder stock(String symbol) {
+ return new StockBuilder(this, trade, symbol);
+ }
+
+ }
+
+ public static class StockBuilder {
+ private final TradeBuilder builder;
+ private final Trade trade;
+ private final Stock stock = new Stock();
+
+ public StockBuilder(TradeBuilder tradeBuilder, Trade trade, String symbol) {
+ this.builder = tradeBuilder;
+ this.trade = trade;
+ this.stock.setSymbol(symbol);
+ }
+
+ public TradeBuilder on(String market) {
+ this.stock.setMarket(market);
+ trade.setStock(this.stock);
+ return builder;
+ }
+
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/NestedFunctionOrderBuilder.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/NestedFunctionOrderBuilder.java
new file mode 100644
index 0000000..41f2231
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/NestedFunctionOrderBuilder.java
@@ -0,0 +1,46 @@
+package org.gym.fp.moderjava.dsl;
+
+import java.util.stream.Stream;
+
+public class NestedFunctionOrderBuilder {
+
+ public static Order order(String customer, Trade... trades) {
+ Order order = new Order();
+ order.setCustomer(customer);
+ Stream.of(trades).forEach(order::addTrade);
+ return order;
+ }
+
+ public static Trade buy(int quantity, Stock stock, double price) {
+ return buildTrade(quantity, stock, price, Trade.Type.BUY);
+ }
+
+ public static Trade sell(int quantity, Stock stock, double price) {
+ return buildTrade(quantity, stock, price, Trade.Type.SELL);
+ }
+
+ private static Trade buildTrade(int quantity, Stock stock, double price, Trade.Type buy) {
+ Trade trade = new Trade();
+ trade.setQuantity(quantity);
+ trade.setType(buy);
+ trade.setStock(stock);
+ trade.setPrice(price);
+ return trade;
+ }
+
+ public static double at(double price) {
+ return price;
+ }
+
+ public static Stock stock(String symbol, String market) {
+ Stock stock = new Stock();
+ stock.setSymbol(symbol);
+ stock.setMarket(market);
+ return stock;
+ }
+
+ public static String on(String market) {
+ return market;
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Order.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Order.java
new file mode 100644
index 0000000..54960f3
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Order.java
@@ -0,0 +1,31 @@
+package org.gym.fp.moderjava.dsl;
+
+import lombok.ToString;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@ToString
+public class Order {
+ private String customer;
+ private List trades = new ArrayList<>();
+
+ public void addTrade(Trade trade) {
+ trades.add(trade);
+ }
+
+ public String getCustomer() {
+ return customer;
+ }
+
+ public void setCustomer(String customer) {
+ this.customer = customer;
+ }
+
+ public double getValue() {
+ return trades.stream()
+ .mapToDouble(Trade::getValue)
+ .sum();
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Stock.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Stock.java
new file mode 100644
index 0000000..4b3b31d
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Stock.java
@@ -0,0 +1,25 @@
+package org.gym.fp.moderjava.dsl;
+
+import lombok.ToString;
+
+@ToString
+public class Stock {
+ private String symbol;
+ private String market;
+
+ public String getSymbol() {
+ return symbol;
+ }
+
+ public void setSymbol(String symbol) {
+ this.symbol = symbol;
+ }
+
+ public String getMarket() {
+ return market;
+ }
+
+ public void setMarket(String market) {
+ this.market = market;
+ }
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/StockBuilder.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/StockBuilder.java
new file mode 100644
index 0000000..36e885a
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/StockBuilder.java
@@ -0,0 +1,21 @@
+package org.gym.fp.moderjava.dsl;
+
+public class StockBuilder {
+ private final MethodChainingOrderBuilder builder;
+ private final Trade trade;
+ private final Stock stock = new Stock();
+
+ public StockBuilder(MethodChainingOrderBuilder builder,
+ Trade trade, String symbol) {
+ this.builder = builder;
+ this.trade = trade;
+ this.stock.setSymbol(symbol);
+ }
+
+ public TradeBuilderWithStock on(String market) {
+ this.stock.setMarket(market);
+ trade.setStock(this.stock);
+ return new TradeBuilderWithStock(builder, trade);
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Tax.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Tax.java
new file mode 100644
index 0000000..98a798a
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Tax.java
@@ -0,0 +1,17 @@
+package org.gym.fp.moderjava.dsl;
+
+public class Tax {
+
+ public static double regional(double value) {
+ return value * 1.1;
+ }
+
+ public static double general(double value) {
+ return value * 1.3;
+ }
+
+ public static double surcharge(double value) {
+ return value * 1.05;
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/TaxCalculator.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/TaxCalculator.java
new file mode 100644
index 0000000..5c808a2
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/TaxCalculator.java
@@ -0,0 +1,17 @@
+package org.gym.fp.moderjava.dsl;
+
+import java.util.function.DoubleUnaryOperator;
+
+public class TaxCalculator {
+ private DoubleUnaryOperator taxFunction = d -> d;
+
+ public TaxCalculator with(DoubleUnaryOperator f) {
+ this.taxFunction = taxFunction.andThen(f);
+ return this;
+ }
+
+ public double calculate(Order order) {
+ return this.taxFunction.applyAsDouble(order.getValue());
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Trade.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Trade.java
new file mode 100644
index 0000000..b4cdead
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/Trade.java
@@ -0,0 +1,50 @@
+package org.gym.fp.moderjava.dsl;
+
+import lombok.ToString;
+
+@ToString
+public class Trade {
+ public enum Type {BUY, SELL}
+
+ private Type type;
+ private Stock stock;
+ private int quantity;
+ private double price;
+
+ public Type getType() {
+ return type;
+ }
+
+ public void setType(Type type) {
+ this.type = type;
+ }
+
+ public Stock getStock() {
+ return stock;
+ }
+
+ public void setStock(Stock stock) {
+ this.stock = stock;
+ }
+
+ public int getQuantity() {
+ return quantity;
+ }
+
+ public void setQuantity(int quantity) {
+ this.quantity = quantity;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+ public double getValue() {
+ return quantity * price;
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/TradeBuilder.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/TradeBuilder.java
new file mode 100644
index 0000000..8996310
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/TradeBuilder.java
@@ -0,0 +1,18 @@
+package org.gym.fp.moderjava.dsl;
+
+public class TradeBuilder {
+ private final MethodChainingOrderBuilder builder;
+ private final Trade trade = new Trade();
+
+ public TradeBuilder(MethodChainingOrderBuilder builder,
+ Trade.Type buy, int quantity) {
+ this.builder = builder;
+ this.trade.setType(buy);
+ this.trade.setQuantity(quantity);
+ }
+
+ public StockBuilder stock(String symbol) {
+ return new StockBuilder(builder, trade, symbol);
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/dsl/TradeBuilderWithStock.java b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/TradeBuilderWithStock.java
new file mode 100644
index 0000000..2651bf6
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/dsl/TradeBuilderWithStock.java
@@ -0,0 +1,17 @@
+package org.gym.fp.moderjava.dsl;
+
+public class TradeBuilderWithStock {
+ private final MethodChainingOrderBuilder builder;
+ private final Trade trade;
+
+ public TradeBuilderWithStock(MethodChainingOrderBuilder builder, Trade trade) {
+ this.builder = builder;
+ this.trade = trade;
+ }
+
+ public MethodChainingOrderBuilder at(double price) {
+ this.trade.setPrice(price);
+ return builder.addTrade(trade);
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/optional/Car.java b/modern-java/src/main/java/org/gym/fp/moderjava/optional/Car.java
new file mode 100644
index 0000000..9bf9f79
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/optional/Car.java
@@ -0,0 +1,17 @@
+package org.gym.fp.moderjava.optional;
+
+import lombok.Getter;
+import lombok.ToString;
+
+import java.util.Optional;
+
+@ToString
+public class Car {
+
+ @Getter
+ private Optional insurance;
+
+ public Car(Optional insurance) {
+ this.insurance = insurance;
+ }
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/optional/Insurance.java b/modern-java/src/main/java/org/gym/fp/moderjava/optional/Insurance.java
new file mode 100644
index 0000000..4ea8ded
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/optional/Insurance.java
@@ -0,0 +1,16 @@
+package org.gym.fp.moderjava.optional;
+
+import lombok.Getter;
+import lombok.ToString;
+
+@ToString
+public class Insurance {
+
+ @Getter
+ private String name;
+
+ public Insurance(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/optional/Person.java b/modern-java/src/main/java/org/gym/fp/moderjava/optional/Person.java
new file mode 100644
index 0000000..4ec0b32
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/optional/Person.java
@@ -0,0 +1,18 @@
+package org.gym.fp.moderjava.optional;
+
+import lombok.Getter;
+import lombok.ToString;
+
+import java.util.Optional;
+
+@ToString
+public class Person {
+
+ @Getter
+ private Optional car;
+
+ public Person(Optional car) {
+ this.car = car;
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/stream/Dish.java b/modern-java/src/main/java/org/gym/fp/moderjava/stream/Dish.java
new file mode 100644
index 0000000..3aa4235
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/stream/Dish.java
@@ -0,0 +1,26 @@
+package org.gym.fp.moderjava.stream;
+
+import lombok.AllArgsConstructor;
+import lombok.Value;
+
+@Value
+@AllArgsConstructor
+public final class Dish {
+ private final String name;
+ private final int calories;
+ private final Type type;
+
+ public enum Type {
+ MEAT, FISH, VEGETARIAN, OTHER
+ }
+
+ public boolean isVegetarian() {
+ return this.type == Type.VEGETARIAN;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/stream/ForkJoinSumCalculator.java b/modern-java/src/main/java/org/gym/fp/moderjava/stream/ForkJoinSumCalculator.java
new file mode 100644
index 0000000..f291003
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/stream/ForkJoinSumCalculator.java
@@ -0,0 +1,44 @@
+package org.gym.fp.moderjava.stream;
+
+import java.util.concurrent.RecursiveTask;
+
+public class ForkJoinSumCalculator extends RecursiveTask {
+ public static final long THRESHOLD = 10_000;
+
+ private final long[] numbers;
+ private final int start;
+ private final int end;
+
+ public ForkJoinSumCalculator(long[] numbers) {
+ this(numbers, 0, numbers.length);
+ }
+
+ private ForkJoinSumCalculator(long[] numbers, int start, int end) {
+ this.numbers = numbers;
+ this.start = start;
+ this.end = end;
+ }
+
+ @Override
+ protected Long compute() {
+ int length = end - start;
+ if (length <= THRESHOLD) {
+ return computeSequentially();
+ }
+ ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, start + length / 2);
+ leftTask.fork();
+ ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, start + length / 2, end);
+ Long rightResult = rightTask.compute();
+ Long leftResult = leftTask.join();
+ return leftResult + rightResult;
+ }
+
+ private Long computeSequentially() {
+ long sum = 0;
+ for (int i = start; i < end; i++) {
+ sum += numbers[i];
+ }
+ return sum;
+ }
+
+}
diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/stream/PrimeNumbersCollector.java b/modern-java/src/main/java/org/gym/fp/moderjava/stream/PrimeNumbersCollector.java
new file mode 100644
index 0000000..e29e486
--- /dev/null
+++ b/modern-java/src/main/java/org/gym/fp/moderjava/stream/PrimeNumbersCollector.java
@@ -0,0 +1,56 @@
+package org.gym.fp.moderjava.stream;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+
+import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH;
+
+public class PrimeNumbersCollector implements Collector>, Map>> {
+
+ @Override
+ public Supplier