diff --git a/spring-batch-plus/src/main/java/com/navercorp/spring/batch/plus/item/adapter/ItemStreamIteratorReaderAdapter.java b/spring-batch-plus/src/main/java/com/navercorp/spring/batch/plus/item/adapter/ItemStreamIteratorReaderAdapter.java new file mode 100644 index 00000000..91c2413a --- /dev/null +++ b/spring-batch-plus/src/main/java/com/navercorp/spring/batch/plus/item/adapter/ItemStreamIteratorReaderAdapter.java @@ -0,0 +1,88 @@ +/* + * Spring Batch Plus + * + * Copyright 2022-present NAVER Corp. + * + * 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 com.navercorp.spring.batch.plus.item.adapter; + +import java.util.Iterator; +import java.util.Objects; + +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStreamReader; +import org.springframework.lang.NonNull; + +/** + * An adaptor which adapt {@link ItemStreamIteratorReaderDelegate} to {@link ItemStreamReader}. + * + * @since 1.1.0 + */ +public class ItemStreamIteratorReaderAdapter implements ItemStreamReader { + + /** + * Create an adaptor which adapt {@link ItemStreamIteratorReaderDelegate} to {@link ItemStreamReader}. + * + * @param delegate a delegate + * @return an adapted ItemStreamReader + * @param a read item type + */ + public static ItemStreamReader of(@NonNull ItemStreamIteratorReaderDelegate delegate) { + return new ItemStreamIteratorReaderAdapter<>(delegate); + } + + protected final ItemStreamIteratorReaderDelegate delegate; + + protected Iterator iterator = null; + + protected ItemStreamIteratorReaderAdapter(ItemStreamIteratorReaderDelegate delegate) { + this.delegate = Objects.requireNonNull(delegate, "Delegate reader must not be null"); + } + + @SuppressWarnings("NullableProblems") + @Override + public void open(ExecutionContext executionContext) { + this.delegate.onOpenRead(executionContext); + this.iterator = this.delegate.readIterator(executionContext); + } + + @Override + public T read() { + Iterator iterator = getIterator(); + if (iterator.hasNext()) { + return iterator.next(); + } else { + return null; + } + } + + @SuppressWarnings("NullableProblems") + @Override + public void update(ExecutionContext executionContext) { + this.delegate.onUpdateRead(executionContext); + } + + @Override + public void close() { + this.delegate.onCloseRead(); + } + + protected Iterator getIterator() { + if (this.iterator == null) { + throw new IllegalStateException("No iterator is set. Call 'open' first."); + } + return this.iterator; + } +} diff --git a/spring-batch-plus/src/test/java/com/navercorp/spring/batch/plus/item/adapter/ItemStreamIteratorReaderAdapterTest.java b/spring-batch-plus/src/test/java/com/navercorp/spring/batch/plus/item/adapter/ItemStreamIteratorReaderAdapterTest.java new file mode 100644 index 00000000..00d89020 --- /dev/null +++ b/spring-batch-plus/src/test/java/com/navercorp/spring/batch/plus/item/adapter/ItemStreamIteratorReaderAdapterTest.java @@ -0,0 +1,112 @@ +/* + * Spring Batch Plus + * + * Copyright 2022-present NAVER Corp. + * + * 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 com.navercorp.spring.batch.plus.item.adapter; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.mockito.internal.verification.VerificationModeFactory.times; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStreamReader; + +@SuppressWarnings("unchecked") +class ItemStreamIteratorReaderAdapterTest { + + @Test + void testOpen() { + // given + ItemStreamIteratorReaderDelegate delegate = mock(ItemStreamIteratorReaderDelegate.class); + ItemStreamReader itemStreamReader = ItemStreamIteratorReaderAdapter.of(delegate); + + // when + itemStreamReader.open(new ExecutionContext()); + + // then + verify(delegate, times(1)).onOpenRead(any()); + verify(delegate, times(1)).readIterator(any()); + } + + @Test + void testRead() throws Exception { + // given + List expected = List.of(1, 2, 3); + ItemStreamIteratorReaderDelegate delegate = mock(ItemStreamIteratorReaderDelegate.class); + when(delegate.readIterator(any())).thenAnswer($ -> expected.iterator()); + ItemStreamReader itemStreamReader = ItemStreamIteratorReaderAdapter.of(delegate); + + // when + itemStreamReader.open(new ExecutionContext()); + List items = new ArrayList<>(); + Integer item; + while ((item = itemStreamReader.read()) != null) { + items.add(item); + } + + // then + assertThat(items).isEqualTo(expected); + } + + @Test + void testReadWithOpenShouldThrowsException() { + // given + ItemStreamIteratorReaderDelegate delegate = mock(ItemStreamIteratorReaderDelegate.class); + ItemStreamReader itemStreamReader = ItemStreamIteratorReaderAdapter.of(delegate); + + // when, then + assertThatThrownBy(itemStreamReader::read).isInstanceOf(IllegalStateException.class); + } + + @Test + void testUpdate() { + // given + ItemStreamIteratorReaderDelegate delegate = mock(ItemStreamIteratorReaderDelegate.class); + ItemStreamReader itemStreamReader = ItemStreamIteratorReaderAdapter.of(delegate); + + // when + itemStreamReader.update(new ExecutionContext()); + + // then + verify(delegate, times(1)).onUpdateRead(any()); + } + + @Test + void testClose() { + // given + ItemStreamIteratorReaderDelegate delegate = mock(ItemStreamIteratorReaderDelegate.class); + ItemStreamReader itemStreamReader = ItemStreamIteratorReaderAdapter.of(delegate); + + // when + itemStreamReader.close(); + + // then + verify(delegate, times(1)).onCloseRead(); + } + + @SuppressWarnings({"ResultOfMethodCallIgnored", "ConstantConditions"}) + @Test + void testPassingNull() { + // when, then + assertThatThrownBy(() -> ItemStreamIteratorReaderAdapter.of(null)); + } +}