From 063e33daee01601d81b10c69abbcf00efd574f29 Mon Sep 17 00:00:00 2001 From: Tomasz Nurkiewicz Date: Sat, 11 May 2013 09:34:58 +0200 Subject: [PATCH] Initial commit, mostly working, tests missing --- .gitignore | 3 + pom.xml | 55 +++ src/README.md | 3 + .../com/blogspot/nurkiewicz/lazyseq/Cons.java | 79 ++++ .../lazyseq/DummyLazySeqCollector.java | 41 ++ .../nurkiewicz/lazyseq/FixedCons.java | 72 ++++ .../blogspot/nurkiewicz/lazyseq/LazySeq.java | 394 ++++++++++++++++++ .../nurkiewicz/lazyseq/LazySeqIterator.java | 39 ++ .../nurkiewicz/lazyseq/LazySeqStream.java | 243 +++++++++++ .../com/blogspot/nurkiewicz/lazyseq/Nil.java | 116 ++++++ .../lazyseq/AbstractBaseTestCase.java | 17 + .../nurkiewicz/lazyseq/LazySeqHeadTest.java | 123 ++++++ 12 files changed, 1185 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/README.md create mode 100644 src/main/java/com/blogspot/nurkiewicz/lazyseq/Cons.java create mode 100644 src/main/java/com/blogspot/nurkiewicz/lazyseq/DummyLazySeqCollector.java create mode 100644 src/main/java/com/blogspot/nurkiewicz/lazyseq/FixedCons.java create mode 100644 src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeq.java create mode 100644 src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeqIterator.java create mode 100644 src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeqStream.java create mode 100644 src/main/java/com/blogspot/nurkiewicz/lazyseq/Nil.java create mode 100644 src/test/java/com/blogspot/nurkiewicz/lazyseq/AbstractBaseTestCase.java create mode 100644 src/test/java/com/blogspot/nurkiewicz/lazyseq/LazySeqHeadTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ddd19a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +*.iml +.idea diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..fd532d1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + + LazySeq + com.blogspot.nurkiewicz.lazyseq + lazyseq + 0.0.1-SNAPSHOT + + + UTF-8 + + + + + + org.testng + testng + 6.8.1 + + + junit + junit + + + test + + + org.easytesting + fest-assert-core + 2.0M9 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + + + + + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + + + + + diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..c532ff2 --- /dev/null +++ b/src/README.md @@ -0,0 +1,3 @@ +# `LazySeq` - lazy sequence implementation for Java 8 + +## TODO diff --git a/src/main/java/com/blogspot/nurkiewicz/lazyseq/Cons.java b/src/main/java/com/blogspot/nurkiewicz/lazyseq/Cons.java new file mode 100644 index 0000000..11b13b2 --- /dev/null +++ b/src/main/java/com/blogspot/nurkiewicz/lazyseq/Cons.java @@ -0,0 +1,79 @@ +package com.blogspot.nurkiewicz.lazyseq; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Tomasz Nurkiewicz + * @since 5/8/13, 9:08 PM + */ +class Cons extends LazySeq { + private final E head; + private volatile LazySeq tailOrNull; + private final Supplier> tailFun; + + Cons(E head, Supplier> tailFun) { + this.head = head; + this.tailFun = tailFun; + } + + @Override + public E head() { + return head; + } + + @Override + public LazySeq tail() { + if (!isTailDefined()) { + synchronized (this) { + if (!isTailDefined()) { + tailOrNull = tailFun.get(); + } + } + } + return tailOrNull; + } + + @Override + protected boolean isTailDefined() { + return tailOrNull != null; + } + + public LazySeq map(Function mapper) { + return cons(mapper.apply(head()), () -> tail().map(mapper)); + } + + @Override + public LazySeq filter(Predicate predicate) { + if (predicate.test(head)) { + return cons(head, () -> tail().filter(predicate)); + } else { + return tail().filter(predicate); + } + } + + @Override + public LazySeq flatMap(Function> mapper) { + final List headFlattened = mapper.apply(head).collect(Collectors.toList()); + return concat(headFlattened, () -> tail().flatMap(mapper)); + } + + @Override + public LazySeq limit(long maxSize) { + if (maxSize > 0) { + return cons(head, () -> tail().limit(maxSize - 1)); + } else { + return LazySeq.empty(); + } + } + + @Override + public boolean isEmpty() { + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/com/blogspot/nurkiewicz/lazyseq/DummyLazySeqCollector.java b/src/main/java/com/blogspot/nurkiewicz/lazyseq/DummyLazySeqCollector.java new file mode 100644 index 0000000..8791179 --- /dev/null +++ b/src/main/java/com/blogspot/nurkiewicz/lazyseq/DummyLazySeqCollector.java @@ -0,0 +1,41 @@ +package com.blogspot.nurkiewicz.lazyseq; + +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.BinaryOperator; +import java.util.function.Supplier; +import java.util.stream.Collector; + +/** + * @author Tomasz Nurkiewicz + * @since 5/9/13, 11:09 AM + */ +final class DummyLazySeqCollector implements Collector> { + + private static final DummyLazySeqCollector INSTANCE = new DummyLazySeqCollector<>(); + + @SuppressWarnings("unchecked") + public static DummyLazySeqCollector getInstance() { + return (DummyLazySeqCollector) INSTANCE; + } + + @Override + public Supplier> resultSupplier() { + throw new IllegalStateException("Should never be called"); + } + + @Override + public BiFunction, E, LazySeq> accumulator() { + throw new IllegalStateException("Should never be called"); + } + + @Override + public BinaryOperator> combiner() { + throw new IllegalStateException("Should never be called"); + } + + @Override + public Set characteristics() { + throw new IllegalStateException("Should never be called"); + } +} diff --git a/src/main/java/com/blogspot/nurkiewicz/lazyseq/FixedCons.java b/src/main/java/com/blogspot/nurkiewicz/lazyseq/FixedCons.java new file mode 100644 index 0000000..28f0fe7 --- /dev/null +++ b/src/main/java/com/blogspot/nurkiewicz/lazyseq/FixedCons.java @@ -0,0 +1,72 @@ +package com.blogspot.nurkiewicz.lazyseq; + +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Tomasz Nurkiewicz + * @since 5/8/13, 9:09 PM + */ +class FixedCons extends LazySeq { + + private final E head; + private final LazySeq tail; + + public FixedCons(E head, LazySeq tail) { + this.head = head; + this.tail = tail; + } + + @Override + public E head() { + return head; + } + + @Override + public LazySeq tail() { + return tail; + } + + @Override + protected boolean isTailDefined() { + return true; + } + + @Override + public LazySeq map(Function mapper) { + return cons(mapper.apply(head), tail.map(mapper)); + } + + @Override + public LazySeq filter(Predicate predicate) { + if (predicate.test(head)) { + return cons(head, tail.filter(predicate)); + } else { + return tail.filter(predicate); + } + } + + @Override + public LazySeq flatMap(Function> mapper) { + final List headFlattened = mapper.apply(head).collect(Collectors.toList()); + return concat(headFlattened, tail.flatMap(mapper)); + } + + @Override + public LazySeq limit(long maxSize) { + if (maxSize > 0) { + return cons(head, tail.limit(maxSize - 1)); + } else { + return LazySeq.empty(); + } + } + + @Override + public boolean isEmpty() { + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeq.java b/src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeq.java new file mode 100644 index 0000000..d2de4d0 --- /dev/null +++ b/src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeq.java @@ -0,0 +1,394 @@ +package com.blogspot.nurkiewicz.lazyseq; + +import java.util.*; +import java.util.function.*; +import java.util.stream.Collector; +import java.util.stream.Stream; + +import static java.util.Collections.unmodifiableList; + +/** + * @author Tomasz Nurkiewicz + * @since 5/6/13, 9:20 PM + */ +public abstract class LazySeq extends AbstractList { + + private static final LazySeq NIL = new Nil<>(); + + public abstract E head(); + + public Optional headOption() { + return Optional.of(head()); + } + + public abstract LazySeq tail(); + + public static LazySeq of(E element) { + return cons(element, empty()); + } + + public static LazySeq of(E element1, E element2, E element3) { + return cons(element1, of(element2, element3)); + } + + public static LazySeq of(E element1, E element2, E element3, Supplier> tailFun) { + return cons(element1, of(element2, element3, tailFun)); + } + + public static LazySeq of(E element1, E element2, Supplier> tailFun) { + return cons(element1, of(element2, tailFun)); + } + + public static LazySeq of(E element, Supplier> tailFun) { + return cons(element, tailFun); + } + + public static LazySeq of(E element1, E element2) { + return cons(element1, of(element2)); + } + + public static LazySeq iterate(E initial, Function fun) { + return new Cons<>(initial, () -> iterate(fun.apply(initial), fun)); + } + + public static LazySeq of(E... elements) { + return of(Arrays.asList(elements).iterator()); + } + + public static LazySeq of(Iterable elements) { + return of(elements.iterator()); + } + + public static LazySeq concat(Iterable elements, Supplier> tailFun) { + return concat(elements.iterator(), tailFun); + } + + public static LazySeq concat(Iterable elements, LazySeq tail) { + return concat(elements.iterator(), tail); + } + + public static LazySeq concat(Iterator iterator, LazySeq tail) { + if (iterator.hasNext()) { + return concatNonEmptyIterator(iterator, tail); + } else { + return tail; + } + } + + private static LazySeq concatNonEmptyIterator(Iterator iterator, LazySeq tail) { + final E next = iterator.next(); + if (iterator.hasNext()) { + return cons(next, concatNonEmptyIterator(iterator, tail)); + } else { + return cons(next, tail); + } + } + + public static LazySeq concat(Iterator iterator, Supplier> tailFun) { + if (iterator.hasNext()) { + return concatNonEmptyIterator(iterator, tailFun); + } else { + return tailFun.get(); + } + } + + private static LazySeq concatNonEmptyIterator(Iterator iterator, Supplier> tailFun) { + final E next = iterator.next(); + if (iterator.hasNext()) { + return cons(next, concatNonEmptyIterator(iterator, tailFun)); + } else { + return cons(next, tailFun); + } + } + + public static LazySeq of(Iterator iterator) { + if (iterator.hasNext()) { + return cons(iterator.next(), of(iterator)); + } else { + return empty(); + } + } + + public static LazySeq cons(E head, Supplier> tailFun) { + return new Cons<>(head, tailFun); + } + + public static LazySeq cons(E head, LazySeq tail) { + return new FixedCons<>(head, tail); + } + + @SuppressWarnings("unchecked") + public static LazySeq empty() { + return (LazySeq) NIL; + } + + public static Collector> toLazySeq() { + return DummyLazySeqCollector.getInstance(); + } + + public static LazySeq tabulate(int start, Function generator) { + return cons(generator.apply(start), () -> tabulate(start + 1, generator)); + } + + public static LazySeq continually(Supplier generator) { + return cons(generator.get(), () -> continually(generator)); + } + + protected abstract boolean isTailDefined(); + + @Override + public E get(int index) { + if (index < 0) { + throw new IndexOutOfBoundsException(Integer.toString(index)); + } + return getUnsafe(index); + } + + private E getUnsafe(int index) { + if (index == 0) { + return head(); + } else { + return tail().getUnsafe(index - 1); + } + } + + public abstract LazySeq map(Function mapper); + + @Override + public Stream stream() { + return new LazySeqStream(this); + } + + @Override + public Spliterator spliterator() { + return new Spliterator() { + + private LazySeq cur = LazySeq.this; + + @Override + public boolean tryAdvance(Consumer action) { + if (!cur.isEmpty()) { + action.accept(cur.head()); + cur = cur.tail(); + return true; + } else { + return false; + } + } + + @Override + public Spliterator trySplit() { + if (cur.isEmpty()) { + return null; + } + final E singleton = cur.head(); + cur = cur.tail(); + return new Spliterator() { + @Override + public boolean tryAdvance(Consumer action) { + action.accept(singleton); + return true; + } + + @Override + public Spliterator trySplit() { + return null; + } + + @Override + public long estimateSize() { + return 1; + } + + @Override + public int characteristics() { + return CONCURRENT | IMMUTABLE | NONNULL | ORDERED; + } + }; + } + + @Override + public long estimateSize() { + return Long.MAX_VALUE; + } + + @Override + public int characteristics() { + return CONCURRENT | IMMUTABLE | NONNULL | ORDERED; + } + }; + } + + @Override + public String toString() { + final StringBuilder s = new StringBuilder("["); + LazySeq cur = this; + while (!cur.isEmpty()) { + s.append(cur.head()); + if (cur.isTailDefined()) { + if (!cur.tail().isEmpty()) { + s.append(", "); + } + cur = cur.tail(); + } else { + s.append(", ?"); + break; + } + } + return s.append("]").toString(); + } + + public abstract LazySeq filter(Predicate predicate); + + public abstract LazySeq flatMap(Function> mapper); + + public abstract LazySeq limit(long maxSize); + + public LazySeq take(long maxSize) { + return limit(maxSize); + } + + public LazySeq substream(long startInclusive) { + if (startInclusive > 0) { + return tail().substream(startInclusive - 1); + } else { + return this; + } + } + + public LazySeq drop(long startInclusive) { + return substream(startInclusive); + } + + public LazySeq substream(long startInclusive, long endExclusive) { + if (startInclusive > 0) { + return tail().substream(startInclusive - 1, endExclusive - 1); + } else { + return limit(endExclusive); + } + } + + public void forEach(Consumer action) { + action.accept(head()); + tail().forEach(action); + } + + public E reduce(E identity, BinaryOperator accumulator) { + E result = identity; + LazySeq cur = this; + while (!cur.isEmpty()) { + result = accumulator.apply(result, cur.head()); + cur = cur.tail(); + } + return result; + } + + public Optional reduce(BinaryOperator accumulator) { + if (isEmpty() || tail().isEmpty()) { + return Optional.empty(); + } + return Optional.of(tail().reduce(head(), accumulator)); + } + + public U reduce(U identity, BiFunction accumulator) { + U result = identity; + LazySeq cur = this; + while (!cur.isEmpty()) { + identity = accumulator.apply(identity, cur.head()); + cur = cur.tail(); + } + return result; + } + + public Optional min(Comparator comparator) { + return greatestByComparator(comparator.reverseOrder()); + } + + public Optional max(Comparator comparator) { + return greatestByComparator(comparator); + } + + private Optional greatestByComparator(Comparator comparator) { + if (tail().isEmpty()) { + return Optional.of(head()); + } + E minSoFar = minByComparator(head(), tail().head(), comparator); + LazySeq cur = this.tail(); + while (!cur.isEmpty()) { + minSoFar = minByComparator(minSoFar, cur.tail().head(), comparator); + } + return Optional.of(minSoFar); + } + + @Override + public int size() { + int sizeSoFar = 0; + for (LazySeq cur = this; !cur.isEmpty(); cur = cur.tail()) { + ++sizeSoFar; + } + return sizeSoFar; + } + + @Override + public Iterator iterator() { + return new LazySeqIterator(this); + } + + private static E minByComparator(E first, E second, Comparator comparator) { + return comparator.compare(first, second) <= 0? first : second; + } + + public boolean anyMatch(Predicate predicate) { + return predicate.test(head()) || tail().anyMatch(predicate); + } + + public boolean allMatch(Predicate predicate) { + return predicate.test(head()) && tail().allMatch(predicate); + } + + public boolean noneMatch(Predicate predicate) { + return allMatch(predicate.negate()); + } + + public LazySeq zip(LazySeq second, BiFunction zipper) { + final R headsZipped = zipper.apply(head(), second.head()); + if (second.tail().isEmpty()) { + return of(headsZipped); + } else { + return cons(headsZipped, () -> tail().zip(second.tail(), zipper)); + } + } + + public LazySeq takeWhile(Predicate predicate) { + if (predicate.test(head())) { + return cons(head(), tail().takeWhile(predicate)); + } else { + return empty(); + } + } + + public LazySeq dropWhile(Predicate predicate) { + if (predicate.test(head())) { + return tail().dropWhile(predicate); + } else { + return empty(); + } + } + + public LazySeq> sliding(int size) { + final List window = unmodifiableList(new ArrayList<>(take(size))); + return cons(window, () -> tail().sliding(size)); + } + + public LazySeq> grouped(int size) { + final List window = unmodifiableList(new ArrayList<>(take(size))); + return cons(window, () -> drop(size).grouped(size)); + } + + public LazySeq scan(E initial, BinaryOperator fun) { + return cons(initial, () -> tail().scan(fun.apply(initial, head()), fun)); + } + +} + + diff --git a/src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeqIterator.java b/src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeqIterator.java new file mode 100644 index 0000000..c6a6edd --- /dev/null +++ b/src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeqIterator.java @@ -0,0 +1,39 @@ +package com.blogspot.nurkiewicz.lazyseq; + +import java.util.Iterator; +import java.util.function.Consumer; + +/** + * @author Tomasz Nurkiewicz + * @since 5/9/13, 2:15 PM + */ +public class LazySeqIterator implements Iterator { + + private LazySeq underlying; + + public LazySeqIterator(LazySeq lazySeq) { + this.underlying = lazySeq; + } + + @Override + public boolean hasNext() { + return underlying.isEmpty(); + } + + @Override + public E next() { + final E next = underlying.head(); + underlying = underlying.tail(); + return next; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + + @Override + public void forEachRemaining(Consumer action) { + underlying.forEach(action); + } +} diff --git a/src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeqStream.java b/src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeqStream.java new file mode 100644 index 0000000..19120de --- /dev/null +++ b/src/main/java/com/blogspot/nurkiewicz/lazyseq/LazySeqStream.java @@ -0,0 +1,243 @@ +package com.blogspot.nurkiewicz.lazyseq; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.Optional; +import java.util.Spliterator; +import java.util.function.*; +import java.util.stream.*; + +/** + * @author Tomasz Nurkiewicz + * @since 5/8/13, 9:09 PM + */ +class LazySeqStream implements Stream { + + private final LazySeq underlying; + + LazySeqStream(LazySeq underlying) { + this.underlying = underlying; + } + + @Override + public Stream filter(Predicate predicate) { + return underlying.filter(predicate).stream(); + } + + @Override + public Stream map(Function mapper) { + return underlying.map(mapper).stream(); + } + + @Override + public Stream flatMap(Function> mapper) { + return underlying.flatMap(mapper).stream(); + } + + @Override + public Stream limit(long maxSize) { + return underlying.limit(maxSize).stream(); + } + + @Override + public Stream substream(long startInclusive) { + return underlying.substream(startInclusive).stream(); + } + + @Override + public Stream substream(long startInclusive, long endExclusive) { + return underlying.substream(startInclusive, endExclusive).stream(); + } + + @Override + public void forEach(Consumer action) { + underlying.forEach(action); + } + + @Override + public void forEachOrdered(Consumer action) { + underlying.forEach(action); + } + + @Override + public Object[] toArray() { + final Object[] array = new Object[underlying.size()]; + copyToArray(array); + return array; + } + + @Override + public A[] toArray(IntFunction generator) { + final A[] array = generator.apply(underlying.size()); + copyToArray(array); + return array; + } + + private void copyToArray(Object[] array) { + LazySeq cur = underlying; + for (int i = 0; i < array.length; ++i) { + array[i] = cur.head(); + cur = cur.tail(); + } + } + + @Override + public E reduce(E identity, BinaryOperator accumulator) { + return underlying.reduce(identity, accumulator); + } + + @Override + public Optional reduce(BinaryOperator accumulator) { + return underlying.reduce(accumulator); + } + + @Override + public U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) { + return underlying.reduce(identity, accumulator); + } + + @Override + public Optional min(Comparator comparator) { + return underlying.min(comparator); + } + + @Override + public Optional max(Comparator comparator) { + return underlying.max(comparator); + } + + @Override + public long count() { + return underlying.size(); + } + + @Override + public boolean anyMatch(Predicate predicate) { + return underlying.anyMatch(predicate); + } + + @Override + public boolean allMatch(Predicate predicate) { + return underlying.allMatch(predicate); + } + + @Override + public boolean noneMatch(Predicate predicate) { + return underlying.noneMatch(predicate); + } + + @Override + public Optional findFirst() { + return underlying.headOption(); + } + + @Override + public Optional findAny() { + return underlying.headOption(); + } + + @Override + public Iterator iterator() { + return underlying.iterator(); + } + + @Override + public Spliterator spliterator() { + throw new UnsupportedOperationException("Not yet implemented: spliterator"); + } + + @Override + public boolean isParallel() { + throw new UnsupportedOperationException("Not yet implemented: isParallel"); + } + + @Override + public Stream sequential() { + throw new UnsupportedOperationException("Not yet implemented: sequential"); + } + + @Override + public Stream parallel() { + throw new UnsupportedOperationException("Not yet implemented: parallel"); + } + + @Override + public Stream unordered() { + throw new UnsupportedOperationException("Not yet implemented: unordered"); + } + + @Override + public IntStream mapToInt(ToIntFunction mapper) { + throw new UnsupportedOperationException("Not yet implemented: mapToInt"); + } + + @Override + public LongStream mapToLong(ToLongFunction mapper) { + throw new UnsupportedOperationException("Not yet implemented: mapToLong"); + } + + @Override + public DoubleStream mapToDouble(ToDoubleFunction mapper) { + throw new UnsupportedOperationException("Not yet implemented: mapToDouble"); + } + + @Override + public IntStream flatMapToInt(Function mapper) { + throw new UnsupportedOperationException("Not yet implemented: flatMapToInt"); + } + + @Override + public LongStream flatMapToLong(Function mapper) { + throw new UnsupportedOperationException("Not yet implemented: flatMapToLong"); + } + + @Override + public DoubleStream flatMapToDouble(Function mapper) { + throw new UnsupportedOperationException("Not yet implemented: flatMapToDouble"); + } + + @Override + public Stream distinct() { + throw new UnsupportedOperationException("Not yet implemented: distinct"); + } + + @Override + public Stream sorted() { + throw new UnsupportedOperationException("Not yet implemented: sorted"); + } + + @Override + public Stream sorted(Comparator comparator) { + throw new UnsupportedOperationException("Not yet implemented: sorted"); + } + + @Override + public Stream peek(Consumer consumer) { + throw new UnsupportedOperationException("Not yet implemented: peek"); + } + + @Override + public R collect(Supplier resultFactory, BiConsumer accumulator, BiConsumer combiner) { + throw new UnsupportedOperationException("Not yet implemented: collect"); + } + + @Override + public R collect(Collector collector) { + if (collector instanceof DummyLazySeqCollector) { + //noinspection unchecked + return (R) underlying; + } + return doCollect(collector); + } + + private R doCollect(Collector collector) { + R result = collector.resultSupplier().get(); + LazySeq cur = underlying; + while (!cur.isEmpty()) { + result = collector.accumulator().apply(result, cur.head()); + cur = cur.tail(); + } + return result; + } + +} diff --git a/src/main/java/com/blogspot/nurkiewicz/lazyseq/Nil.java b/src/main/java/com/blogspot/nurkiewicz/lazyseq/Nil.java new file mode 100644 index 0000000..3ccf89d --- /dev/null +++ b/src/main/java/com/blogspot/nurkiewicz/lazyseq/Nil.java @@ -0,0 +1,116 @@ +package com.blogspot.nurkiewicz.lazyseq; + +import java.util.Comparator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.*; +import java.util.stream.Stream; + +class Nil extends LazySeq { + + @Override + public E head() { + throw new NoSuchElementException("head of empty stream"); + } + + @Override + public Optional headOption() { + return Optional.empty(); + } + + @Override + public LazySeq tail() { + throw new UnsupportedOperationException("tail of empty stream"); + } + + @Override + protected boolean isTailDefined() { + return false; + } + + @Override + public LazySeq map(Function mapper) { + return empty(); + } + + @Override + public LazySeq filter(Predicate predicate) { + return empty(); + } + + @Override + public LazySeq flatMap(Function> mapper) { + return empty(); + } + + @Override + public LazySeq limit(long maxSize) { + return empty(); + } + + @Override + public LazySeq substream(long startInclusive) { + return empty(); + } + + @Override + public LazySeq substream(long startInclusive, long endExclusive) { + return empty(); + } + + @Override + public void forEach(Consumer action) { + //no op + } + + @Override + public Optional min(Comparator comparator) { + return Optional.empty(); + } + + @Override + public Optional max(Comparator comparator) { + return Optional.empty(); + } + + @Override + public boolean anyMatch(Predicate predicate) { + return false; + } + + @Override + public boolean allMatch(Predicate predicate) { + return true; + } + + @Override + public LazySeq zip(LazySeq second, BiFunction zipper) { + return empty(); + } + + @Override + public LazySeq takeWhile(Predicate predicate) { + return empty(); + } + + @Override + public LazySeq dropWhile(Predicate predicate) { + return empty(); + } + + @Override + public LazySeq> sliding(int size) { + return empty(); + } + + @Override + public LazySeq scan(E initial, BinaryOperator fun) { + return of(initial); + } + + @Override + public boolean isEmpty() { + return true; + } +} \ No newline at end of file diff --git a/src/test/java/com/blogspot/nurkiewicz/lazyseq/AbstractBaseTestCase.java b/src/test/java/com/blogspot/nurkiewicz/lazyseq/AbstractBaseTestCase.java new file mode 100644 index 0000000..ceccd3b --- /dev/null +++ b/src/test/java/com/blogspot/nurkiewicz/lazyseq/AbstractBaseTestCase.java @@ -0,0 +1,17 @@ +package com.blogspot.nurkiewicz.lazyseq; + +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; + +/** + * @author Tomasz Nurkiewicz + * @since 5/10/13, 9:56 PM + */ +public class AbstractBaseTestCase { + + @BeforeMethod(alwaysRun=true) + public void injectMocks() { + MockitoAnnotations.initMocks(this); + } + +} diff --git a/src/test/java/com/blogspot/nurkiewicz/lazyseq/LazySeqHeadTest.java b/src/test/java/com/blogspot/nurkiewicz/lazyseq/LazySeqHeadTest.java new file mode 100644 index 0000000..ac052dd --- /dev/null +++ b/src/test/java/com/blogspot/nurkiewicz/lazyseq/LazySeqHeadTest.java @@ -0,0 +1,123 @@ +package com.blogspot.nurkiewicz.lazyseq; + +import org.mockito.Mock; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +import static com.blogspot.nurkiewicz.lazyseq.LazySeq.cons; +import static com.blogspot.nurkiewicz.lazyseq.LazySeq.empty; +import static org.fest.assertions.api.Assertions.assertThat; +import static org.fest.assertions.api.Assertions.failBecauseExceptionWasNotThrown; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.*; + +/** + * @author Tomasz Nurkiewicz + * @since 5/10/13, 9:56 PM + */ +public class LazySeqHeadTest extends AbstractBaseTestCase { + + @Mock + private Supplier> supplierMock; + + @Test + public void shouldFailWhenTryingToAccessHeadOfNil() throws Exception { + //given + final LazySeq empty = empty(); + + try { + //when + empty.head(); + failBecauseExceptionWasNotThrown(NoSuchElementException.class); + } catch (NoSuchElementException e) { + //then + } + } + + @Test + public void shouldReturnHeadOfFixedSeqs() throws Exception { + assertThat(LazySeq.of(1).head()).isEqualTo(1); + assertThat(LazySeq.of(2, 3).head()).isEqualTo(2); + assertThat(LazySeq.of(4, 5, 6).head()).isEqualTo(4); + assertThat(LazySeq.of(Arrays.asList(7, 8, 9, 10, 11, 12)).head()).isEqualTo(7); + } + + @Test + public void shouldReturnHeadOfDynamicLazySeq() throws Exception { + assertThat(LazySeq.of(1, tail()).head()).isEqualTo(1); + assertThat(LazySeq.of(2, 3, tail()).head()).isEqualTo(2); + assertThat(LazySeq.of(4, 5, 6, tail()).head()).isEqualTo(4); + assertThat(LazySeq.concat(Arrays.asList(7, 8, 9, 10, 11, 12), tail()).head()).isEqualTo(7); + } + + @Test + public void shouldReturnHeadOfTail() throws Exception { + assertThat(LazySeq.of(2, 3).tail().head()).isEqualTo(3); + assertThat(LazySeq.of(2, 3, tail()).tail().head()).isEqualTo(3); + assertThat(LazySeq.of(4, 5, 6, tail()).tail().head()).isEqualTo(5); + assertThat(LazySeq.concat(Arrays.asList(7, 8, 9, 10, 11, 12), tail()).tail().head()).isEqualTo(8); + } + + @Test + public void shouldNotEvaluateTailIfHeadRequested() throws Exception { + //given + LazySeq lazy = cons(1, supplierMock); + + //when + lazy.head(); + + //then + verifyZeroInteractions(supplierMock); + } + + @Test + public void shouldEvaluateTailOnlyOnceWhenTailsHeadRequested() throws Exception { + //given + LazySeq lazy = cons(1, supplierMock); + given(supplierMock.get()).willReturn(cons(2, supplierMock)); + + //when + final Integer tailsHead = lazy.tail().head(); + + //then + verify(supplierMock).get(); + assertThat(tailsHead).isEqualTo(2); + } + + @Test + public void shouldEvaluateTailOnlyTwiceOnAccessingThirdElement() throws Exception { + //given + LazySeq lazy = cons(1, supplierMock); + given(supplierMock.get()).willReturn(cons(2, supplierMock)); + + //when + final Integer tailsHead = lazy.tail().tail().head(); + + //then + verify(supplierMock, times(2)).get(); + assertThat(tailsHead).isEqualTo(2); + } + + @Test + public void shouldThrowWhenTryingToAccessHeadPastTheEndElementOfEmptySeq() throws Exception { + //given + final LazySeq twoItems = LazySeq.of(1, 2); + final LazySeq tail = twoItems.tail().tail(); + + try { + //when + tail.head(); + failBecauseExceptionWasNotThrown(NoSuchElementException.class); + } catch (NoSuchElementException e) { + //then + } + } + + private Supplier> tail() { + return () -> LazySeq.of(42); + } + +}