diff --git a/core/src/main/java/uk/gov/gchq/koryphe/impl/binaryoperator/IterableMerge.java b/core/src/main/java/uk/gov/gchq/koryphe/impl/binaryoperator/IterableMerge.java new file mode 100644 index 00000000..8e3a3d05 --- /dev/null +++ b/core/src/main/java/uk/gov/gchq/koryphe/impl/binaryoperator/IterableMerge.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 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.impl.binaryoperator; + +import uk.gov.gchq.koryphe.Since; +import uk.gov.gchq.koryphe.Summary; +import uk.gov.gchq.koryphe.binaryoperator.KorypheBinaryOperator; +import uk.gov.gchq.koryphe.util.IterableUtil; + +import java.util.Arrays; + +/** + * An IterableMerge is a {@link KorypheBinaryOperator} that takes two + * {@link java.lang.Iterable}s and merges them together. + */ +@Since("2.6.0") +@Summary("Merges two iterables together.") +public class IterableMerge extends KorypheBinaryOperator> { + + @Override + protected Iterable _apply(final Iterable a, final Iterable b) { + return IterableUtil.concat(Arrays.asList(a, b)); + } +} 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 index 2c38130d..e51edac4 100644 --- a/core/src/main/java/uk/gov/gchq/koryphe/iterable/ChainedIterator.java +++ b/core/src/main/java/uk/gov/gchq/koryphe/iterable/ChainedIterator.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Crown Copyright + * Copyright 2025 Crown Copyright * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import uk.gov.gchq.koryphe.util.CloseableUtil; import java.io.Closeable; +import java.util.Arrays; import java.util.Collections; import java.util.Iterator; @@ -31,6 +32,10 @@ public class ChainedIterator implements Closeable, Iterator { private final Iterator> iterablesIterator; private Iterator currentIterator = Collections.emptyIterator(); + public ChainedIterator(final Iterable... iterators) { + this((iterators == null || iterators.length == 0) ? null : Arrays.asList(iterators).iterator()); + } + public ChainedIterator(final Iterator> iterablesIterator) { if (null == iterablesIterator) { throw new IllegalArgumentException("iterables are required"); diff --git a/core/src/test/java/uk/gov/gchq/koryphe/impl/binaryoperator/IterableMergeTest.java b/core/src/test/java/uk/gov/gchq/koryphe/impl/binaryoperator/IterableMergeTest.java new file mode 100644 index 00000000..bebb0fce --- /dev/null +++ b/core/src/test/java/uk/gov/gchq/koryphe/impl/binaryoperator/IterableMergeTest.java @@ -0,0 +1,132 @@ +/* + * Copyright 2025 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.impl.binaryoperator; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +import com.google.common.collect.Lists; + +import uk.gov.gchq.koryphe.binaryoperator.BinaryOperatorTest; +import uk.gov.gchq.koryphe.iterable.ChainedIterable; +import uk.gov.gchq.koryphe.util.JsonSerialiser; + +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.TreeSet; + +class IterableMergeTest extends BinaryOperatorTest { + + @Test + public void shouldJsonSerialiseAndDeserialise() throws IOException { + // Given + final IterableMerge merger = new IterableMerge<>(); + + // When + final String json = JsonSerialiser.serialise(merger); + + // Then + JsonSerialiser.assertEquals( + String.format("{%n" + + " \"class\" : \"uk.gov.gchq.koryphe.impl.binaryoperator.IterableMerge\"%n" + + "}"), + json); + + // When + final IterableMerge deserialisedMerger = JsonSerialiser.deserialise(json, IterableMerge.class); + + // Then + assertThat(deserialisedMerger).isNotNull(); + } + + @Test + void shouldMergeArrays() { + // Given + final IterableMerge merger = new IterableMerge<>(); + final List itr1 = Lists.newArrayList(1, 2, 3, 4); + final List itr2 = Lists.newArrayList(5, 6); + + // When + final Iterable result = merger.apply(itr1, itr2); + + // Then + assertThat(result) + .isInstanceOf(ChainedIterable.class) + .containsExactly(1, 2, 3, 4, 5, 6); + } + + @Test + void shouldMergeHashSets() { + // Given + final HashSet hashSet1 = new HashSet<>(); + hashSet1.add(1); + + final HashSet hashSet2 = new HashSet<>(); + hashSet2.add(2); + hashSet2.add(3); + + IterableMerge merger = new IterableMerge<>(); + final Iterable result = merger.apply(hashSet1, hashSet2); + + assertThat(result).containsExactly(1, 2, 3); + } + + @Test + void shouldMergeTreeSets() { + // Given + final TreeSet treeSet1 = new TreeSet<>(); + treeSet1.add("string1"); + + final TreeSet treeSet2 = new TreeSet<>(); + treeSet2.add("string3"); + treeSet2.add("string2"); + + final IterableMerge merger = new IterableMerge<>(); + + // When + final Iterable result = merger.apply(treeSet1, treeSet2); + + // Then + assertThat(result).containsExactly("string1", "string2", "string3"); + } + + @Test + void shouldHandleNullElementsOfIterable() { + // Given + final IterableMerge merger = new IterableMerge<>(); + final List itr1 = Lists.newArrayList(1, 2, null, 4); + final List itr2 = Lists.newArrayList(null, 6); + + // When + final Iterable results = merger.apply(itr1, itr2); + + // Then + assertThat(results).containsExactly(1, 2, null, 4, null, 6); + } + + @Override + protected IterableMerge getInstance() { + return new IterableMerge<>(); + } + + @Override + protected Iterable getDifferentInstancesOrNull() { + return null; + } +}