diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/AbstractExcelItemReader.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/AbstractExcelItemReader.java index caba8fa..9b9c45f 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/AbstractExcelItemReader.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/AbstractExcelItemReader.java @@ -49,6 +49,7 @@ public abstract class AbstractExcelItemReader extends AbstractItemCountingIte private RowCallbackHandler skippedRowsCallback; private boolean noInput = false; private boolean strict = true; + private RowSet rs; public AbstractExcelItemReader() { super(); @@ -57,31 +58,29 @@ public abstract class AbstractExcelItemReader extends AbstractItemCountingIte @Override protected T doRead() throws Exception { - if (this.noInput) { + if (this.noInput || this.rs == null) { return null; } - final Sheet sheet = this.getSheet(this.currentSheet); - final String[] row = this.readRow(sheet); - if (ObjectUtils.isEmpty(row)) { - this.currentSheet++; - if (this.currentSheet >= this.getNumberOfSheets()) { - if (logger.isDebugEnabled() ) { - logger.debug("No more sheets in '" + this.resource.getDescription() + "'."); - } - return null; - } else { - this.currentRow = 0; - this.openSheet(); - return this.doRead(); - } - } else { - try { - return this.rowMapper.mapRow(sheet, row, this.currentRow); - } catch (final Exception e) { - throw new ExcelFileParseException("Exception parsing Excel file.", e, this.resource.getDescription(), - sheet.getName(), this.currentRow, row); - } - } + + if (rs.next()) { + try { + return this.rowMapper.mapRow(rs); + } catch (final Exception e) { + throw new ExcelFileParseException("Exception parsing Excel file.", e, this.resource.getDescription(), + rs.getMetaData().getSheetName(), rs.getCurrentRowIndex(), rs.getCurrentRow()); + } + } else { + this.currentSheet++; + if (this.currentSheet >= this.getNumberOfSheets()) { + if (logger.isDebugEnabled() ) { + logger.debug("No more sheets in '" + this.resource.getDescription() + "'."); + } + return null; + } else { + this.openSheet(); + return this.doRead(); + } + } } @Override @@ -124,13 +123,15 @@ public abstract class AbstractExcelItemReader extends AbstractItemCountingIte private void openSheet() { final Sheet sheet = this.getSheet(this.currentSheet); - if (logger.isDebugEnabled()) { + this.rs = new RowSet(sheet); + + if (logger.isDebugEnabled()) { logger.debug("Opening sheet "+sheet.getName()+"."); } - for (int i = 0; i < this.linesToSkip; i++) { - final String[] row = this.readRow(sheet); - if (this.skippedRowsCallback != null) { - this.skippedRowsCallback.handleRow(sheet, row); + + for (int i = 0; i < this.linesToSkip; i++) { + if (rs.next() && this.skippedRowsCallback != null) { + this.skippedRowsCallback.handleRow(rs); } } if (logger.isDebugEnabled()) { diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowCallbackHandler.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowCallbackHandler.java index 0b6c172..c1dc7ff 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowCallbackHandler.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowCallbackHandler.java @@ -24,6 +24,6 @@ package org.springframework.batch.item.excel; */ public interface RowCallbackHandler { - void handleRow(Sheet sheet, String[] row); + void handleRow(RowSet rs); } diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowMapper.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowMapper.java index 85b90cb..5e43430 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowMapper.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowMapper.java @@ -30,12 +30,10 @@ public interface RowMapper { * the parameter type T. The row number represents the number of rows * into a {@link Sheet} the current line resides. * - * @param sheet the current sheet - * @param row to be mapped - * @param rowNum of the current row + * @param rs the RowSet used for mapping. * @return mapped object of type T * @throws Exception if error occured while parsing. */ - T mapRow(Sheet sheet, String[] row, int rowNum) throws Exception; + T mapRow(RowSet rs) throws Exception; } diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowSet.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowSet.java new file mode 100644 index 0000000..89a2301 --- /dev/null +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowSet.java @@ -0,0 +1,87 @@ +package org.springframework.batch.item.excel; + +import java.util.Properties; + +/** + * @author Marten Deinum + */ +public class RowSet { + + private final Sheet sheet; + private final RowSetMetaData metaData; + + private int currentRowIndex = -1; + private String[] currentRow; + + public RowSet(Sheet sheet) { + this.sheet=sheet; + this.metaData = new RowSetMetaData(sheet); + } + + public RowSetMetaData getMetaData() { + return metaData; + } + + public boolean next() { + currentRow = null; + currentRowIndex++; + if (currentRowIndex <= sheet.getNumberOfRows()) { + currentRow = sheet.getRow(currentRowIndex); + return true; + } + return false; + } + + /** + * The current row index represents the number of rows + * into a {@link Sheet} the current line resides + */ + public int getCurrentRowIndex() { + return this.currentRowIndex; + } + + /** + * Get the data of the current row. + * + * @return a String[] for the current data + */ + public String[] getCurrentRow() { + return this.currentRow; + } + + /** + * Get the value of the given column + * + * @param idx index of the column to get, 0 based + * @return the value + * @throws java.lang.ArrayIndexOutOfBoundsException + */ + public String getColumnValue(int idx) { + return currentRow[idx]; + } + + /** + * Construct name-value pairs from the column names and string values. Null + * values are omitted. + * + * @return some properties representing the row set. + * + * @throws IllegalStateException if the column name meta data is not + * available. + */ + public Properties getProperties() { + final String[] names = metaData.getColumnNames(); + if (names == null) { + throw new IllegalStateException("Cannot create properties without meta data"); + } + + Properties props = new Properties(); + for (int i = 0; i < currentRow.length; i++) { + String value = currentRow[i]; + if (value != null) { + props.setProperty(names[i], value); + } + } + return props; + } +} diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowSetMetaData.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowSetMetaData.java new file mode 100644 index 0000000..e270b16 --- /dev/null +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/RowSetMetaData.java @@ -0,0 +1,31 @@ +package org.springframework.batch.item.excel; + +/** + * Created by in329dei on 3-9-2014. + */ +public class RowSetMetaData { + + private final Sheet sheet; + + RowSetMetaData(Sheet sheet) { + this.sheet = sheet; + } + + public String[] getColumnNames() { + return sheet.getHeader(); + } + + public String getColumnName(int idx) { + String[] names = getColumnNames(); + return names[idx]; + } + + public int getColumnCount() { + return sheet.getNumberOfColumns(); + } + + public String getSheetName() { + return sheet.getName(); + } + +} diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/jxl/JxlSheet.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/jxl/JxlSheet.java index 1d32e83..cb5542a 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/jxl/JxlSheet.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/jxl/JxlSheet.java @@ -61,8 +61,12 @@ public class JxlSheet implements Sheet { */ @Override public String[] getRow(final int rowNumber) { - final Cell[] row = this.delegate.getRow(rowNumber); - return JxlUtils.extractContents(row); + if (rowNumber < getNumberOfRows()) { + final Cell[] row = this.delegate.getRow(rowNumber); + return JxlUtils.extractContents(row); + } else { + return null; + } } /** diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/mapping/DefaultRowMapper.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/mapping/DefaultRowMapper.java index 70dd89b..52f13f3 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/mapping/DefaultRowMapper.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/mapping/DefaultRowMapper.java @@ -18,6 +18,7 @@ package org.springframework.batch.item.excel.mapping; import org.springframework.batch.item.excel.RowMapper; +import org.springframework.batch.item.excel.RowSet; import org.springframework.batch.item.excel.Sheet; import org.springframework.batch.item.excel.transform.DefaultRowTokenizer; import org.springframework.batch.item.excel.transform.RowTokenizer; @@ -40,8 +41,8 @@ public class DefaultRowMapper implements RowMapper, InitializingBean { private FieldSetMapper fieldSetMapper; @Override - public T mapRow(final Sheet sheet, final String[] row, final int rowNum) throws Exception { - return this.fieldSetMapper.mapFieldSet(this.rowTokenizer.tokenize(sheet, row)); + public T mapRow(RowSet rs) throws Exception { + return this.fieldSetMapper.mapFieldSet(this.rowTokenizer.tokenize(rs)); } public void setFieldSetMapper(final FieldSetMapper fieldSetMapper) { diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/mapping/PassThroughRowMapper.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/mapping/PassThroughRowMapper.java index 9f198b9..22f4174 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/mapping/PassThroughRowMapper.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/mapping/PassThroughRowMapper.java @@ -16,6 +16,7 @@ package org.springframework.batch.item.excel.mapping; import org.springframework.batch.item.excel.RowMapper; +import org.springframework.batch.item.excel.RowSet; import org.springframework.batch.item.excel.Sheet; /** @@ -29,8 +30,8 @@ import org.springframework.batch.item.excel.Sheet; public class PassThroughRowMapper implements RowMapper { @Override - public String[] mapRow(final Sheet sheet, final String[] row, final int rowNum) throws Exception { - return row; + public String[] mapRow(final RowSet rs) throws Exception { + return rs.getCurrentRow(); } } diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/transform/DefaultRowTokenizer.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/transform/DefaultRowTokenizer.java index 0da0fbf..cc1ca17 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/transform/DefaultRowTokenizer.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/transform/DefaultRowTokenizer.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.item.excel.transform; +import org.springframework.batch.item.excel.RowSet; import org.springframework.batch.item.excel.Sheet; import org.springframework.batch.item.file.transform.DefaultFieldSetFactory; import org.springframework.batch.item.file.transform.FieldSet; @@ -41,16 +42,15 @@ public class DefaultRowTokenizer implements RowTokenizer, InitializingBean { private String attributeForSheetName = null; @Override - public FieldSet tokenize(final Sheet sheet, final String[] row) { - String[] values = new String[sheet.getNumberOfColumns()]; - System.arraycopy(row, 0, values, 0, row.length); + public FieldSet tokenize(RowSet rs) { + String[] values = rs.getCurrentRow(); if (this.includeSheetName) { - values = StringUtils.addStringToArray(values, sheet.getName()); + values = StringUtils.addStringToArray(values, rs.getMetaData().getSheetName()); } if (this.useColumnHeader) { - String[] names = sheet.getHeader(); + String[] names = rs.getMetaData().getColumnNames(); if (this.includeSheetName) { names = StringUtils.addStringToArray(names, this.attributeForSheetName); } diff --git a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/transform/RowTokenizer.java b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/transform/RowTokenizer.java index 138c541..56cd030 100644 --- a/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/transform/RowTokenizer.java +++ b/spring-batch-excel/src/main/java/org/springframework/batch/item/excel/transform/RowTokenizer.java @@ -15,6 +15,7 @@ */ package org.springframework.batch.item.excel.transform; +import org.springframework.batch.item.excel.RowSet; import org.springframework.batch.item.excel.Sheet; import org.springframework.batch.item.file.transform.FieldSet; @@ -27,5 +28,5 @@ import org.springframework.batch.item.file.transform.FieldSet; */ public interface RowTokenizer { - FieldSet tokenize(Sheet sheet, String[] row); + FieldSet tokenize(RowSet rs); } diff --git a/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/AbstractExcelItemReaderTests.java b/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/AbstractExcelItemReaderTests.java index 21628f5..7aa99ed 100644 --- a/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/AbstractExcelItemReaderTests.java +++ b/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/AbstractExcelItemReaderTests.java @@ -49,8 +49,8 @@ public abstract class AbstractExcelItemReaderTests { this.itemReader.setRowMapper(new PassThroughRowMapper()); this.itemReader.setSkippedRowsCallback(new RowCallbackHandler() { - public void handleRow(final Sheet sheet, final String[] row) { - logger.info("Skipping: " + StringUtils.arrayToCommaDelimitedString(row)); + public void handleRow(RowSet rs) { + logger.info("Skipping: " + StringUtils.arrayToCommaDelimitedString(rs.getCurrentRow())); } }); configureItemReader(this.itemReader); @@ -70,7 +70,7 @@ public abstract class AbstractExcelItemReaderTests { @Test public void readExcelFile() throws Exception { assertEquals(3, this.itemReader.getNumberOfSheets()); - String[] row = null; + String[] row; do { row = (String[]) this.itemReader.read(); this.logger.debug("Read: " + StringUtils.arrayToCommaDelimitedString(row)); @@ -79,7 +79,7 @@ public abstract class AbstractExcelItemReaderTests { } } while (row != null); int readCount = (Integer) ReflectionTestUtils.getField(this.itemReader, "currentItemCount" ); - assertEquals(4320, readCount); // File contains 4321 lines, first is header 4321-1=4320 records read. + assertEquals(4321, readCount); } @Test(expected = IllegalArgumentException.class) diff --git a/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/mapping/DefaultRowMapperTests.java b/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/mapping/DefaultRowMapperTests.java index 9472acf..255fc7a 100644 --- a/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/mapping/DefaultRowMapperTests.java +++ b/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/mapping/DefaultRowMapperTests.java @@ -21,6 +21,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.batch.item.excel.RowSet; import org.springframework.batch.item.excel.Sheet; import org.springframework.batch.item.excel.transform.RowTokenizer; import org.springframework.batch.item.file.mapping.FieldSetMapper; @@ -65,10 +66,10 @@ public class DefaultRowMapperTests { mapper.setFieldSetMapper(this.fieldSetMapper); final FieldSet fs = Mockito.mock(FieldSet.class); final Object result = new Object(); - Mockito.when(this.rowTokenizer.tokenize(any(Sheet.class), any(String[].class))).thenReturn(fs); + Mockito.when(this.rowTokenizer.tokenize(any(RowSet.class))).thenReturn(fs); Mockito.when(this.fieldSetMapper.mapFieldSet(fs)).thenReturn(result); - Assert.assertEquals(result, mapper.mapRow(null, null, 0)); - Mockito.verify(this.rowTokenizer, Mockito.times(1)).tokenize(any(Sheet.class), any(String[].class)); + Assert.assertEquals(result, mapper.mapRow(null)); + Mockito.verify(this.rowTokenizer, Mockito.times(1)).tokenize(any(RowSet.class)); Mockito.verify(this.fieldSetMapper, Mockito.times(1)).mapFieldSet(fs); } diff --git a/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/mapping/PassThroughRowMapperTests.java b/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/mapping/PassThroughRowMapperTests.java index 6c55aab..dedeb01 100644 --- a/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/mapping/PassThroughRowMapperTests.java +++ b/spring-batch-excel/src/test/java/org/springframework/batch/item/excel/mapping/PassThroughRowMapperTests.java @@ -16,9 +16,14 @@ package org.springframework.batch.item.excel.mapping; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.batch.item.excel.RowSet; +import org.springframework.batch.item.excel.Sheet; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; /** * Tests for {@link PassThroughRowMapper}. @@ -33,13 +38,12 @@ public class PassThroughRowMapperTests { @Test public void mapRowShouldReturnSameValues() throws Exception { final String[] row = new String[] { "foo", "bar", "baz" }; - - assertArrayEquals(row, this.rowMapper.mapRow(null, row, 0)); - } - - @Test - public void mapRowShouldReturnNull() throws Exception { - assertNull(this.rowMapper.mapRow(null, null, 0)); + Sheet sheet = mock(Sheet.class); + when(sheet.getRow(0)).thenReturn(row); + when(sheet.getNumberOfRows()).thenReturn(1); + RowSet rs = new RowSet(sheet); + assertTrue(rs.next()); + assertArrayEquals(row, this.rowMapper.mapRow(rs)); } }