Introduce RowSet abstraction.
This commit is contained in:
committed by
Michael Minella
parent
f486e83eb8
commit
5a4aa5d533
@@ -49,6 +49,7 @@ public abstract class AbstractExcelItemReader<T> 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<T> 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<T> 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()) {
|
||||
|
||||
@@ -24,6 +24,6 @@ package org.springframework.batch.item.excel;
|
||||
*/
|
||||
public interface RowCallbackHandler {
|
||||
|
||||
void handleRow(Sheet sheet, String[] row);
|
||||
void handleRow(RowSet rs);
|
||||
|
||||
}
|
||||
|
||||
@@ -30,12 +30,10 @@ public interface RowMapper<T> {
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<T> implements RowMapper<T>, InitializingBean {
|
||||
private FieldSetMapper<T> 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<T> fieldSetMapper) {
|
||||
|
||||
@@ -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<String[]> {
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user