diff --git a/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java index 779ec1e161ee..57664e8d97fb 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyAssignmentStatementImpl.java @@ -112,10 +112,10 @@ public class PyAssignmentStatementImpl extends PyElementImpl implements PyAssign } else if (lhs_tuple != null && rhs_target != null) { // multiple LHS, single RHS: unpacking //for (PyExpression tuple_elt : lhs_tuple.getElements()) map.add(new Pair(tuple_elt, rhs_target)); - map.addAll(FP.zip(lhs_tuple, new RepeatIterable(rhs_target))); + map.addAll(FP.zipList(lhs_tuple, new RepeatIterable(rhs_target))); } else if (lhs_tuple != null && rhs_tuple != null) { // multiple both sides: piecewise mapping - map.addAll(FP.zip(lhs_tuple, rhs_tuple, null, null)); + map.addAll(FP.zipList(lhs_tuple, rhs_tuple, null, null)); } } diff --git a/python/src/com/jetbrains/python/toolbox/ChainIterable.java b/python/src/com/jetbrains/python/toolbox/ChainIterable.java new file mode 100644 index 000000000000..6c9425ac1d9e --- /dev/null +++ b/python/src/com/jetbrains/python/toolbox/ChainIterable.java @@ -0,0 +1,97 @@ +package com.jetbrains.python.toolbox; + +import org.jetbrains.annotations.Nullable; + +import java.util.Iterator; + +/** + * Iterable that splices other iterables and iterates over them sequentially. + * User: dcheryasov + * Date: Nov 20, 2009 8:01:23 AM + */ +public class ChainIterable extends ChainedListBase> implements Iterable { + + public ChainIterable(@Nullable Iterable initial) { + super(initial); + } + + public ChainIterable() { + super(null); + } + + + public ChainIterable add(Iterable another) { + return (ChainIterable)super.add(another); + } + + /** + * Apply wrapper to another and add the result. Convenience to avoid cluttering code with apply() calls. + * @param wrapper + * @param another + * @return + */ + public ChainIterable addWith(FP.Lambda1, Iterable> wrapper, Iterable another) { + return (ChainIterable)super.add(wrapper.apply(another)); + } + + /** + * Convenience: add an item wrapping it into a SingleIterable behind the scenes. + */ + public ChainIterable add(T item) { + return (ChainIterable)super.add(new SingleIterable(item)); + } + + /** + * Convenience, works without ever touching an iterator. + * @return true if the chain contains at least one iterable (but all iterables in the chain may happen to be empty). + */ + public boolean isEmpty() { + return (myPayload == null); + } + + public Iterator iterator() { + + + class IterMixedIn extends ChainIterationMixin> { + IterMixedIn(ChainedListBase> link) { + super(link); + } + + @Override + public Iterator toIterator(Iterable first) { + return first.iterator(); + } + } + final IterMixedIn mixin = new IterMixedIn(this); + + + class Iter extends ChainedListBase> implements Iterator { + + Iter(ChainedListBase> piggybacked) { + super(piggybacked.myPayload); + myNext = piggybacked.myNext; + } + + public boolean hasNext() { + return mixin.hasNext(); + } + + public void remove() { + throw new UnsupportedOperationException(); // we don't remove things + } + + public T next() { + return (T)mixin.next(); + } + + } + + return new Iter(this); + + } + + @Override + public String toString() { + return FP.fold(new FP.StringCollector(), this, new StringBuilder()).toString(); + } +} diff --git a/python/src/com/jetbrains/python/toolbox/ChainIterationMixin.java b/python/src/com/jetbrains/python/toolbox/ChainIterationMixin.java new file mode 100644 index 000000000000..1db0d4a35ee2 --- /dev/null +++ b/python/src/com/jetbrains/python/toolbox/ChainIterationMixin.java @@ -0,0 +1,52 @@ +package com.jetbrains.python.toolbox; + +import org.jetbrains.annotations.Nullable; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Common logic of chain iterators. + * User: dcheryasov + * Date: Nov 20, 2009 9:10:39 AM + */ +/* explicitly not public */ +abstract class ChainIterationMixin { + protected ChainedListBase myLink; // link of the chain we're currently at + + protected Iterator myCurrent; + + public ChainIterationMixin(ChainedListBase link) { + myLink = link; + } + + abstract public Iterator toIterator(TPayload first); + + // returns either null or a non-exhausted iterator. + @Nullable + public Iterator getCurrent() { + while ((myCurrent == null || !myCurrent.hasNext()) && myLink.hasPayload()) { // fix myCurrent + if (myCurrent == null) { + myCurrent = toIterator(myLink.myPayload); + assert myCurrent != null; + } + else { + myLink.moveOn(); + myCurrent = null; + } + } + return myCurrent; + } + + public boolean hasNext() { + Iterator current = getCurrent(); + return (current != null); + } + + public T next() { + Iterator current = getCurrent(); + if (current != null) return current.next(); + else throw new NoSuchElementException(); + } + +} diff --git a/python/src/com/jetbrains/python/toolbox/ChainIterator.java b/python/src/com/jetbrains/python/toolbox/ChainIterator.java new file mode 100644 index 000000000000..f5a1cd7edc91 --- /dev/null +++ b/python/src/com/jetbrains/python/toolbox/ChainIterator.java @@ -0,0 +1,54 @@ +package com.jetbrains.python.toolbox; + +import org.jetbrains.annotations.Nullable; + +import java.util.Iterator; + +/** + * An iterator that combines several other iterators and exhaust them one by one, in chain. + * User: dcheryasov + * Date: Nov 19, 2009 3:49:38 AM + */ +public class ChainIterator extends ChainedListBase> implements Iterator { + + private ChainIterationMixin> myMixin; + + /** + * Creates new instance. + * @param initial initial iterator. If null, the new iterator is empty, use {@link #add} to add initial content. + */ + public ChainIterator(@Nullable Iterator initial) { + super(initial); + + myMixin = new ChainIterationMixin>(this) { + @Override + public Iterator toIterator(Iterator first) { + return first; + } + }; + } + + + /** + * Adds another iterator to the chain. Values from this iterator will follow the values of the iterator passed to the constructor. + * Adding after the iteration has started is safe. + * @param another iterator to add to the end of the chain. + * @return self, for easy chaining. + */ + public ChainIterator add(Iterator another) { + return (ChainIterator)super.add(another); + } + + + public boolean hasNext() { + return myMixin.hasNext(); + } + + public T next() { + return myMixin.next(); + } + + public void remove() { + throw new UnsupportedOperationException("Cannot remove from ChainIterator"); + } +} diff --git a/python/src/com/jetbrains/python/toolbox/ChainedListBase.java b/python/src/com/jetbrains/python/toolbox/ChainedListBase.java new file mode 100644 index 000000000000..fe74be537a80 --- /dev/null +++ b/python/src/com/jetbrains/python/toolbox/ChainedListBase.java @@ -0,0 +1,52 @@ +package com.jetbrains.python.toolbox; + +/** + * Linked list to base chain iterators an iterables on. + * User: dcheryasov + * Date: Nov 20, 2009 9:43:02 AM + */ +public /*abstract */class ChainedListBase { + protected TPayload myPayload; + protected ChainedListBase myNext; + + protected ChainedListBase(TPayload initial) { + myPayload = initial; + } + + /** + * Wrap payload into a new linked list element. + * @param payload + * @return + */ + /* + abstract protected ChainedListBase createInstance(TPayload payload); + */ + + /** + * Add another element to the end of our linked list + * @param another + * @return + */ + protected ChainedListBase add(TPayload another) { + if (myPayload == null) myPayload = another; + else { + ChainedListBase farthest = this; + while (farthest.myNext != null) farthest = farthest.myNext; + farthest.myNext = /*createInstance*/new ChainedListBase(another); + } + return this; + } + + // become to our next + public void moveOn() { + if (myNext != null) { + myPayload = myNext.myPayload; + myNext = myNext.myNext; + } + else myPayload = null; // position 'after the end' + } + + public boolean hasPayload() { + return myPayload != null; + } +} diff --git a/python/src/com/jetbrains/python/toolbox/FP.java b/python/src/com/jetbrains/python/toolbox/FP.java index 4b10c0301b65..7389568eb1d3 100644 --- a/python/src/com/jetbrains/python/toolbox/FP.java +++ b/python/src/com/jetbrains/python/toolbox/FP.java @@ -4,41 +4,81 @@ import com.intellij.openapi.util.Pair; import com.jetbrains.python.PythonDocumentationProvider; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.util.*; /** -* Tools of functional programming, the notorious half of implementation of Lisp. +* Tools of functional programming, the notorious half of implementation of Lisp. (And sometimes a shard or two of Haskell.) * User: dcheryasov * Date: Nov 6, 2009 10:06:50 AM */ public class FP { + + private FP() { + // do not instantiate + } + /** * [a, b,..] -> [lambda(a), lambda(b),...] + * Action is lazy: function is applied to source items only as soon as values are extracted from the resulting iterable. * @param lambda function to apply * @param source list to process - * @param type of source elements - * @param type of result elements * @return list of mapped values. */ @NotNull - public static List map(@NotNull Lambda1 lambda, @NotNull List source) { - List ret = new ArrayList(source.size()); - for (S item : source) ret.add(lambda.apply(item)); + public static Iterable map(@NotNull final Lambda1 lambda, @NotNull final Iterable source) { + + return new Iterable() { + final Iterator feeder = source.iterator(); + public Iterator iterator() { + return new Iterator() { + + public boolean hasNext() { + return feeder.hasNext(); + } + + public R next() { + return lambda.apply(feeder.next()); + } + + public void remove() { + throw new UnsupportedOperationException("Cannot remove from map()"); + } + }; + } + }; + } + + /** + * Convenience form of {@link #map(Lambda1, Iterable)}. + */ + @NotNull + public static Iterable map(@NotNull final Lambda1 lambda, @NotNull final S[] source) { + return map(lambda, Arrays.asList(source)); + } + + + /** + * Same as {@link #map}, but non-lazy an returns a modifiable List. + */ + public static List mapList(@NotNull final Lambda1 lambda, @NotNull final Iterable source) { + List ret = new ArrayList(source instanceof Collection? ((Collection)source).size() : 10); + for (R what : map(lambda, source)) ret.add(what); return ret; } + /** * Apply a two-argument lambda to each sequence element and an accumulator. * @param lambda function to apply; accumulator is the first parameter, sequence item is the second * @param source sequence to process * @param unit initial value of the accumulator (like the initial 0 for summing) + * @param type of items in the list + * @param 'accumulator' type; can be same as ItemT, or reasonably different (consider ItemT=String and AccT=StringBuilder) * @return value of the accumulator after all the list is processed. */ - public static R fold(@NotNull Lambda2 lambda, @NotNull Iterable source, @NotNull final R unit) { - R ret = unit; - for (R item : source) ret = lambda.apply(ret, item); + public static AccT fold(@NotNull Lambda2 lambda, @NotNull Iterable source, @NotNull final AccT unit) { + AccT ret = unit; + for (ItemT item : source) ret = lambda.apply(ret, item); return ret; } @@ -47,63 +87,94 @@ public class FP { * @param lambda function to apply; sequence item is the first parameter, accumulator is the second * @param source sequence to process * @param unit initial value of the accumulator (like the initial 0 for summing) - * @return value of the accumulator after all the sequence is processed. + * @param type of items in the list + * @param 'accumulator' type + * @return value of the accumulator after all the list is processed. */ - public static R foldr(@NotNull Lambda2 lambda, @NotNull Iterable source, @NotNull final R unit) { - R ret = unit; - for (R item : source) ret = lambda.apply(item, ret); + public static AccT foldr(@NotNull Lambda2 lambda, @NotNull Iterable source, @NotNull final AccT unit) { + AccT ret = unit; + for (ItemT item : source) ret = lambda.apply(item, ret); return ret; } /** * Zips together two sequences: [a, b,..] + [x, y,..] -> [(a, x), (b, y),..]. * If sequences are of different length, uses up the shortest of the sequences; the rest of the longer sequence is unused. + * The action is lazy: either iterable is only accessed as many times as the result. * @param one source of first elements * @param two source of second elements, possibly shorter * @return list of pairs of elements */ - public static List> zip(Iterable one, Iterable two) { + public static Iterable> zip(Iterable one, Iterable two) { return zipInternal(one, two, null, null, false, false); } + /** + * Same as {@link #zip(Iterable, Iterable)}, but non-lazy and returns a modifiable List. + */ + public static List> zipList(Iterable one, Iterable two) { + List> ret = new ArrayList>(proposeZippedListLength(one, two, false, false)); + for (Pairwhat : zipInternal(one, two, null, null, false, false)) ret.add(what); + return ret; + } + /** * Zips together two sequences: [a, b,..] + [x, y,..] -> [(a, x), (b, y),..]. Fills missing second elements with filler. * Always uses up entire sequence one; if sequence two is longer, part of it is unused. + * The action is lazy: either iterable is only accessed as many times as the result. * @param one source of first elements * @param two source of second elements, possibly shorter * @param filler value to use instead of elements of sequence two if it is shorter than sequence one * @return list of pairs of elements */ - public static List> zip(Iterable one, Iterable two, R2 filler) { + public static Iterable> zip(Iterable one, Iterable two, R2 filler) { return zipInternal(one, two, null, filler, false, true); } + /** + * Same as {@link #zip(Iterable, Iterable, Object)}, but non-lazy and returns a modifiable List. + */ + public static List> zipList(Iterable one, Iterable two, R2 filler) { + List> ret = new ArrayList>(proposeZippedListLength(one, two, false, true)); + for (Pairwhat : zipInternal(one, two, null, filler, false, true)) ret.add(what); + return ret; + } + + /** * Zips together two sequences: [a, b,..] + [x, y,..] -> [(a, x), (b, y),..]. Fills all missing elements with filler. * Always uses up both sequences, using the appropriate filler for elements of the shorter sequences. + * The action is lazy: either iterable is only accessed as many times as the result. * @param one sequences of first elements * @param two sequences of second elements, possibly shorter * @param filler1 value to use instead of elements of sequences one if it is shorter than list two * @param filler2 value to use instead of elements of sequences two if it is shorter than list one * @return list of pairs of elements */ - public static List> zip(Iterable one, Iterable two, R1 filler1, R2 filler2) { + public static Iterable> zip(Iterable one, Iterable two, R1 filler1, R2 filler2) { return zipInternal(one, two, filler1, filler2, true, true); } /** - * Zips two lists. - * @param one first elements - * @param two second elements - * @param filler1 to fill missing first elements - * @param filler2 to fill missing second elements - * @param fill1 use filler1 if list one is too short - * @param fill2 use filler2 if list two is too short - * @return zipped list + * Same as {@link #zip(Iterable, Iterable, Object, Object)}, but non-lazy and returns a modifiable List. */ - private static List> zipInternal(Iterable one, Iterable two, R1 filler1, R2 filler2, boolean fill1, boolean fill2) { - // a boring premature optimization which tries to preallocate an array list of exactly the right size + public static List> zipList(Iterable one, Iterable two, R1 filler1, R2 filler2) { + List> ret = new ArrayList>(proposeZippedListLength(one, two, true, true)); + for (Pairwhat : zipInternal(one, two, filler1, filler2, true, true)) ret.add(what); + return ret; + } + + /** + * Tries to determine the size of an array list of exactly the right size to accommodate + * the result of {@link #zip(Iterable, Iterable, Object, Object)}. + * @param one first iterbale + * @param two second iterable + * @param fill1 true if padding of iterable one is required + * @param fill2 true if padding of iterable two is required + * @return size, if it can be determined, or 10 (which is the default size of ArrayList). + */ + public static int proposeZippedListLength(Iterable one, Iterable two, boolean fill1, boolean fill2) { int size1 = 0; int size2 = 0; int approx_size = 0; @@ -114,14 +185,53 @@ public class FP { if (fill1 && !fill2) approx_size = size1; if (!fill1 && fill2) approx_size = size2; if (approx_size == 0) approx_size = 10; - List> ret = new ArrayList>(approx_size); - // the gist - Iterator one_iter = one.iterator(); - Iterator two_iter = two.iterator(); - while (one_iter.hasNext() && two_iter.hasNext()) ret.add(new Pair(one_iter.next(), two_iter.next())); - while (fill1 && two_iter.hasNext()) ret.add(new Pair(filler1, two_iter.next())); - while (fill2 && one_iter.hasNext()) ret.add(new Pair(one_iter.next(), filler2)); - return ret; + return approx_size; + } + + /** + * Zips two lists. + * @param one first elements + * @param two second elements + * @param filler1 to fill missing first elements + * @param filler2 to fill missing second elements + * @param fill1 use filler1 if list one is too short + * @param fill2 use filler2 if list two is too short + * @return zipped list + */ + private static Iterable> zipInternal( + Iterable one, Iterable two, final R1 filler1, final R2 filler2, final boolean fill1, final boolean fill2 + ) { + final Iterator one_iter = one.iterator(); + final Iterator two_iter = two.iterator(); + + return new Iterable>() { + public Iterator> iterator() { + + return new Iterator>() { + + public void remove() { + throw new UnsupportedOperationException("Cannot remove from zip()"); + } + + public boolean hasNext() { + final boolean one_has = one_iter.hasNext(); + final boolean two_has = two_iter.hasNext(); + return ( + one_has && two_has || + fill1 && two_has || + fill2 && one_has + ); + } + + public Pair next() { + if (one_iter.hasNext() && two_iter.hasNext()) return new Pair(one_iter.next(), two_iter.next()); + if (fill1 && two_iter.hasNext()) return new Pair(filler1, two_iter.next()); + if (fill2 && one_iter.hasNext()) return new Pair(one_iter.next(), filler2); + throw new NoSuchElementException(); + } + }; + } + }; } /** @@ -153,4 +263,15 @@ public class FP { public interface Lambda2 { R apply(A1 arg1, A2 arg2); } + + + /** + * Useful for {@link FP#fold(Lambda2, Iterable, Object) fold}ing into a string. Element's {@code .toString()} is appended to the string builder. + */ + public static class StringCollector implements FP.Lambda2 { + public StringBuilder apply(StringBuilder builder, T arg2) { + return builder.append(arg2.toString()); + } + } + } diff --git a/python/src/com/jetbrains/python/toolbox/SingleIterator.java b/python/src/com/jetbrains/python/toolbox/SingleIterator.java index a02ab54c9431..dfc0148e8d37 100644 --- a/python/src/com/jetbrains/python/toolbox/SingleIterator.java +++ b/python/src/com/jetbrains/python/toolbox/SingleIterator.java @@ -8,12 +8,12 @@ import java.util.NoSuchElementException; * User: dcheryasov * Date: Nov 6, 2009 9:57:41 AM */ -class SingleIterator implements Iterator { +public class SingleIterator implements Iterator { boolean expired; private T content; - SingleIterator(T content) { + public SingleIterator(T content) { this.content = content; expired = false; } diff --git a/python/testSrc/com/jetbrains/python/toolbox/FPTest.java b/python/testSrc/com/jetbrains/python/toolbox/FPTest.java new file mode 100644 index 000000000000..549c4840636f --- /dev/null +++ b/python/testSrc/com/jetbrains/python/toolbox/FPTest.java @@ -0,0 +1,89 @@ +package com.jetbrains.python.toolbox; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * Tests basic FP stuff. + * User: dcheryasov + * Date: Nov 20, 2009 6:46:25 AM + */ +public class FPTest extends TestCase { + + public void testMap() { + List sequence = Arrays.asList("a", "b", "c"); + FP.Lambda1 func = new FP.Lambda1() { + public String apply(String arg) { + return arg.toUpperCase(); + } + }; + + int count = 0; + for (String what : FP.map(func, sequence)) { + assertEquals(sequence.get(count).toUpperCase(), what); + count += 1; + } + assertEquals(sequence.size(), count); + } + + public void testMapEmpty() { + List sequence = Arrays.asList(); + FP.Lambda1 func = new FP.Lambda1() { + public String apply(String arg) { + return arg.toUpperCase(); + } + }; + + int count = 0; + for (String what : FP.map(func, sequence)) { + count += 1; // this never happens + } + assertEquals(sequence.size(), count); + } + + + public void testMapLazy() { + List sequence = Arrays.asList(1.0f, 2.0f, 3.0f, 0.0f); + FP.Lambda1 func = new FP.Lambda1() { + public Float apply(Float arg) { + return 1.0f/arg; + } + }; + + int count = 0; + Iterator iterator = FP.map(func, sequence).iterator(); + while (iterator.hasNext() && count < 3) { // func is applied on the fly and will not be calculated for 4th arg + iterator.next(); + count += 1; + } + assertEquals(3, count); + } + + + public void testFold() { + List sequence = Arrays.asList("a", "b", "c"); + FP.Lambda2 adder = new FP.Lambda2() { + public StringBuilder apply(StringBuilder builder, String arg2) { + return builder.append(arg2); + } + }; + StringBuilder result = FP.fold(adder, sequence, new StringBuilder()); + assertEquals("abc", result.toString()); + } + + public void testFoldr() { + List sequence = Arrays.asList("a", "b", "c"); + FP.Lambda2 adder = new FP.Lambda2() { + public String apply(String left, String right) { + return left + right; + } + }; + String result = FP.foldr(adder, sequence, ""); + assertEquals("cba", result); + } + +} diff --git a/python/testSrc/com/jetbrains/python/toolbox/IteratorsTest.java b/python/testSrc/com/jetbrains/python/toolbox/IteratorsTest.java new file mode 100644 index 000000000000..a7ca9c321ad4 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/toolbox/IteratorsTest.java @@ -0,0 +1,227 @@ +package com.jetbrains.python.toolbox; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Tests all iterators and iterables. + * User: dcheryasov + * Date: Nov 20, 2009 3:42:51 AM + */ +public class IteratorsTest extends TestCase { + + public IteratorsTest() { + super(); + } + + public void testSingleIterable() { + final String value = "foo"; + int count = 0; + SingleIterable tested = new SingleIterable(value); + for (String what : tested) { + assertEquals(value, what); + count += 1; + } + assertEquals(1, count); + } + + public void testArrayIterable() { + final String[] values = {"foo", "bar", "baz"}; + int count = 0; + ArrayIterable tested = new ArrayIterable(values); + for (String what : tested) { + assertEquals(values[count], what); + count += 1; + } + assertEquals(values.length, count); + } + + public void testArrayIterableEmpty() { + final String[] values = {}; + int count = 0; + ArrayIterable tested = new ArrayIterable(values); + for (String what : tested) { + count += 1; + } + assertEquals(values.length, count); + } + + public void testRepeatIterable() { + String value = "foo"; + RepeatIterable tested = new RepeatIterable(value); + int count = 0; + int times = 10; + for (String what : tested) { + assertEquals(value, what); + count += 1; + if (count >= times) break; + } + assertEquals(times, count); + } + + public void testChainIterableByLists() { + List list1 = Arrays.asList("foo", "bar", "baz"); + List list2 = Arrays.asList("ichi", "ni", "san"); + List list3 = Arrays.asList("a", "s", "d", "f"); + List all = new ArrayList(); + all.addAll(list1); + all.addAll(list2); + all.addAll(list3); + ChainIterable tested = new ChainIterable(list1).add(list2).add(list3); + int count = 0; + for (String what : tested) { + assertEquals(all.get(count), what); + count += 1; + } + assertEquals(all.size(), count); + } + + public void testChainIterableEmptyFirst() { + List list1 = Arrays.asList(); + List list2 = Arrays.asList("ichi", "ni", "san"); + List list3 = Arrays.asList("a", "s", "d", "f"); + List all = new ArrayList(); + all.addAll(list1); + all.addAll(list2); + all.addAll(list3); + ChainIterable tested = new ChainIterable(list1).add(list2).add(list3); + int count = 0; + for (String what : tested) { + assertEquals(all.get(count), what); + count += 1; + } + assertEquals(all.size(), count); + } + + public void testChainIterableEmptyLast() { + List list1 = Arrays.asList("foo", "bar", "baz"); + List list2 = Arrays.asList("ichi", "ni", "san"); + List list3 = Arrays.asList(); + List all = new ArrayList(); + all.addAll(list1); + all.addAll(list2); + all.addAll(list3); + ChainIterable tested = new ChainIterable(list1).add(list2).add(list3); + int count = 0; + for (String what : tested) { + assertEquals(all.get(count), what); + count += 1; + } + assertEquals(all.size(), count); + } + + public void testChainIterableEmptyMiddle() { + List list1 = Arrays.asList("foo", "bar", "baz"); + List list2 = Arrays.asList(); + List list3 = Arrays.asList("a", "s", "d", "f"); + List all = new ArrayList(); + all.addAll(list1); + all.addAll(list2); + all.addAll(list3); + ChainIterable tested = new ChainIterable(list1).add(list2).add(list3); + int count = 0; + for (String what : tested) { + assertEquals(all.get(count), what); + count += 1; + } + assertEquals(all.size(), count); + } + + + public void testChainIteratorBySingles() { + final String[] values = {"foo", "bar", "baz"}; + SingleIterator zero = new SingleIterator(values[0]); + SingleIterator one = new SingleIterator(values[1]); + SingleIterator two = new SingleIterator(values[2]); + ChainIterator tested = new ChainIterator(zero).add(one).add(two); + int count = 0; + String what; + while (tested.hasNext()) { + what = tested.next(); + assertEquals(values[count], what); + count += 1; + } + assertEquals(values.length, count); + } + + public void testChainIteratorByLists() { + List list1 = Arrays.asList("foo", "bar", "baz"); + List list2 = Arrays.asList("ichi", "ni", "san"); + List list3 = Arrays.asList("a", "s", "d", "f"); + List all = new ArrayList(); + all.addAll(list1); + all.addAll(list2); + all.addAll(list3); + ChainIterator tested = new ChainIterator(list1.iterator()).add(list2.iterator()).add(list3.iterator()); + int count = 0; + String what; + while (tested.hasNext()) { + what = tested.next(); + assertEquals(all.get(count), what); + count += 1; + } + assertEquals(all.size(), count); + } + + public void testChainIteratorEmptyFirst() { + List list1 = Arrays.asList(); + List list2 = Arrays.asList("ichi", "ni", "san"); + List list3 = Arrays.asList("a", "s", "d", "f"); + List all = new ArrayList(); + all.addAll(list1); + all.addAll(list2); + all.addAll(list3); + ChainIterator tested = new ChainIterator(list1.iterator()).add(list2.iterator()).add(list3.iterator()); + int count = 0; + String what; + while (tested.hasNext()) { + what = tested.next(); + assertEquals(all.get(count), what); + count += 1; + } + assertEquals(all.size(), count); + } + + public void testChainIteratorEmptyLast() { + List list1 = Arrays.asList("foo", "bar", "baz"); + List list2 = Arrays.asList("ichi", "ni", "san"); + List list3 = Arrays.asList(); + List all = new ArrayList(); + all.addAll(list1); + all.addAll(list2); + all.addAll(list3); + ChainIterator tested = new ChainIterator(list1.iterator()).add(list2.iterator()).add(list3.iterator()); + int count = 0; + String what; + while (tested.hasNext()) { + what = tested.next(); + assertEquals(all.get(count), what); + count += 1; + } + assertEquals(all.size(), count); + } + + public void testChainIteratorEmptyMiddle() { + List list1 = Arrays.asList("foo", "bar", "baz"); + List list2 = Arrays.asList(); + List list3 = Arrays.asList("a", "s", "d", "f"); + List all = new ArrayList(); + all.addAll(list1); + all.addAll(list2); + all.addAll(list3); + ChainIterator tested = new ChainIterator(list1.iterator()).add(list2.iterator()).add(list3.iterator()); + int count = 0; + String what; + while (tested.hasNext()) { + what = tested.next(); + assertEquals(all.get(count), what); + count += 1; + } + assertEquals(all.size(), count); + } + + +}