From 7540ff32b6ea802b7fe6a7e7c8a6ddcc7f35e954 Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 19 Jun 2020 17:56:26 -0700 Subject: [PATCH] DATAGEODE-302 - Introduce CompositeTypeFilter @FunctionalInterface extending Spring's o.s.core.type.filter.TypeFilter interface. The SDG CompositeTypeFilter uses the Composite Software Design Pattern to compose multiple TypeFilters as a single, uniform instance of TypeFilter. --- .../gemfire/support/CompositeTypeFilter.java | 196 +++++++ .../support/CompositeTypeFilterUnitTests.java | 527 ++++++++++++++++++ 2 files changed, 723 insertions(+) create mode 100644 src/main/java/org/springframework/data/gemfire/support/CompositeTypeFilter.java create mode 100644 src/test/java/org/springframework/data/gemfire/support/CompositeTypeFilterUnitTests.java diff --git a/src/main/java/org/springframework/data/gemfire/support/CompositeTypeFilter.java b/src/main/java/org/springframework/data/gemfire/support/CompositeTypeFilter.java new file mode 100644 index 00000000..0f95ec8a --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/support/CompositeTypeFilter.java @@ -0,0 +1,196 @@ +/* + * Copyright 2020 the original author or authors. + * + * 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 + * + * https://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 org.springframework.data.gemfire.support; + +import java.util.Arrays; + +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.gemfire.util.ArrayUtils; +import org.springframework.data.gemfire.util.CollectionUtils; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +/** + * A Spring {@link TypeFilter} implementation using the {@literal Composite Software Design Pattern} to compose multiple + * {@link TypeFilter TypeFilters} acting a single instance of {@link TypeFilter}. + * + * @author John Blum + * @see org.springframework.core.type.filter.TypeFilter + * @see Composite Software Design Pattern + * @since 2.4.0 + */ +@FunctionalInterface +@SuppressWarnings("unused") +public interface CompositeTypeFilter extends TypeFilter { + + CompositeTypeFilter ALLOW = (metadataReader, metadataReaderFactory) -> true; + CompositeTypeFilter DENY = (metadataReader, metadataReaderFactory) -> false; + + /** + * Composes {@literal this} {@link TypeFilter} with the given {@link TypeFilter} + * using the {@literal logical AND operator}. + * + * @param typeFilter {@link TypeFilter} to compose with {@literal this} {@link TypeFilter}; + * must not be {@literal null}. + * @return a composed {@link TypeFilter} consisting of {@literal this} {@link TypeFilter} + * and the given {@link TypeFilter}. + * @throws IllegalArgumentException if {@link TypeFilter} is {@literal null}. + * @see org.springframework.core.type.filter.TypeFilter + * @see #orThen(TypeFilter) + */ + default @NonNull CompositeTypeFilter andThen(@NonNull TypeFilter typeFilter) { + + Assert.notNull(typeFilter, "TypeFilter must not be null"); + + return (metadataReader, metadataReaderFactory) -> + this.match(metadataReader, metadataReaderFactory) + && typeFilter.match(metadataReader, metadataReaderFactory); + } + + /** + * Composes the array of {@link TypeFilter TypeFilters} into a {@literal Composite} {@link TypeFilter} + * that acts as a single instance of {@link TypeFilter}. + * + * The {@literal Composite} {@link TypeFilter} may be treated as a single instance of {@link TypeFilter}, too, + * given {@link CompositeTypeFilter} extends (i.e. "is a") {@link TypeFilter}. + * + * @param array array of {@link TypeFilter TypeFilters} to compose. + * @return a {@literal Composite} {@link TypeFilter} composed from the array of {@link TypeFilter TypeFilers} + * using the {@literal logical AND operator}; may return {@literal null}. + * @see org.springframework.core.type.filter.TypeFilter + * @see #composeAnd(Iterable) + */ + static @Nullable TypeFilter composeAnd(@Nullable TypeFilter... array) { + return composeAnd(Arrays.asList(ArrayUtils.nullSafeArray(array, TypeFilter.class))); + } + + /** + * Composes the {@link Iterable} of {@link TypeFilter TypeFilters} into a {@literal Composite} {@link TypeFilter} + * that acts as a single instance of {@link TypeFilter}. + * + * The {@literal Composite} {@link TypeFilter} may be treated as a single instance of {@link TypeFilter}, too, + * given {@link CompositeTypeFilter} extends (i.e. "is a") {@link TypeFilter}. + * + * @param iterable {@link Iterable} of {@link TypeFilter TypeFilters} to compose. + * @return a {@literal Composite} {@link TypeFilter} composed from the {@link Iterable} + * of {@link TypeFilter TypeFilers} using the {@literal logical AND operator}; may return {@literal null}. + * @see org.springframework.core.type.filter.TypeFilter + * @see java.lang.Iterable + */ + static @Nullable TypeFilter composeAnd(@Nullable Iterable iterable) { + + CompositeTypeFilter current = null; + + for (TypeFilter typeFilter : CollectionUtils.nullSafeIterable(iterable)) { + if (typeFilter != null) { + current = current == null ? of(typeFilter) : current.andThen(typeFilter); + } + } + + return current; + } + + /** + * Composes the array of {@link TypeFilter TypeFilters} into a {@literal Composite} {@link TypeFilter} + * that acts as a single instance of {@link TypeFilter}. + * + * The {@literal Composite} {@link TypeFilter} may be treated as a single instance of {@link TypeFilter}, too, + * given {@link CompositeTypeFilter} extends (i.e. "is a") {@link TypeFilter}. + * + * @param array array of {@link TypeFilter TypeFilters} to compose. + * @return a {@literal Composite} {@link TypeFilter} composed from the array of {@link TypeFilter TypeFilers} + * using the {@literal logical OR operator}; may return {@literal null}. + * @see org.springframework.core.type.filter.TypeFilter + * @see #composeOr(Iterable) + */ + static @Nullable TypeFilter composeOr(@Nullable TypeFilter... array) { + return composeOr(Arrays.asList(ArrayUtils.nullSafeArray(array, TypeFilter.class))); + } + + /** + * Composes the {@link Iterable} of {@link TypeFilter TypeFilters} into a {@literal Composite} {@link TypeFilter} + * that acts as a single instance of {@link TypeFilter}. + * + * The {@literal Composite} {@link TypeFilter} may be treated as a single instance of {@link TypeFilter}, too, + * given {@link CompositeTypeFilter} extends (i.e. "is a") {@link TypeFilter}. + * + * @param iterable {@link Iterable} of {@link TypeFilter TypeFilters} to compose. + * @return a {@literal Composite} {@link TypeFilter} composed from the {@link Iterable} + * of {@link TypeFilter TypeFilers} using the {@literal logical OR operator}; may return {@literal null}. + * @see org.springframework.core.type.filter.TypeFilter + * @see java.lang.Iterable + */ + static @Nullable TypeFilter composeOr(@Nullable Iterable iterable) { + + CompositeTypeFilter current = null; + + for (TypeFilter typeFilter : CollectionUtils.nullSafeIterable(iterable)) { + if (typeFilter != null) { + current = current == null ? of(typeFilter) : current.orThen(typeFilter); + } + } + + return current; + } + + /** + * Wraps an existing {@link TypeFilter} in an instance of {@link CompositeTypeFilter}. + * + * @param typeFilter {@link TypeFilter} to wrap; must not be {@literal null}. + * @return a {@link CompositeTypeFilter} wrapping the existing {@link TypeFilter}. + * @throws IllegalArgumentException if {@link TypeFilter} is {@literal null}. + * @see org.springframework.core.type.filter.TypeFilter + */ + static @NonNull CompositeTypeFilter of(@NonNull TypeFilter typeFilter) { + + Assert.notNull(typeFilter, "TypeFilter to wrap must not be null"); + + return (metadataReader, metadataReaderFactory) -> typeFilter.match(metadataReader, metadataReaderFactory); + } + + /** + * Negates the result of {@literal this} {@link TypeFilter TypeFilter's} + * {@link #match(MetadataReader, MetadataReaderFactory)} operation. + * + * @return {@literal this} {@link TypeFilter} negated. + * @see org.springframework.data.gemfire.support.CompositeTypeFilter + */ + default @NonNull CompositeTypeFilter negate() { + return (metadataReader, metadataReaderFactory) -> !this.match(metadataReader, metadataReaderFactory); + } + + /** + * Composes {@literal this} {@link TypeFilter} with the given {@link TypeFilter} + * using the {@literal logical OR operator}. + * + * @param typeFilter {@link TypeFilter} to compose with {@literal this} {@link TypeFilter}; + * must not be {@literal null}. + * @return a composed {@link TypeFilter} consisting of {@literal this} {@link TypeFilter} + * and the given {@link TypeFilter}. + * @throws IllegalArgumentException if {@link TypeFilter} is {@literal null}. + * @see org.springframework.core.type.filter.TypeFilter + * @see #andThen(TypeFilter) + */ + default @NonNull CompositeTypeFilter orThen(@NonNull TypeFilter typeFilter) { + + return (metadataReader, metadataReaderFactory) -> + this.match(metadataReader, metadataReaderFactory) + || typeFilter.match(metadataReader, metadataReaderFactory); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/support/CompositeTypeFilterUnitTests.java b/src/test/java/org/springframework/data/gemfire/support/CompositeTypeFilterUnitTests.java new file mode 100644 index 00000000..32644c72 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/CompositeTypeFilterUnitTests.java @@ -0,0 +1,527 @@ +/* + * Copyright 2020 the original author or authors. + * + * 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 + * + * https://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 org.springframework.data.gemfire.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import java.io.IOException; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; + +/** + * Unit Tests for {@link CompositeTypeFilter}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.core.type.filter.TypeFilter + * @see org.springframework.data.gemfire.support.CompositeTypeFilter + * @since 2.4.0 + */ +public class CompositeTypeFilterUnitTests { + + @Test + public void allowTypeFilterMatchReturnsTrue() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + assertThat(CompositeTypeFilter.ALLOW.match(mockMetadataReader, mockMetadataReaderFactory)).isTrue(); + } + + @Test + public void denyTypeFilterMatchReturnsFalse() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + assertThat(CompositeTypeFilter.DENY.match(mockMetadataReader, mockMetadataReaderFactory)).isFalse(); + } + + @Test + public void ofWrapsExistingTypeFilter() { + + TypeFilter mockTypeFilter = mock(TypeFilter.class); + + CompositeTypeFilter compositeTypeFilter = CompositeTypeFilter.of(mockTypeFilter); + + assertThat(compositeTypeFilter).isNotNull(); + } + + @Test(expected = IllegalArgumentException.class) + public void ofWithNullTypeFilterThrowsIllegalArgumentException() { + + try { + CompositeTypeFilter.of(null); + } + catch (IllegalArgumentException expected) { + + assertThat(expected).hasMessageContaining("TypeFilter to wrap must not be null"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test + public void andThenComposesTrueTrueTypeFilters() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + + doReturn(true).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(true).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + assertThat(CompositeTypeFilter.of(mockTypeFilterOne) + .andThen(mockTypeFilterTwo) + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isTrue(); + + InOrder inOrder = Mockito.inOrder(mockTypeFilterOne, mockTypeFilterTwo); + + inOrder.verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + inOrder.verify(mockTypeFilterTwo, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void andThenComposesTrueFalseTypeFilters() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + + doReturn(true).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(false).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + assertThat(CompositeTypeFilter.of(mockTypeFilterOne) + .andThen(mockTypeFilterTwo) + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isFalse(); + + InOrder inOrder = Mockito.inOrder(mockTypeFilterOne, mockTypeFilterTwo); + + inOrder.verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + inOrder.verify(mockTypeFilterTwo, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void andThenComposesFalseTrueTypeFilters() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + + doReturn(false).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(true).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + assertThat(CompositeTypeFilter.of(mockTypeFilterOne) + .andThen(mockTypeFilterTwo) + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isFalse(); + + InOrder inOrder = Mockito.inOrder(mockTypeFilterOne, mockTypeFilterTwo); + + inOrder.verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + inOrder.verify(mockTypeFilterTwo, never()).match(any(), any()); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void andThenComposesFalseFalseTypeFilters() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + + doReturn(false).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(false).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + assertThat(CompositeTypeFilter.of(mockTypeFilterOne) + .andThen(mockTypeFilterTwo) + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isFalse(); + + InOrder inOrder = Mockito.inOrder(mockTypeFilterOne, mockTypeFilterTwo); + + inOrder.verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + inOrder.verify(mockTypeFilterTwo, never()).match(any(), any()); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void negateTrueIsCorrect() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilter = mock(TypeFilter.class); + + doReturn(true).when(mockTypeFilter).match(any(), any()); + + assertThat(CompositeTypeFilter.of(mockTypeFilter) + .negate() + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isFalse(); + + verify(mockTypeFilter, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verifyNoMoreInteractions(mockTypeFilter); + } + + @Test + public void negateFalseIsCorrect() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilter = mock(TypeFilter.class); + + doReturn(false).when(mockTypeFilter).match(any(), any()); + + assertThat(CompositeTypeFilter.of(mockTypeFilter) + .negate() + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isTrue(); + + verify(mockTypeFilter, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verifyNoMoreInteractions(mockTypeFilter); + } + + @Test + public void orThenComposesTrueTrueTypeFilters() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + + doReturn(true).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(true).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + assertThat(CompositeTypeFilter.of(mockTypeFilterOne) + .orThen(mockTypeFilterTwo) + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isTrue(); + + InOrder inOrder = Mockito.inOrder(mockTypeFilterOne, mockTypeFilterTwo); + + inOrder.verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + inOrder.verify(mockTypeFilterTwo, never()).match(any(), any()); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void orThenComposesTrueFalseFilters() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + + doReturn(true).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(false).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + assertThat(CompositeTypeFilter.of(mockTypeFilterOne) + .orThen(mockTypeFilterTwo) + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isTrue(); + + InOrder inOrder = Mockito.inOrder(mockTypeFilterOne, mockTypeFilterTwo); + + inOrder.verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + inOrder.verify(mockTypeFilterTwo, never()).match(any(), any()); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void orThenComposesFalseTrueFilters() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + + doReturn(false).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(true).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + assertThat(CompositeTypeFilter.of(mockTypeFilterOne) + .orThen(mockTypeFilterTwo) + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isTrue(); + + InOrder inOrder = Mockito.inOrder(mockTypeFilterOne, mockTypeFilterTwo); + + inOrder.verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + inOrder.verify(mockTypeFilterTwo, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void orThenComposesFalseFalseFilters() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + + doReturn(false).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(false).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + assertThat(CompositeTypeFilter.of(mockTypeFilterOne) + .orThen(mockTypeFilterTwo) + .match(mockMetadataReader, mockMetadataReaderFactory)) + .isFalse(); + + InOrder inOrder = Mockito.inOrder(mockTypeFilterOne, mockTypeFilterTwo); + + inOrder.verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + inOrder.verify(mockTypeFilterTwo, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void composeAndWithTypeFiltersCallMatchReturnsTrue() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + TypeFilter mockTypeFilterThree = mock(TypeFilter.class); + + doReturn(true).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(true).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(true).when(mockTypeFilterThree) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + TypeFilter typeFilter = CompositeTypeFilter.composeAnd(null, mockTypeFilterOne, mockTypeFilterTwo, + null, null, mockTypeFilterThree); + + assertThat(typeFilter).isNotNull(); + assertThat(typeFilter.match(mockMetadataReader, mockMetadataReaderFactory)).isTrue(); + + verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verify(mockTypeFilterTwo, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verify(mockTypeFilterThree, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void composeAndWithTypeFiltersCallMatchReturnsFalse() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + TypeFilter mockTypeFilterThree = mock(TypeFilter.class); + + doReturn(true).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(false).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(true).when(mockTypeFilterThree) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + TypeFilter typeFilter = + CompositeTypeFilter.composeAnd(mockTypeFilterOne, mockTypeFilterTwo, mockTypeFilterThree); + + assertThat(typeFilter).isNotNull(); + assertThat(typeFilter.match(mockMetadataReader, mockMetadataReaderFactory)).isFalse(); + + verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verify(mockTypeFilterTwo, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verify(mockTypeFilterThree, never()).match(any(), any()); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void composeAndWithEmptyTypeFiltersReturnsNull() { + assertThat(CompositeTypeFilter.composeAnd()).isNull(); + } + + @Test + public void composeAndWithNullTypeFiltersIsNullSafeReturnsNull() { + assertThat(CompositeTypeFilter.composeAnd((TypeFilter[]) null)).isNull(); + } + + @Test + public void composeOrWithTypeFiltersCallMatchReturnsTrue() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + TypeFilter mockTypeFilterThree = mock(TypeFilter.class); + + doReturn(false).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(true).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(false).when(mockTypeFilterThree) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + TypeFilter typeFilter = + CompositeTypeFilter.composeOr(mockTypeFilterOne, null, mockTypeFilterTwo, mockTypeFilterThree, null); + + assertThat(typeFilter).isNotNull(); + assertThat(typeFilter.match(mockMetadataReader, mockMetadataReaderFactory)).isTrue(); + + verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verify(mockTypeFilterTwo, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verify(mockTypeFilterThree, never()).match(any(), any()); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void composeOrWithTypeFiltersCallMatchReturnsFalse() throws IOException { + + MetadataReader mockMetadataReader = mock(MetadataReader.class); + + MetadataReaderFactory mockMetadataReaderFactory = mock(MetadataReaderFactory.class); + + TypeFilter mockTypeFilterOne = mock(TypeFilter.class); + TypeFilter mockTypeFilterTwo = mock(TypeFilter.class); + TypeFilter mockTypeFilterThree = mock(TypeFilter.class); + + doReturn(false).when(mockTypeFilterOne) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(false).when(mockTypeFilterTwo) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + doReturn(false).when(mockTypeFilterThree) + .match(any(MetadataReader.class), any(MetadataReaderFactory.class)); + + TypeFilter typeFilter = + CompositeTypeFilter.composeOr(mockTypeFilterOne, mockTypeFilterTwo, mockTypeFilterThree); + + assertThat(typeFilter).isNotNull(); + assertThat(typeFilter.match(mockMetadataReader, mockMetadataReaderFactory)).isFalse(); + + verify(mockTypeFilterOne, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verify(mockTypeFilterTwo, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + verify(mockTypeFilterThree, times(1)) + .match(eq(mockMetadataReader), eq(mockMetadataReaderFactory)); + + verifyZeroInteractions(mockMetadataReader, mockMetadataReaderFactory); + } + + @Test + public void composeOrWithEmptyTypeFiltersReturnsNull() { + assertThat(CompositeTypeFilter.composeOr()).isNull(); + } + + @Test + public void composeOrWithNullTypeFiltersIsNullSafeReturnsNull() { + assertThat(CompositeTypeFilter.composeOr((TypeFilter[]) null)).isNull(); + } +}