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> collector; + + public GroupingBuilder(Collector> collector) { + this.collector = collector; + } + + public Collector> get() { + return collector; + } + + public GroupingBuilder, J> after(Function classifier) { + return new GroupingBuilder<>(groupingBy(classifier, collector)); + } + + public static GroupingBuilder, K> groupOn(Function 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>> supplier() { + return () -> new HashMap<>() {{ + put(true, new ArrayList<>()); + put(false, new ArrayList<>()); + }}; + } + + @Override + public BiConsumer>, Integer> accumulator() { + return (Map> acc, Integer candidate) -> { + acc.get(isPrime(acc.get(true), candidate)) + .add(candidate); + }; + } + + @Override + public BinaryOperator>> combiner() { + return (Map> map1, Map> map2) -> { + map1.get(true).addAll(map2.get(true)); + map1.get(false).addAll(map2.get(false)); + return map1; + }; + } + + @Override + public Function>, Map>> finisher() { + return Function.identity(); + } + + @Override + public Set characteristics() { + return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH)); + } + + private boolean isPrime(List primes, int candidate) { + int candidateRoot = (int) Math.sqrt(candidate); + return primes.stream() + .takeWhile(i -> i <= candidateRoot) + .noneMatch(i -> candidate % i == 0); + } + +} diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/stream/ToListCollector.java b/modern-java/src/main/java/org/gym/fp/moderjava/stream/ToListCollector.java new file mode 100644 index 0000000..c88f807 --- /dev/null +++ b/modern-java/src/main/java/org/gym/fp/moderjava/stream/ToListCollector.java @@ -0,0 +1,68 @@ +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.CONCURRENT; +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; + +// Collector => +// T = tipo generico degli elementi nello stream +// A = tipo dell'accumulatore, risultato parziale della riduzione +// R = tipo dell'oggetto risultato dall'operazione +public class ToListCollector implements Collector, List> { + + /** + * @return l'accumulatore vuoto + */ + @Override + public Supplier> supplier() { + return ArrayList::new; + } + + /** + * @return la funzione che accumula gli elementi T in A + */ + @Override + public BiConsumer, T> accumulator() { + return List::add; + } + + /** + * Questo metodo è invocato per combinare gli accumulatori + * elaborati da diversi sotto-stream + * + * @return la combinazione degli accumulatori + */ + @Override + public BinaryOperator> combiner() { + return (list1, list2) -> { + list1.addAll(list2); + return list1; + }; + } + + /** + * Questo metodo viene invocato al termine del processo + * di accumulazione degli elementi dello {@link java.util.stream.Stream} + * + * @return la funzione che trasforma A in R + */ + @Override + public Function, List> finisher() { + return Function.identity(); + } + + /** + * @return insieme dei comportamenti che il collector deve assumere + */ + @Override + public Set characteristics() { + return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH, CONCURRENT)); + } + +} diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/stream/Trader.java b/modern-java/src/main/java/org/gym/fp/moderjava/stream/Trader.java new file mode 100644 index 0000000..6acaf23 --- /dev/null +++ b/modern-java/src/main/java/org/gym/fp/moderjava/stream/Trader.java @@ -0,0 +1,11 @@ +package org.gym.fp.moderjava.stream; + +import lombok.AllArgsConstructor; +import lombok.Value; + +@Value +@AllArgsConstructor +public class Trader { + private final String name; + private final String city; +} diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/stream/Transaction.java b/modern-java/src/main/java/org/gym/fp/moderjava/stream/Transaction.java new file mode 100644 index 0000000..b1afa49 --- /dev/null +++ b/modern-java/src/main/java/org/gym/fp/moderjava/stream/Transaction.java @@ -0,0 +1,12 @@ +package org.gym.fp.moderjava.stream; + +import lombok.AllArgsConstructor; +import lombok.Value; + +@Value +@AllArgsConstructor +public class Transaction { + private final Trader trader; + private final int year; + private final int value; +} diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/stream/WordCounter.java b/modern-java/src/main/java/org/gym/fp/moderjava/stream/WordCounter.java new file mode 100644 index 0000000..3ef5cee --- /dev/null +++ b/modern-java/src/main/java/org/gym/fp/moderjava/stream/WordCounter.java @@ -0,0 +1,25 @@ +package org.gym.fp.moderjava.stream; + +public class WordCounter { + private final int counter; + private final boolean lastSpace; + + public WordCounter(int counter, boolean lastSpace) { + this.counter = counter; + this.lastSpace = lastSpace; + } + + public WordCounter accumulate(Character c) { + if (Character.isWhitespace(c)) return lastSpace ? this : new WordCounter(counter, true); + else return lastSpace ? new WordCounter(counter + 1, false) : this; + } + + public WordCounter combine(WordCounter wordCounter) { + return new WordCounter(this.counter + wordCounter.getCounter(), wordCounter.lastSpace); + } + + public int getCounter() { + return this.counter; + } + +} diff --git a/modern-java/src/main/java/org/gym/fp/moderjava/stream/WordCounterSpliterator.java b/modern-java/src/main/java/org/gym/fp/moderjava/stream/WordCounterSpliterator.java new file mode 100644 index 0000000..5a7e4b7 --- /dev/null +++ b/modern-java/src/main/java/org/gym/fp/moderjava/stream/WordCounterSpliterator.java @@ -0,0 +1,45 @@ +package org.gym.fp.moderjava.stream; + +import java.util.Spliterator; +import java.util.function.Consumer; + +public class WordCounterSpliterator implements Spliterator { + private final String string; + private int currentChar = 0; + + public WordCounterSpliterator(String string) { + this.string = string; + } + + @Override + public boolean tryAdvance(Consumer action) { + action.accept(string.charAt(currentChar++)); + return currentChar < string.length(); + } + + @Override + public Spliterator trySplit() { + int currentSize = string.length() - currentChar; + if (currentSize < 10) return null; + for (int splitPos = currentSize / 2 + currentChar; splitPos < string.length(); splitPos++) { + if (Character.isWhitespace(string.charAt(splitPos))) { + Spliterator spliterator = new WordCounterSpliterator( + string.substring(currentChar, splitPos)); + currentChar = splitPos; + return spliterator; + } + } + return null; + } + + @Override + public long estimateSize() { + return string.length() - currentChar; + } + + @Override + public int characteristics() { + return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE; + } + +} diff --git a/pom.xml b/pom.xml index dafdb24..11f7a65 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,7 @@ scala + modern-java