diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/App.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/App.java index fded13624..96a2db323 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/App.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/App.java @@ -1,14 +1,14 @@ package com.iluwatar.fluentinterface; -import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; +import com.iluwatar.fluentinterface.fluentiterable.lazy.LazyFluentIterable; +import com.iluwatar.fluentinterface.fluentiterable.simple.SimpleFluentIterable; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.StringJoiner; +import java.util.*; import java.util.function.Function; import java.util.function.Predicate; +import static java.lang.String.valueOf; + public class App { public static void main(String[] args) { @@ -34,35 +34,49 @@ public class App { }}; prettyPrint("The initial list contains: ", integerList); - List firstFiveNegatives = FluentIterable.from(integerList) + List firstFiveNegatives = SimpleFluentIterable.from(integerList) .filter(negatives()) .first(3) .asList(); prettyPrint("The first three negative values are: ", firstFiveNegatives); - List lastTwoPositives = FluentIterable.from(integerList) + List lastTwoPositives = SimpleFluentIterable.from(integerList) .filter(positives()) .last(2) .asList(); prettyPrint("The last two positive values are: ", lastTwoPositives); - FluentIterable.from(integerList) + SimpleFluentIterable.from(integerList) .filter(number -> number%2 == 0) .first() .ifPresent(evenNumber -> System.out.println(String.format("The first even number is: %d", evenNumber))); - List transformedList = FluentIterable.from(integerList) + List transformedList = SimpleFluentIterable.from(integerList) .filter(negatives()) .map(transformToString()) .asList(); prettyPrint("A string-mapped list of negative numbers contains: ", transformedList); + + List lastTwoOfFirstFourStringMapped = LazyFluentIterable.from(integerList) + .filter(positives()) + .first(4) + .last(2) + .map(number -> "String[" + String.valueOf(number) + "]") + .asList(); + prettyPrint("The lazy list contains the last two of the first four positive numbers mapped to Strings: ", lastTwoOfFirstFourStringMapped); + + LazyFluentIterable.from(integerList) + .filter(negatives()) + .first(2) + .last() + .ifPresent(lastOfFirstTwo -> System.out.println(String.format("The last of the first two negatives is: %d", lastOfFirstTwo))); } private static Function transformToString() { - return integer -> "String[" + String.valueOf(integer) + "]"; + return integer -> "String[" + valueOf(integer) + "]"; } private static Predicate negatives() { return integer -> (integer < 0); diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java index edb9275c1..7bdaf274c 100644 --- a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/FluentIterable.java @@ -1,6 +1,9 @@ package com.iluwatar.fluentinterface.fluentiterable; -import java.util.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; @@ -8,116 +11,49 @@ import java.util.function.Predicate; /** * The FluentIterable is a more convenient implementation of the common iterable interface based * on the fluent interface design pattern. - * This implementation demonstrates a possible way to implement this functionality, but + * This interface defines common operations, but * doesn't aim to be complete. It was inspired by Guava's com.google.common.collect.FluentIterable. * @param is the class of objects the iterable contains */ -public class FluentIterable implements Iterable { - - private final Iterable iterable; +public interface FluentIterable extends Iterable { /** - * This constructor creates a copy of a given iterable's contents. - * @param iterable the iterable this interface copies to work on. - */ - protected FluentIterable(Iterable iterable) { - ArrayList copy = new ArrayList<>(); - Iterator iterator = iterable.iterator(); - while (iterator.hasNext()) { - copy.add(iterator.next()); - } - this.iterable = copy; - } - - /** - * Iterates over all elements of this iterator and filters them. + * Filters the iteration with the given predicate. * @param predicate the condition to test with for the filtering. If the test * is negative, the tested object is removed by the iterator. - * @return the same FluentIterable with a filtered collection + * @return a filtered FluentIterable */ - public final FluentIterable filter(Predicate predicate) { - Iterator iterator = iterator(); - while (iterator.hasNext()) { - TYPE nextElement = iterator.next(); - if(!predicate.test(nextElement)) { - iterator.remove(); - } - } - return this; - } + FluentIterable filter(Predicate predicate); /** * Uses the Iterable interface's forEach method to apply a given function - * for each object of the iterator. - * @param action the action for each object - * @return the same FluentIterable with an untouched collection + * for each object of the iterator. This is a terminating operation. */ - public final FluentIterable forEachDo(Consumer action) { - iterable.forEach(action); - return this; - } + void forEachDo(Consumer action); /** - * Can be used to collect objects from the iteration. - * @return an option of the first object of the iteration + * Evaluates the iteration and returns the first element. This is a terminating operation. + * @return the first element after the iteration is evaluated */ - public final Optional first() { - List list = first(1).asList(); - if(list.isEmpty()) { - return Optional.empty(); - } - return Optional.of(list.get(0)); - } + Optional first(); /** - * Can be used to collect objects from the iteration. - * @param count defines the number of objects to return - * @return the same FluentIterable with a collection decimated to a maximum of 'count' first objects. + * Evaluates the iteration and leaves only the count first elements. + * @return the first count elements as an Iterable */ - public final FluentIterable first(int count) { - Iterator iterator = iterator(); - int currentCount = 0; - while (iterator.hasNext()) { - iterator.next(); - if(currentCount >= count) { - iterator.remove(); - } - currentCount++; - } - return this; - } + FluentIterable first(int count); /** - * Can be used to collect objects from the iteration. - * @return an option of the last object of the iteration + * Evaluates the iteration and returns the last element. This is a terminating operation. + * @return the last element after the iteration is evaluated */ - public final Optional last() { - List list = last(1).asList(); - if(list.isEmpty()) { - return Optional.empty(); - } - return Optional.of(list.get(0)); - } + Optional last(); /** - * Can be used to collect objects from the iteration. - * @param count defines the number of objects to return - * @return the same FluentIterable with a collection decimated to a maximum of 'count' last objects + * Evaluates the iteration and leaves only the count last elements. + * @return the last counts elements as an Iterable */ - public final FluentIterable last(int count) { - int remainingElementsCount = getRemainingElementsCount(); - Iterator iterator = iterator(); - int currentIndex = 0; - while (iterator.hasNext()) { - iterator.next(); - if(currentIndex < remainingElementsCount - count) { - iterator.remove(); - } - currentIndex++; - } - - return this; - } + FluentIterable last(int count); /** * Transforms this FluentIterable into a new one containing objects of the type NEW_TYPE. @@ -125,65 +61,18 @@ public class FluentIterable implements Iterable { * @param the target type of the transformation * @return a new FluentIterable of the new type */ - public final FluentIterable map(Function function) { - List temporaryList = new ArrayList(); - Iterator iterator = iterator(); - while (iterator.hasNext()) { - temporaryList.add(function.apply(iterator.next())); - } - return from(temporaryList); - } + FluentIterable map(Function function); + List asList(); /** - * Collects all remaining objects of this iteration into a list. - * @return a list with all remaining objects of this iteration + * Utility method that iterates over iterable and adds the contents to a list. + * @param iterable the iterable to collect + * @param the type of the objects to iterate + * @return a list with all objects of the given iterator */ - public List asList() { - return toList(iterable.iterator()); - } - - /** - * @return a FluentIterable from a given iterable. Calls the FluentIterable constructor. - */ - public static final FluentIterable from(Iterable iterable) { - return new FluentIterable<>(iterable); - } - - @Override - public Iterator iterator() { - return iterable.iterator(); - } - - @Override - public void forEach(Consumer action) { - iterable.forEach(action); - } - - - @Override - public Spliterator spliterator() { - return iterable.spliterator(); - } - - /** - * @return the count of remaining objects in the current iteration - */ - public final int getRemainingElementsCount() { - int counter = 0; - Iterator iterator = iterator(); - while(iterator.hasNext()) { - iterator.next(); - counter++; - } - return counter; - } - - /** - * Collects the remaining objects of the given iterators iteration into an List. - * @return a new List with the remaining objects. - */ - public static List toList(Iterator iterator) { - List copy = new ArrayList<>(); + static List copyToList(Iterable iterable) { + ArrayList copy = new ArrayList<>(); + Iterator iterator = iterable.iterator(); while (iterator.hasNext()) { copy.add(iterator.next()); } diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java new file mode 100644 index 000000000..0e5b410cc --- /dev/null +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/DecoratingIterator.java @@ -0,0 +1,53 @@ +package com.iluwatar.fluentinterface.fluentiterable.lazy; + +import java.util.Iterator; + +/** + * This class is used to realize LazyFluentIterables. It decorates + * a given iterator. + * @param + */ +public abstract class DecoratingIterator implements Iterator { + + protected final Iterator fromIterator; + + private TYPE next = null; + + /** + * Creates an iterator that decorates the given iterator. + * @param fromIterator + */ + public DecoratingIterator(Iterator fromIterator) { + this.fromIterator = fromIterator; + } + + /** + * Precomputes and caches the next element of the iteration. + * @return true if a next element is available + */ + @Override + public final boolean hasNext() { + next = computeNext(); + return next != null; + } + + /** + * Returns the next element of the iteration. This implementation caches it. + * If no next element is cached, it is computed. + * @return the next element obf the iteration + */ + @Override + public final TYPE next() { + TYPE result = next; + next = null; + result = (result == null ? fromIterator.next() : result); + return result; + } + + /** + * Computes the next object of the iteration. Can be implemented to + * realize custom behaviour for an iteration process. + * @return + */ + public abstract TYPE computeNext(); +} diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java new file mode 100644 index 000000000..c6db4d2cd --- /dev/null +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/lazy/LazyFluentIterable.java @@ -0,0 +1,236 @@ +package com.iluwatar.fluentinterface.fluentiterable.lazy; + +import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * This is a lazy implementation of the FluentIterable interface. It evaluates + * all chained operations when a terminating operation is applied. + * @param the type of the objects the iteration is about + */ +public class LazyFluentIterable implements FluentIterable { + + private final Iterable iterable; + + /** + * This constructor creates a new LazyFluentIterable. It wraps the + * given iterable. + * @param iterable the iterable this FluentIterable works on. + */ + protected LazyFluentIterable(Iterable iterable) { + this.iterable = iterable; + } + + /** + * This constructor can be used to implement anonymous subclasses + * of the LazyFluentIterable. + */ + protected LazyFluentIterable() { + iterable = this; + } + + /** + * Adds a filter operation to the operation chain and returns a new iterable. + * @param predicate the condition to test with for the filtering. If the test + * is negative, the tested object is removed by the iterator. + * @return a new FluentIterable object that decorates the source iterable + */ + @Override + public FluentIterable filter(Predicate predicate) { + return new LazyFluentIterable() { + @Override + public Iterator iterator() { + return new DecoratingIterator(iterable.iterator()) { + @Override + public TYPE computeNext() { + while(true) { + if(fromIterator.hasNext()) { + TYPE candidate = fromIterator.next(); + if(!predicate.test(candidate)) { + continue; + } + return candidate; + } + + return null; + } + } + }; + } + }; + } + + /** + * Uses the Iterable interface's forEach method to apply a given function + * for each object of the iterator. Is a terminating operation. + * @param action the action for each object + */ + @Override + public void forEachDo(Consumer action) { + Iterator newIterator = iterable.iterator(); + while(newIterator.hasNext()) { + action.accept(newIterator.next()); + } + } + + /** + * Can be used to collect objects from the iteration. Is a terminating operation. + * @return an option of the first object of the iteration + */ + @Override + public Optional first() { + Optional result = Optional.empty(); + List list = first(1).asList(); + if(!list.isEmpty()) { + result = Optional.of(list.get(0)); + } + + return result; + } + + /** + * Can be used to collect objects from the iteration. Is a terminating operation. + * @param count defines the number of objects to return + * @return the same FluentIterable with a collection decimated to a maximum of 'count' first objects. + */ + @Override + public FluentIterable first(int count) { + return new LazyFluentIterable() { + @Override + public Iterator iterator() { + return new DecoratingIterator(iterable.iterator()) { + int currentIndex = 0; + + @Override + public TYPE computeNext() { + if(currentIndex < count) { + if(fromIterator.hasNext()) { + TYPE candidate = fromIterator.next(); + currentIndex++; + return candidate; + } + } + return null; + } + }; + } + }; + } + + /** + * Can be used to collect objects from the iteration. Is a terminating operation. + * @return an option of the last object of the iteration + */ + @Override + public Optional last() { + Optional result = Optional.empty(); + List list = last(1).asList(); + if(!list.isEmpty()) { + result = Optional.of(list.get(0)); + } + + return result; + } + + /** + * Can be used to collect objects from the iteration. Is a terminating operation. + * @param count defines the number of objects to return + * @return the same FluentIterable with a collection decimated to a maximum of 'count' last objects + */ + @Override + public FluentIterable last(int count) {return new LazyFluentIterable() { + @Override + public Iterator iterator() { + return new DecoratingIterator(iterable.iterator()) { + int currentIndex = 0; + + @Override + public TYPE computeNext() { + List list = new ArrayList<>(); + + Iterator newIterator = iterable.iterator(); + while(newIterator.hasNext()) { + list.add(newIterator.next()); + } + + int totalElementsCount = list.size(); + int stopIndex = totalElementsCount - count; + + TYPE candidate = null; + while(currentIndex < stopIndex && fromIterator.hasNext()) { + currentIndex++; + fromIterator.next(); + } + if(currentIndex >= stopIndex && fromIterator.hasNext()) { + candidate = fromIterator.next(); + } + return candidate; + } + }; + } + }; + } + + /** + * Transforms this FluentIterable into a new one containing objects of the type NEW_TYPE. + * @param function a function that transforms an instance of TYPE into an instance of NEW_TYPE + * @param the target type of the transformation + * @return a new FluentIterable of the new type + */ + @Override + public FluentIterable map(Function function) { + return new LazyFluentIterable() { + @Override + public Iterator iterator() { + return new DecoratingIterator(null) { + Iterator oldTypeIterator = iterable.iterator(); + @Override + public NEW_TYPE computeNext() { + while(true) { + if(oldTypeIterator.hasNext()) { + TYPE candidate = oldTypeIterator.next(); + return function.apply(candidate); + } + return null; + } + } + }; + } + }; + } + + /** + * Collects all remaining objects of this iteration into a list. + * @return a list with all remaining objects of this iteration + */ + @Override + public List asList() { + List copy = FluentIterable.copyToList(iterable); + return copy; + } + + @Override + public Iterator iterator() { + return new DecoratingIterator(iterable.iterator()) { + @Override + public TYPE computeNext() { + return fromIterator.next(); + } + }; + } + + /** + * @return a FluentIterable from a given iterable. Calls the LazyFluentIterable constructor. + */ + public static final FluentIterable from(Iterable iterable) { + return new LazyFluentIterable<>(iterable); + } + +} diff --git a/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java new file mode 100644 index 000000000..efaa87bbb --- /dev/null +++ b/fluentinterface/src/main/java/com/iluwatar/fluentinterface/fluentiterable/simple/SimpleFluentIterable.java @@ -0,0 +1,194 @@ +package com.iluwatar.fluentinterface.fluentiterable.simple; + +import com.iluwatar.fluentinterface.fluentiterable.FluentIterable; + +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * This is a simple implementation of the FluentIterable interface. It evaluates + * all chained operations eagerly. + * @param the type of the objects the iteration is about + */ +public class SimpleFluentIterable implements FluentIterable { + + private final Iterable iterable; + + /** + * This constructor creates a copy of a given iterable's contents. + * @param iterable the iterable this interface copies to work on. + */ + protected SimpleFluentIterable(Iterable iterable) { + List copy = FluentIterable.copyToList(iterable); + this.iterable = copy; + } + + /** + * Iterates over all elements of this iterator and filters them. + * @param predicate the condition to test with for the filtering. If the test + * is negative, the tested object is removed by the iterator. + * @return the same FluentIterable with a filtered collection + */ + @Override + public final FluentIterable filter(Predicate predicate) { + Iterator iterator = iterator(); + while (iterator.hasNext()) { + TYPE nextElement = iterator.next(); + if(!predicate.test(nextElement)) { + iterator.remove(); + } + } + return this; + } + + /** + * Uses the Iterable interface's forEach method to apply a given function + * for each object of the iterator. Is a terminating operation. + * @param action the action for each object + */ + @Override + public void forEachDo(Consumer action) { + iterable.forEach(action); + } + + /** + * Can be used to collect objects from the iteration. Is a terminating operation. + * @return an option of the first object of the iteration + */ + @Override + public final Optional first() { + List list = first(1).asList(); + if(list.isEmpty()) { + return Optional.empty(); + } + return Optional.of(list.get(0)); + } + + /** + * Can be used to collect objects from the iteration. Is a terminating operation. + * @param count defines the number of objects to return + * @return the same FluentIterable with a collection decimated to a maximum of 'count' first objects. + */ + @Override + public final FluentIterable first(int count) { + Iterator iterator = iterator(); + int currentCount = 0; + while (iterator.hasNext()) { + iterator.next(); + if(currentCount >= count) { + iterator.remove(); + } + currentCount++; + } + return this; + } + + /** + * Can be used to collect objects from the iteration. Is a terminating operation. + * @return an option of the last object of the iteration + */ + @Override + public final Optional last() { + List list = last(1).asList(); + if(list.isEmpty()) { + return Optional.empty(); + } + return Optional.of(list.get(0)); + } + + /** + * Can be used to collect objects from the iteration. Is a terminating operation. + * @param count defines the number of objects to return + * @return the same FluentIterable with a collection decimated to a maximum of 'count' last objects + */ + @Override + public final FluentIterable last(int count) { + int remainingElementsCount = getRemainingElementsCount(); + Iterator iterator = iterator(); + int currentIndex = 0; + while (iterator.hasNext()) { + iterator.next(); + if(currentIndex < remainingElementsCount - count) { + iterator.remove(); + } + currentIndex++; + } + + return this; + } + + /** + * Transforms this FluentIterable into a new one containing objects of the type NEW_TYPE. + * @param function a function that transforms an instance of TYPE into an instance of NEW_TYPE + * @param the target type of the transformation + * @return a new FluentIterable of the new type + */ + @Override + public final FluentIterable map(Function function) { + List temporaryList = new ArrayList(); + Iterator iterator = iterator(); + while (iterator.hasNext()) { + temporaryList.add(function.apply(iterator.next())); + } + return from(temporaryList); + } + + /** + * Collects all remaining objects of this iteration into a list. + * @return a list with all remaining objects of this iteration + */ + @Override + public List asList() { + return toList(iterable.iterator()); + } + + /** + * @return a FluentIterable from a given iterable. Calls the SimpleFluentIterable constructor. + */ + public static final FluentIterable from(Iterable iterable) { + return new SimpleFluentIterable<>(iterable); + } + + @Override + public Iterator iterator() { + return iterable.iterator(); + } + + @Override + public void forEach(Consumer action) { + iterable.forEach(action); + } + + + @Override + public Spliterator spliterator() { + return iterable.spliterator(); + } + + /** + * @return the count of remaining objects in the current iteration + */ + public final int getRemainingElementsCount() { + int counter = 0; + Iterator iterator = iterator(); + while(iterator.hasNext()) { + iterator.next(); + counter++; + } + return counter; + } + + /** + * Collects the remaining objects of the given iterators iteration into an List. + * @return a new List with the remaining objects. + */ + public static List toList(Iterator iterator) { + List copy = new ArrayList<>(); + while (iterator.hasNext()) { + copy.add(iterator.next()); + } + return copy; + } +}