diff --git a/core/src/main/java/uk/gov/gchq/koryphe/iterable/ChainedIterable.java b/core/src/main/java/uk/gov/gchq/koryphe/iterable/ChainedIterable.java new file mode 100644 index 00000000..bd7de153 --- /dev/null +++ b/core/src/main/java/uk/gov/gchq/koryphe/iterable/ChainedIterable.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import org.apache.commons.lang3.ArrayUtils; + +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.io.Closeable; +import java.util.Arrays; +import java.util.Iterator; + +/** + * A {@code ChainedIterable} is a {@link java.io.Closeable} + * {@link java.lang.Iterable} composed of other {@link java.lang.Iterable}s. + * + * As a client iterates through this iterable, the child iterables are consumed + * sequentially. + * + * @param the type of items in the iterable. + */ +public class ChainedIterable implements Closeable, Iterable { + private final Iterable> iterables; + + public ChainedIterable(final Iterable... iterables) { + this(ArrayUtils.isEmpty(iterables) ? null : Arrays.asList(iterables)); + } + + public ChainedIterable(final Iterable> iterables) { + if (null == iterables) { + throw new IllegalArgumentException("iterables are required"); + } + this.iterables = iterables; + } + + @Override + public Iterator iterator() { + return new ChainedIterator<>(iterables.iterator()); + } + + @Override + public void close() { + for (final Iterable iterable : iterables) { + CloseableUtil.close(iterable); + } + } +} diff --git a/core/src/main/java/uk/gov/gchq/koryphe/iterable/ChainedIterator.java b/core/src/main/java/uk/gov/gchq/koryphe/iterable/ChainedIterator.java new file mode 100644 index 00000000..425bf2fc --- /dev/null +++ b/core/src/main/java/uk/gov/gchq/koryphe/iterable/ChainedIterator.java @@ -0,0 +1,74 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.io.Closeable; +import java.util.Collections; +import java.util.Iterator; + +/** + * @param the type of items in the iterator + */ +public class ChainedIterator implements Closeable, Iterator { + private final Iterator> iterablesIterator; + private Iterator currentIterator = Collections.emptyIterator(); + + public ChainedIterator(final Iterator> iterablesIterator) { + if (null == iterablesIterator) { + throw new IllegalArgumentException("iterables are required"); + } + this.iterablesIterator = iterablesIterator; + } + + @Override + public boolean hasNext() { + return getIterator().hasNext(); + } + + @Override + public T next() { + return getIterator().next(); + } + + @Override + public void remove() { + currentIterator.remove(); + } + + @Override + public void close() { + CloseableUtil.close(currentIterator); + while (iterablesIterator.hasNext()) { + CloseableUtil.close(iterablesIterator.next()); + } + } + + private Iterator getIterator() { + while (!currentIterator.hasNext()) { + CloseableUtil.close(currentIterator); + if (iterablesIterator.hasNext()) { + currentIterator = iterablesIterator.next().iterator(); + } else { + break; + } + } + + return currentIterator; + } +} diff --git a/core/src/main/java/uk/gov/gchq/koryphe/iterable/FilteredIterable.java b/core/src/main/java/uk/gov/gchq/koryphe/iterable/FilteredIterable.java new file mode 100644 index 00000000..14e3905d --- /dev/null +++ b/core/src/main/java/uk/gov/gchq/koryphe/iterable/FilteredIterable.java @@ -0,0 +1,68 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import com.google.common.collect.Lists; + +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.io.Closeable; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * A {@code FilteredIterable} is a {@link java.io.Closeable} + * {@link java.lang.Iterable} which can filter out elements + * based on a {@link java.util.List} of {@link java.util.function.Predicate}s. + * + * @param the type of items in the iterable. + */ +public class FilteredIterable implements Closeable, Iterable { + private final Iterable iterable; + private final List predicates; + + public FilteredIterable(final Iterable iterable, final Predicate... predicates) { + this(iterable, Lists.newArrayList(predicates)); + } + + public FilteredIterable(final Iterable iterable, final List predicates) { + if (null == iterable) { + throw new IllegalArgumentException("iterable is required"); + } + if (null == predicates) { + throw new IllegalArgumentException("List of predicates cannot be null"); + } + if (predicates.stream().anyMatch(Objects::isNull)) { + throw new IllegalArgumentException("Predicates list cannot contain a null predicate"); + } + + this.iterable = iterable; + this.predicates = predicates; + } + + @Override + public Iterator iterator() { + return new FilteredIterator<>(iterable.iterator(), predicates); + } + + @Override + public void close() { + CloseableUtil.close(iterable); + } +} diff --git a/core/src/main/java/uk/gov/gchq/koryphe/iterable/FilteredIterator.java b/core/src/main/java/uk/gov/gchq/koryphe/iterable/FilteredIterator.java new file mode 100644 index 00000000..c8e3d5be --- /dev/null +++ b/core/src/main/java/uk/gov/gchq/koryphe/iterable/FilteredIterator.java @@ -0,0 +1,99 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import uk.gov.gchq.koryphe.impl.predicate.And; +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.io.Closeable; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * @param the type of items in the iterator + */ +public class FilteredIterator implements Closeable, Iterator { + private final Iterator iterator; + private final And andPredicate; + + public FilteredIterator(final Iterator iterator, final List predicates) { + if (null == iterator) { + throw new IllegalArgumentException("iterator is required"); + } + if (null == predicates) { + throw new IllegalArgumentException("List of predicates cannot be null"); + } + if (predicates.stream().anyMatch(Objects::isNull)) { + throw new IllegalArgumentException("Predicates list cannot contain a null predicate"); + } + + this.iterator = iterator; + this.andPredicate = new And<>(predicates); + } + + private T nextElement; + private Boolean hasNext; + + @Override + public boolean hasNext() { + if (null == hasNext) { + while (iterator.hasNext()) { + final T possibleNext = iterator.next(); + if (andPredicate.test(possibleNext)) { + nextElement = possibleNext; + hasNext = true; + return true; + } + } + hasNext = false; + nextElement = null; + } + + final boolean hasNextResult = Boolean.TRUE.equals(hasNext); + if (!hasNextResult) { + close(); + } + + return hasNextResult; + } + + @Override + public T next() { + if ((null == hasNext) && (!hasNext())) { + throw new NoSuchElementException("Reached the end of the iterator"); + } + + final T elementToReturn = nextElement; + nextElement = null; + hasNext = null; + + return elementToReturn; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Cannot call remove on a " + getClass().getSimpleName()); + } + + @Override + public void close() { + CloseableUtil.close(iterator); + } +} diff --git a/core/src/main/java/uk/gov/gchq/koryphe/iterable/LimitedIterable.java b/core/src/main/java/uk/gov/gchq/koryphe/iterable/LimitedIterable.java new file mode 100644 index 00000000..99137317 --- /dev/null +++ b/core/src/main/java/uk/gov/gchq/koryphe/iterable/LimitedIterable.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.io.Closeable; +import java.util.Collections; +import java.util.Iterator; + +import static uk.gov.gchq.koryphe.util.JavaUtils.requireNonNullElse; + +/** + * A {@code LimitedIterable} is a {@link java.io.Closeable} + * {@link java.lang.Iterable} which is limited to a maximum size. + * + * @param the type of items in the iterable. + */ +public final class LimitedIterable implements Closeable, Iterable { + private final Iterable iterable; + private final int start; + private final Integer end; + private final Boolean truncate; + + public LimitedIterable(final Iterable iterable, final int start, final Integer end) { + this(iterable, start, end, true); + } + + public LimitedIterable(final Iterable iterable, final int start, final Integer end, final boolean truncate) { + if (null != end && start > end) { + throw new IllegalArgumentException("The start pointer must be less than the end pointer."); + } + + this.iterable = requireNonNullElse(iterable, Collections.emptyList()); + + this.start = start; + this.end = end; + this.truncate = truncate; + } + + @JsonIgnore + public int getStart() { + return start; + } + + @JsonIgnore + public Integer getEnd() { + return end; + } + + @Override + public void close() { + CloseableUtil.close(iterable); + } + + @Override + public Iterator iterator() { + return new LimitedIterator<>(iterable.iterator(), start, end, truncate); + } +} diff --git a/core/src/main/java/uk/gov/gchq/koryphe/iterable/LimitedIterator.java b/core/src/main/java/uk/gov/gchq/koryphe/iterable/LimitedIterator.java new file mode 100644 index 00000000..ae96ef69 --- /dev/null +++ b/core/src/main/java/uk/gov/gchq/koryphe/iterable/LimitedIterator.java @@ -0,0 +1,101 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.io.Closeable; +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import static uk.gov.gchq.koryphe.util.JavaUtils.requireNonNullElse; + +/** + * An {@code LimitedIterator} is a {@link java.io.Closeable} + * {@link java.util.Iterator} which is limited to a maximum size. This is + * achieved by iterating through the objects contained in the iterator + * until the preconfigured starting point is reached + * (and discarding these), then by retrieving objects until either: + *
    + *
  • the end of the iterator is reached, or
  • + *
  • the iterator pointer exceeds the specified limit
  • + *
+ * + * @param the type of items in the iterator. + */ +public final class LimitedIterator implements Closeable, Iterator { + private final Iterator iterator; + private final Integer end; + private int index = 0; + private Boolean truncate = true; + + public LimitedIterator(final Iterator iterator, final int start, final Integer end) { + this(iterator, start, end, true); + } + + public LimitedIterator(final Iterator iterator, final int start, final Integer end, final boolean truncate) { + if (null != end && start > end) { + throw new IllegalArgumentException("start should be less than end"); + } + + this.iterator = requireNonNullElse(iterator, Collections.emptyIterator()); + this.end = end; + this.truncate = truncate; + + while (index < start && hasNext()) { + next(); + } + } + + @Override + public void close() { + CloseableUtil.close(iterator); + } + + @Override + public boolean hasNext() { + final boolean withinLimit = (null == end || index < end); + + if (!withinLimit && !truncate && iterator.hasNext()) { + // Throw an exception if we are - not within the limit, we don't want to truncate and there are items remaining. + throw new NoSuchElementException("Limit of " + end + " exceeded."); + } + + final boolean hasNext = withinLimit && iterator.hasNext(); + if (!hasNext) { + close(); + } + + return hasNext; + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + index++; + return iterator.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/main/java/uk/gov/gchq/koryphe/iterable/MappedIterable.java b/core/src/main/java/uk/gov/gchq/koryphe/iterable/MappedIterable.java new file mode 100644 index 00000000..c45a51ec --- /dev/null +++ b/core/src/main/java/uk/gov/gchq/koryphe/iterable/MappedIterable.java @@ -0,0 +1,69 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import com.google.common.collect.Lists; + +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.io.Closeable; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +/** + * A {@code MappedIterable} is a {@link java.io.Closeable} + * {@link java.lang.Iterable} which applied a {@link java.util.List} + * of {@link java.util.function.Function}s to an {@link java.lang.Iterable}. + * + * @param input type of items in the input iterator + * @param output type of items in the output iterator + */ +public class MappedIterable implements Closeable, Iterable { + private final Iterable iterable; + private final List functions; + + public MappedIterable(final Iterable iterable, final Function... functions) { + this(iterable, Lists.newArrayList(functions)); + } + + public MappedIterable(final Iterable iterable, final List functions) { + if (null == iterable) { + throw new IllegalArgumentException("iterable is required"); + } + if (null == functions) { + throw new IllegalArgumentException("List of functions cannot be null"); + } + if (functions.stream().anyMatch(Objects::isNull)) { + throw new IllegalArgumentException("Functions list cannot contain a null function"); + } + + this.iterable = iterable; + this.functions = functions; + } + + @Override + public Iterator iterator() { + return new MappedIterator<>(iterable.iterator(), functions); + } + + @Override + public void close() { + CloseableUtil.close(iterable); + } +} diff --git a/core/src/main/java/uk/gov/gchq/koryphe/iterable/MappedIterator.java b/core/src/main/java/uk/gov/gchq/koryphe/iterable/MappedIterator.java new file mode 100644 index 00000000..b405fa80 --- /dev/null +++ b/core/src/main/java/uk/gov/gchq/koryphe/iterable/MappedIterator.java @@ -0,0 +1,71 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.io.Closeable; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +/** + * @param the type of items in the iterator + */ +public class MappedIterator implements Closeable, Iterator { + private final Iterator iterator; + private final List functions; + + public MappedIterator(final Iterator iterator, final List functions) { + if (null == iterator) { + throw new IllegalArgumentException("iterator is required"); + } + if (null == functions) { + throw new IllegalArgumentException("List of functions cannot be null"); + } + if (functions.stream().anyMatch(Objects::isNull)) { + throw new IllegalArgumentException("Functions list cannot contain a null function"); + } + + this.iterator = iterator; + this.functions = functions; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public O_ITEM next() { + Object item = iterator.next(); + try { + for (final Function function : functions) { + item = function.apply(item); + } + return (O_ITEM) item; + } catch (final ClassCastException c) { + throw new IllegalArgumentException("The input/output types of the functions were incompatible", c); + } + } + + @Override + public void close() { + CloseableUtil.close(iterator); + } +} diff --git a/core/src/main/java/uk/gov/gchq/koryphe/util/IterableUtil.java b/core/src/main/java/uk/gov/gchq/koryphe/util/IterableUtil.java index 14a78f80..3aadc445 100644 --- a/core/src/main/java/uk/gov/gchq/koryphe/util/IterableUtil.java +++ b/core/src/main/java/uk/gov/gchq/koryphe/util/IterableUtil.java @@ -16,20 +16,16 @@ package uk.gov.gchq.koryphe.util; -import com.fasterxml.jackson.annotation.JsonIgnore; +import uk.gov.gchq.koryphe.iterable.ChainedIterable; +import uk.gov.gchq.koryphe.iterable.FilteredIterable; +import uk.gov.gchq.koryphe.iterable.LimitedIterable; +import uk.gov.gchq.koryphe.iterable.MappedIterable; -import uk.gov.gchq.koryphe.impl.predicate.And; - -import java.io.Closeable; import java.util.Collections; -import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; import java.util.function.Function; import java.util.function.Predicate; -import static uk.gov.gchq.koryphe.util.JavaUtils.requireNonNullElse; - /** * An {@code IterableUtil} is a utility class providing capabilities for: *
    @@ -53,9 +49,6 @@ private IterableUtil() { * @return the lazily filtered iterable */ public static Iterable filter(final Iterable iterable, final Predicate predicate) { - if (null == predicate) { - throw new IllegalArgumentException("Predicate cannot be null"); - } return filter(iterable, Collections.singletonList(predicate)); } @@ -72,23 +65,10 @@ public static Iterable filter(final Iterable iterable, final List(iterable, predicates); } public static Iterable map(final Iterable iterable, final Function function) { - if (null == function) { - throw new IllegalArgumentException("Function cannot be null"); - } return map(iterable, Collections.singletonList(function)); } @@ -96,16 +76,6 @@ public static Iterable map(final Iterable itera if (null == iterable) { return null; } - - if (null == functions) { - throw new IllegalArgumentException("List of functions cannot be null"); - } - - for (final Function func : functions) { - if (null == func) { - throw new IllegalArgumentException("Functions list cannot contain a null function"); - } - } return new MappedIterable<>(iterable, functions); } @@ -117,320 +87,4 @@ public static Iterable limit(final Iterable iterable, final int start, return new LimitedIterable<>(iterable, start, end, truncate); } - /** - * @param input type of items in the input iterator - * @param output type of items in the output iterator - */ - private static class MappedIterable implements Closeable, Iterable { - private final Iterable iterable; - private final List functions; - - MappedIterable(final Iterable iterable, final List functions) { - this.iterable = iterable; - this.functions = functions; - } - - @Override - public Iterator iterator() { - return new MappedIterator<>(iterable.iterator(), functions); - } - - @Override - public void close() { - CloseableUtil.close(iterable); - } - } - - /** - * @param the type of items in the iterator - */ - private static class MappedIterator implements Closeable, Iterator { - private final Iterator iterator; - private final List functions; - - MappedIterator(final Iterator iterator, final List functions) { - this.iterator = iterator; - this.functions = functions; - } - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public O_ITEM next() { - Object item = iterator.next(); - try { - for (final Function function : functions) { - item = function.apply(item); - } - return (O_ITEM) item; - } catch (final ClassCastException c) { - throw new IllegalArgumentException("The input/output types of the functions were incompatible", c); - } - } - - @Override - public void close() { - CloseableUtil.close(iterator); - } - } - - /** - * @param the type of items in the iterator - */ - private static class FilteredIterable implements Closeable, Iterable { - private final Iterable iterable; - private final List predicates; - - FilteredIterable(final Iterable iterable, final List predicates) { - this.iterable = iterable; - this.predicates = predicates; - } - - @Override - public Iterator iterator() { - return new FilteredIterator<>(iterable.iterator(), predicates); - } - - @Override - public void close() { - CloseableUtil.close(iterable); - } - } - - /** - * @param the type of items in the iterator - */ - private static class FilteredIterator implements Closeable, Iterator { - private final Iterator iterator; - private final And andPredicate; - - FilteredIterator(final Iterator iterator, final List predicates) { - this.iterator = iterator; - this.andPredicate = new And<>(predicates); - } - - private T nextElement; - private Boolean hasNext; - - @Override - public boolean hasNext() { - if (null == hasNext) { - while (iterator.hasNext()) { - final T possibleNext = iterator.next(); - if (andPredicate.test(possibleNext)) { - nextElement = possibleNext; - hasNext = true; - return true; - } - } - hasNext = false; - nextElement = null; - } - - final boolean hasNextResult = Boolean.TRUE.equals(hasNext); - if (!hasNextResult) { - close(); - } - - return hasNextResult; - } - - @Override - public T next() { - if ((null == hasNext) && (!hasNext())) { - throw new NoSuchElementException("Reached the end of the iterator"); - } - - final T elementToReturn = nextElement; - nextElement = null; - hasNext = null; - - return elementToReturn; - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Cannot call remove on a " + getClass().getSimpleName()); - } - - @Override - public void close() { - CloseableUtil.close(iterator); - } - } - - /** - * @param the type of items in the iterator - */ - private static class ChainedIterable implements Closeable, Iterable { - private final Iterable> iterables; - - ChainedIterable(final Iterable> iterables) { - if (null == iterables) { - throw new IllegalArgumentException("iterables are required"); - } - this.iterables = iterables; - } - - @Override - public Iterator iterator() { - return new ChainedIterator<>(iterables.iterator()); - } - - @Override - public void close() { - for (final Iterable iterable : iterables) { - CloseableUtil.close(iterable); - } - } - } - - /** - * @param the type of items in the iterator - */ - private static class ChainedIterator implements Closeable, Iterator { - private final Iterator> iterablesIterator; - private Iterator currentIterator = Collections.emptyIterator(); - - ChainedIterator(final Iterator> iterablesIterator) { - this.iterablesIterator = iterablesIterator; - } - - @Override - public boolean hasNext() { - return getIterator().hasNext(); - } - - @Override - public T next() { - return getIterator().next(); - } - - @Override - public void remove() { - currentIterator.remove(); - } - - @Override - public void close() { - CloseableUtil.close(currentIterator); - while (iterablesIterator.hasNext()) { - CloseableUtil.close(iterablesIterator.next()); - } - } - - private Iterator getIterator() { - while (!currentIterator.hasNext()) { - CloseableUtil.close(currentIterator); - if (iterablesIterator.hasNext()) { - currentIterator = iterablesIterator.next().iterator(); - } else { - break; - } - } - - return currentIterator; - } - } - - /** - * @param the type of items in the iterator - */ - private static final class LimitedIterable implements Closeable, Iterable { - private final Iterable iterable; - private final int start; - private final Integer end; - private final Boolean truncate; - - private LimitedIterable(final Iterable iterable, final int start, final Integer end, final boolean truncate) { - if (null != end && start > end) { - throw new IllegalArgumentException("The start pointer must be less than the end pointer."); - } - - this.iterable = requireNonNullElse(iterable, Collections.emptyList()); - - this.start = start; - this.end = end; - this.truncate = truncate; - } - - @JsonIgnore - public int getStart() { - return start; - } - - @JsonIgnore - public Integer getEnd() { - return end; - } - - @Override - public void close() { - CloseableUtil.close(iterable); - } - - @Override - public Iterator iterator() { - return new LimitedIterator<>(iterable.iterator(), start, end, truncate); - } - } - - /** - * @param the type of items in the iterator - */ - private static final class LimitedIterator implements Closeable, Iterator { - private final Iterator iterator; - private final Integer end; - private int index = 0; - private Boolean truncate = true; - - private LimitedIterator(final Iterator iterator, final int start, final Integer end, final boolean truncate) { - if (null != end && start > end) { - throw new IllegalArgumentException("start should be less than end"); - } - - this.iterator = requireNonNullElse(iterator, Collections.emptyIterator()); - this.end = end; - this.truncate = truncate; - - while (index < start && hasNext()) { - next(); - } - } - - @Override - public void close() { - CloseableUtil.close(iterator); - } - - @Override - public boolean hasNext() { - final boolean withinLimit = (null == end || index < end); - - if (!withinLimit && !truncate && iterator.hasNext()) { - // Throw an exception if we are - not within the limit, we don't want to truncate and there are items remaining. - throw new NoSuchElementException("Limit of " + end + " exceeded."); - } - - final boolean hasNext = withinLimit && iterator.hasNext(); - if (!hasNext) { - close(); - } - - return hasNext; - } - - @Override - public T next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - - index++; - return iterator.next(); - } - } } diff --git a/core/src/test/java/uk/gov/gchq/koryphe/iterable/ChainedIterableTest.java b/core/src/test/java/uk/gov/gchq/koryphe/iterable/ChainedIterableTest.java new file mode 100644 index 00000000..29bb49cd --- /dev/null +++ b/core/src/test/java/uk/gov/gchq/koryphe/iterable/ChainedIterableTest.java @@ -0,0 +1,153 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import com.google.common.collect.Lists; +import org.junit.jupiter.api.Test; + +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class ChainedIterableTest { + + @SuppressWarnings("unchecked") + @Test + public void shouldThrowNSEXWhenNoNextIterableWhenOneElementAndNo2ndNext() { + final ChainedIterable chainedIterable = new ChainedIterable<>(Collections.singletonList(1)); + final Iterator iterator = chainedIterable.iterator(); + + assertThat(iterator.next()).isEqualTo(1); + // No 2nd element + assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> iterator.next()); + } + + @SuppressWarnings("unchecked") + @Test + public void shouldThrowIAXWhenArrayOfIterablesAreEmpty() { + assertThatIllegalArgumentException().isThrownBy(() -> new ChainedIterable<>()); + } + + @Test + public void shouldThrowIAXWhenArrayOfIterablesAreNull() { + assertThatIllegalArgumentException().isThrownBy(() -> new ChainedIterable<>((Iterable[]) null)); + } + + @Test + public void shouldWrapAllIterableOfIterables() { + // Given + final List itr1 = Collections.singletonList(0); + final List emptyItr2 = new ArrayList<>(0); + final List itr3 = Lists.newArrayList(1, 2, 3, 4); + final List itr4 = Lists.newArrayList(5, 6); + + // When + final List> collect = Stream.of(itr1, emptyItr2, itr3, itr4).collect(Collectors.toList()); + final ChainedIterable wrappedItr = new ChainedIterable<>(collect); + + // Then + assertThat(wrappedItr).containsExactly(0, 1, 2, 3, 4, 5, 6); + } + + @SuppressWarnings("unchecked") + @Test + public void shouldWrapAllArrayOfIterables() { + // Given + final List itr1 = Collections.singletonList(0); + final List emptyItr2 = new ArrayList<>(0); + final List itr3 = Lists.newArrayList(1, 2, 3, 4); + final List itr4 = Lists.newArrayList(5, 6); + + // When + final ChainedIterable wrappedItr = new ChainedIterable<>(itr1, emptyItr2, itr3, itr4); + + // Then + assertThat(wrappedItr).containsExactly(0, 1, 2, 3, 4, 5, 6); + } + + @SuppressWarnings({"unchecked"}) + @Test + public void shouldRemoveElementFromFirstIterable() { + // Given + final List itr1 = Lists.newArrayList("a"); + final List emptyItr2 = new ArrayList<>(0); + final List itr3 = Lists.newArrayList("b", "c", "d", "e"); + final List itr4 = Lists.newArrayList("f", "g"); + + ChainedIterable wrappedItr = null; + Iterator itr = null; + try { + wrappedItr = new ChainedIterable<>(itr1, emptyItr2, itr3, itr4); + + // When + itr = wrappedItr.iterator(); + assertThat(itr.next()).isEqualTo("a"); + + itr.remove(); + + // Then + assertThat(itr1).isEmpty(); + assertThat(emptyItr2).isEmpty(); + assertThat(itr3).hasSize(4); + assertThat(itr4).hasSize(2); + } finally { + CloseableUtil.close(itr, wrappedItr); + } + } + + @SuppressWarnings({"unchecked"}) + @Test + public void shouldRemoveElementFromThirdIterable() { + // Given + final List itr1 = Lists.newArrayList("a"); + final List emptyItr2 = new ArrayList<>(0); + final List itr3 = Lists.newArrayList("b", "c", "d", "e"); + final List itr4 = Lists.newArrayList("f", "g"); + + // When + ChainedIterable wrappedItr = null; + Iterator itr = null; + try { + wrappedItr = new ChainedIterable<>(itr1, emptyItr2, itr3, itr4); + + // When + itr = wrappedItr.iterator(); + assertThat(itr.next()).isEqualTo("a"); + assertThat(itr.next()).isEqualTo("b"); + + itr.remove(); + + // Then + assertThat(itr1).hasSize(1); + assertThat(emptyItr2).isEmpty(); + assertThat(itr3).hasSize(3); + assertThat(itr4).hasSize(2); + } finally { + CloseableUtil.close(itr, wrappedItr); + } + } +} diff --git a/core/src/test/java/uk/gov/gchq/koryphe/iterable/FilteredIterableTest.java b/core/src/test/java/uk/gov/gchq/koryphe/iterable/FilteredIterableTest.java new file mode 100644 index 00000000..c7adf2f6 --- /dev/null +++ b/core/src/test/java/uk/gov/gchq/koryphe/iterable/FilteredIterableTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import com.google.common.collect.Lists; +import org.junit.jupiter.api.Test; + +import uk.gov.gchq.koryphe.impl.predicate.IsLessThan; +import uk.gov.gchq.koryphe.impl.predicate.IsMoreThan; + +import java.util.List; +import java.util.function.Predicate; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class FilteredIterableTest { + + @Test + public void shouldThrowIAXWhenArrayOfIterablesIsNull() { + assertThatIllegalArgumentException().isThrownBy(() -> new FilteredIterable(null, Lists.newArrayList(new IsLessThan(4)))); + } + + @Test + public void shouldThrowIAXWhenListOfPredicatesIsNull() { + assertThatIllegalArgumentException().isThrownBy(() -> new FilteredIterable(Lists.newArrayList(1, 2, 3, 4, 5), (List) null)); + } + + @Test + public void shouldThrowIAXWhenOnePredicateIsNull() { + assertThatIllegalArgumentException().isThrownBy(() -> new FilteredIterable(Lists.newArrayList(1, 2, 3, 4, 5), Lists.newArrayList(new IsLessThan(4), (Predicate) null))); + } + + @Test + public void shouldCorrectlyFilterSinglePredicate() { + // Given + final List itr = Lists.newArrayList(1, 2, 3, 4, 5); + + // When + FilteredIterable filteredIterable = new FilteredIterable(itr, new IsLessThan(4)); + + // Then + assertThat(filteredIterable).containsExactly(1, 2, 3); + } + + @Test + public void shouldCorrectlyFilterMultiplePredicates() { + // Given + final List itr = Lists.newArrayList(1, 2, 3, 4, 5); + final List predicates = Lists.newArrayList(new IsLessThan(4), new IsMoreThan(1)); + + // When + FilteredIterable filteredIterable = new FilteredIterable(itr, predicates); + + // Then + assertThat(filteredIterable).containsExactly(2, 3); + } + +} diff --git a/core/src/test/java/uk/gov/gchq/koryphe/iterable/LimitedIterableTest.java b/core/src/test/java/uk/gov/gchq/koryphe/iterable/LimitedIterableTest.java new file mode 100644 index 00000000..974600ff --- /dev/null +++ b/core/src/test/java/uk/gov/gchq/koryphe/iterable/LimitedIterableTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import org.junit.jupiter.api.Test; + +import uk.gov.gchq.koryphe.util.CloseableUtil; + +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class LimitedIterableTest { + + @Test + public void shouldLimitResultsToFirstItem() { + final List values = Arrays.asList(0, 1, 2, 3); + final int start = 0; + final int end = 1; + + final Iterable limitedValues = new LimitedIterable<>(values, start, end); + assertThat(limitedValues).containsExactlyElementsOf(values.subList(start, end)); + } + + @Test + public void shouldLimitResultsToLastItem() { + final List values = Arrays.asList(0, 1, 2, 3); + final int start = 2; + final int end = Integer.MAX_VALUE; + + final Iterable limitedValues = new LimitedIterable<>(values, start, end); + assertThat(limitedValues).containsExactlyElementsOf(values.subList(start, values.size())); + } + + @Test + public void shouldNotLimitResults() { + final List values = Arrays.asList(0, 1, 2, 3); + final int start = 0; + final int end = Integer.MAX_VALUE; + + final Iterable limitedValues = new LimitedIterable<>(values, start, end); + assertThat(limitedValues).containsExactlyElementsOf(values); + } + + @Test + public void shouldReturnNoValuesWhenStartIsBiggerThanSize() { + final List values = Arrays.asList(0, 1, 2, 3); + final int start = 5; + final int end = Integer.MAX_VALUE; + + final Iterable limitedValues = new LimitedIterable<>(values, start, end); + assertThat(limitedValues).isEmpty(); + } + + @Test + public void shouldThrowIAXWhenStartIsBiggerThanEnd() { + final List values = Arrays.asList(0, 1, 2, 3); + final int start = 3; + final int end = 1; + + assertThatIllegalArgumentException().isThrownBy(() -> new LimitedIterable<>(values, start, end)); + } + + @SuppressWarnings("unused") + @Test + public void shouldThrowExceptionWhenDataIsTruncated() { + // Given + final List values = Arrays.asList(0, 1, 2, 3); + final int start = 0; + final int end = 2; + final boolean truncate = false; + + // When + final Iterable limitedValues = new LimitedIterable<>(values, start, end, truncate); + + assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> { + for (final Integer i : limitedValues) { + // Do nothing until LimitExceededException is thrown + } + }).withMessage("Limit of 2 exceeded."); + + CloseableUtil.close(limitedValues); + } + + @Test + public void shouldHandleNullIterable() { + final Iterable nullIterable = new LimitedIterable<>(null, 0, 1, true); + + assertThat(nullIterable).isEmpty(); + } + + @Test + public void shouldHandleLimitEqualToIterableLength() { + // Given + final List values = Arrays.asList(0, 1, 2, 3); + final int start = 0; + final int end = 4; + final boolean truncate = false; + + // When + Iterable equalValues = new LimitedIterable<>(values, start, end, truncate); + + // Then + assertThat(equalValues).containsExactlyElementsOf(values); + } +} diff --git a/core/src/test/java/uk/gov/gchq/koryphe/iterable/MappedIterableTest.java b/core/src/test/java/uk/gov/gchq/koryphe/iterable/MappedIterableTest.java new file mode 100644 index 00000000..e541993f --- /dev/null +++ b/core/src/test/java/uk/gov/gchq/koryphe/iterable/MappedIterableTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2022 Crown Copyright + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uk.gov.gchq.koryphe.iterable; + +import com.google.common.collect.Lists; +import org.junit.jupiter.api.Test; + +import uk.gov.gchq.koryphe.impl.function.Increment; +import uk.gov.gchq.koryphe.impl.function.MultiplyBy; + +import java.util.List; +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +public class MappedIterableTest { + + @Test + public void shouldThrowIAXWhenArrayOfIterablesIsNull() { + assertThatIllegalArgumentException().isThrownBy(() -> new MappedIterable(null, Lists.newArrayList(new Increment(4)))); + } + + @Test + public void shouldThrowIAXWhenListOfFunctionsIsNull() { + assertThatIllegalArgumentException().isThrownBy(() -> new MappedIterable(Lists.newArrayList(1, 2, 3, 4, 5), (List) null)); + } + + @Test + public void shouldThrowIAXWhenOneFunctionIsNull() { + assertThatIllegalArgumentException().isThrownBy(() -> new MappedIterable(Lists.newArrayList(1, 2, 3, 4, 5), Lists.newArrayList(new Increment(4), (Function) null))); + } + + @Test + public void shouldCorrectlyApplySingleFunction() { + // Given + final List itr = Lists.newArrayList(1, 2, 3, 4, 5); + + // When + MappedIterable mappedIterable = new MappedIterable(itr, new Increment(4)); + + // Then + assertThat(mappedIterable).containsExactly(5, 6, 7, 8, 9); + } + + @Test + public void shouldCorrectlyApplyMultipleFunctions() { + // Given + final List itr = Lists.newArrayList(1, 2, 3, 4, 5); + final List functions = Lists.newArrayList(new Increment(4), new MultiplyBy(2)); + + // When + MappedIterable mappedIterable = new MappedIterable(itr, functions); + + // Then + assertThat(mappedIterable).containsExactly(10, 12, 14, 16, 18); + } + +}