RESOLVED - issue BATCH-50: Allow fixed-length file columns with column ranges
http://opensource.atlassian.com/projects/spring/browse/BATCH-50 Tidy test case and javadocs
This commit is contained in:
@@ -21,26 +21,30 @@ import java.util.Arrays;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* LineAggregator implementation which produces line by aggregating provided
|
||||
* strings into columns with fixed length. Columns are specified by array of ranges
|
||||
* ({@link #setColumns(Range[])}.</br>
|
||||
* LineAggregator implementation which produces line by aggregating provided
|
||||
* strings into columns with fixed length. Columns are specified by array of
|
||||
* ranges ({@link #setColumns(Range[])}.</br>
|
||||
*
|
||||
* @author tomas.slanina
|
||||
* @author peter.zozom
|
||||
* @author peter.zozom
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class FixedLengthLineAggregator implements LineAggregator {
|
||||
|
||||
private static final int ALIGN_CENTER = 1;
|
||||
private static final int ALIGN_RIGHT = 2;
|
||||
private static final int ALIGN_LEFT = 3;
|
||||
|
||||
|
||||
private Range[] ranges;
|
||||
private int lastColumn;
|
||||
private int align = ALIGN_LEFT;
|
||||
private char padding = ' ';
|
||||
|
||||
|
||||
/**
|
||||
* Set column ranges.
|
||||
* Set column ranges. Used in conjunction with the
|
||||
* {@link RangeArrayPropertyEditor} this property can be set in the form of
|
||||
* a String describing the range boundaries, e.g. "1,4,7" or "1-3,4-6,7" or
|
||||
* "1-2,4-5,7-10".
|
||||
*
|
||||
* @param columns
|
||||
* array of Range objects which specify column start and end
|
||||
@@ -51,11 +55,13 @@ public class FixedLengthLineAggregator implements LineAggregator {
|
||||
lastColumn = findLastColumn(columns);
|
||||
this.ranges = columns;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Aggregate provided strings into single line using specified column ranges.
|
||||
* Aggregate provided strings into single line using specified column
|
||||
* ranges.
|
||||
*
|
||||
* @param args arrays of strings representing data to be aggregated
|
||||
* @param args
|
||||
* arrays of strings representing data to be aggregated
|
||||
* @return aggregated strings
|
||||
*/
|
||||
public String aggregate(String[] args) {
|
||||
@@ -65,100 +71,107 @@ public class FixedLengthLineAggregator implements LineAggregator {
|
||||
Assert.isTrue(args.length <= ranges.length,
|
||||
"Number of arguments must match number of fields in a record");
|
||||
|
||||
//calculate line length
|
||||
int lineLength = ranges[lastColumn].hasMaxValue() ? ranges[lastColumn].getMax()
|
||||
: ranges[lastColumn].getMin() + args[lastColumn].length() - 1;
|
||||
|
||||
//create stringBuffer with length of line filled with padding characters
|
||||
char[] emptyLine = new char[lineLength];
|
||||
// calculate line length
|
||||
int lineLength = ranges[lastColumn].hasMaxValue() ? ranges[lastColumn]
|
||||
.getMax() : ranges[lastColumn].getMin()
|
||||
+ args[lastColumn].length() - 1;
|
||||
|
||||
// create stringBuffer with length of line filled with padding
|
||||
// characters
|
||||
char[] emptyLine = new char[lineLength];
|
||||
Arrays.fill(emptyLine, padding);
|
||||
|
||||
|
||||
StringBuffer stringBuffer = new StringBuffer(lineLength);
|
||||
stringBuffer.append(emptyLine);
|
||||
|
||||
//aggregate all strings
|
||||
for(int i = 0; i < args.length; i++) {
|
||||
|
||||
//offset where text will be inserted
|
||||
int start = ranges[i].getMin() - 1;
|
||||
|
||||
//calculate column length
|
||||
|
||||
// aggregate all strings
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
|
||||
// offset where text will be inserted
|
||||
int start = ranges[i].getMin() - 1;
|
||||
|
||||
// calculate column length
|
||||
int columnLength;
|
||||
if ((i == lastColumn) && (!ranges[lastColumn].hasMaxValue())) {
|
||||
columnLength = args[lastColumn].length();
|
||||
} else {
|
||||
columnLength = ranges[i].getMax() - ranges[i].getMin() + 1;
|
||||
}
|
||||
|
||||
|
||||
String textToInsert = (args[i] == null) ? "" : args[i];
|
||||
|
||||
Assert.isTrue(columnLength >= textToInsert.length(),
|
||||
"Supplied text: " + textToInsert + " is longer than defined length: " + columnLength);
|
||||
|
||||
Assert
|
||||
.isTrue(columnLength >= textToInsert.length(),
|
||||
"Supplied text: " + textToInsert
|
||||
+ " is longer than defined length: "
|
||||
+ columnLength);
|
||||
|
||||
switch (align) {
|
||||
case ALIGN_RIGHT:
|
||||
start += (columnLength - textToInsert.length());
|
||||
break;
|
||||
case ALIGN_CENTER:
|
||||
start += ((columnLength - textToInsert.length()) / 2);
|
||||
break;
|
||||
case ALIGN_LEFT:
|
||||
//nothing to do
|
||||
break;
|
||||
case ALIGN_RIGHT:
|
||||
start += (columnLength - textToInsert.length());
|
||||
break;
|
||||
case ALIGN_CENTER:
|
||||
start += ((columnLength - textToInsert.length()) / 2);
|
||||
break;
|
||||
case ALIGN_LEFT:
|
||||
// nothing to do
|
||||
break;
|
||||
}
|
||||
|
||||
stringBuffer.replace(start, start + textToInsert.length(), textToInsert);
|
||||
stringBuffer.replace(start, start + textToInsert.length(),
|
||||
textToInsert);
|
||||
}
|
||||
|
||||
|
||||
return stringBuffer.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recognized alignments are <code>CENTER, RIGHT, LEFT</code>.
|
||||
* An IllegalArgumentException is thrown in case the argument does not
|
||||
* match any of the recognized values.
|
||||
* Recognized alignments are <code>CENTER, RIGHT, LEFT</code>. An
|
||||
* IllegalArgumentException is thrown in case the argument does not match
|
||||
* any of the recognized values.
|
||||
*
|
||||
* @param alignment the alignment to be used
|
||||
* @param alignment
|
||||
* the alignment to be used
|
||||
*/
|
||||
public void setAlignment(String alignment) {
|
||||
if ("CENTER".equalsIgnoreCase(alignment)) {
|
||||
this.align = ALIGN_CENTER;
|
||||
}
|
||||
else if ("RIGHT".equalsIgnoreCase(alignment)) {
|
||||
} else if ("RIGHT".equalsIgnoreCase(alignment)) {
|
||||
this.align = ALIGN_RIGHT;
|
||||
}
|
||||
else if ("LEFT".equalsIgnoreCase(alignment)) {
|
||||
} else if ("LEFT".equalsIgnoreCase(alignment)) {
|
||||
this.align = ALIGN_LEFT;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Only 'CENTER', 'RIGHT' or 'LEFT' are allowed alignment values");
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Only 'CENTER', 'RIGHT' or 'LEFT' are allowed alignment values");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter for padding (default space).
|
||||
* @param padding the padding character
|
||||
* Setter for padding (default is space).
|
||||
*
|
||||
* @param padding
|
||||
* the padding character
|
||||
*/
|
||||
public void setPadding(char padding) {
|
||||
this.padding = padding;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find last column. Columns are not sorted.
|
||||
* Returns index of last column (column with highest offset).
|
||||
* Find last column. Columns are not sorted. Returns index of last column
|
||||
* (column with highest offset).
|
||||
*/
|
||||
private int findLastColumn(Range[] columns) {
|
||||
|
||||
|
||||
int lastOffset = 1;
|
||||
int lastIndex = 0;
|
||||
|
||||
for(int i = 0; i < columns.length; i++) {
|
||||
|
||||
for (int i = 0; i < columns.length; i++) {
|
||||
if (columns[i].getMin() > lastOffset) {
|
||||
lastOffset = columns[i].getMin();
|
||||
lastIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return lastIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,30 +21,36 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Tokenizer used to process data obtained from files with fixed-length format.
|
||||
* Columns are specified by array of Range objects ({@link #setColumns(Range[])}).
|
||||
* Columns are specified by array of Range objects ({@link #setColumns(Range[])}).
|
||||
*
|
||||
* @author tomas.slanina
|
||||
* @author peter.zozom
|
||||
* @author Dave Syer
|
||||
*/
|
||||
public class FixedLengthTokenizer extends AbstractLineTokenizer {
|
||||
|
||||
|
||||
private Range[] ranges;
|
||||
|
||||
|
||||
/**
|
||||
* Set the column ranges.
|
||||
* @param ranges
|
||||
* Set the column ranges. Used in conjunction with the
|
||||
* {@link RangeArrayPropertyEditor} this property can be set in the form of
|
||||
* a String describing the range boundaries, e.g. "1,4,7" or "1-3,4-6,7" or
|
||||
* "1-2,4-5,7-10".
|
||||
*
|
||||
* @param ranges the column ranges expected in the input
|
||||
*/
|
||||
public void setColumns(Range[] ranges) {
|
||||
this.ranges = ranges;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Yields the tokens resulting from the splitting of the supplied
|
||||
* <code>line</code>.
|
||||
*
|
||||
* @param line the line to be tokenised (can be <code>null</code>)
|
||||
* @param line
|
||||
* the line to be tokenised (can be <code>null</code>)
|
||||
*
|
||||
* @return the resulting tokens
|
||||
* @return the resulting tokens (empty if the line is null)
|
||||
*/
|
||||
protected List doTokenize(String line) {
|
||||
List tokens = new ArrayList(ranges.length);
|
||||
@@ -55,16 +61,14 @@ public class FixedLengthTokenizer extends AbstractLineTokenizer {
|
||||
|
||||
for (int i = 0; i < ranges.length; i++) {
|
||||
|
||||
int startPos = ranges[i].getMin()-1;
|
||||
int startPos = ranges[i].getMin() - 1;
|
||||
int endPos = ranges[i].getMax();
|
||||
|
||||
|
||||
if (lineLength >= endPos) {
|
||||
token = line.substring(startPos, endPos);
|
||||
}
|
||||
else if (lineLength >= startPos) {
|
||||
} else if (lineLength >= startPos) {
|
||||
token = line.substring(startPos);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
token = "";
|
||||
}
|
||||
|
||||
|
||||
@@ -8,56 +8,75 @@ import junit.framework.TestCase;
|
||||
public class RangeArrayPropertyEditorTests extends TestCase {
|
||||
|
||||
private Range[] ranges;
|
||||
private RangeArrayPropertyEditor pe;
|
||||
|
||||
private RangeArrayPropertyEditor pe;
|
||||
|
||||
public void setUp() {
|
||||
|
||||
|
||||
ranges = null;
|
||||
|
||||
|
||||
pe = new RangeArrayPropertyEditor() {
|
||||
public void setValue(Object value) {
|
||||
ranges = (Range[])value;
|
||||
ranges = (Range[]) value;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return ranges;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testSetAsText() {
|
||||
|
||||
public void testSetAsText() {
|
||||
pe.setAsText("15, 32, 1-10, 33");
|
||||
|
||||
//result should be 15-31, 32-32, 1-10, 33-unbound
|
||||
|
||||
// result should be 15-31, 32-32, 1-10, 33-unbound
|
||||
assertEquals(4, ranges.length);
|
||||
assertEquals(15,ranges[0].getMin());
|
||||
assertEquals(31,ranges[0].getMax());
|
||||
assertEquals(32,ranges[1].getMin());
|
||||
assertEquals(32,ranges[1].getMax());
|
||||
assertEquals(1,ranges[2].getMin());
|
||||
assertEquals(10,ranges[2].getMax());
|
||||
assertEquals(33,ranges[3].getMin());
|
||||
assertEquals(15, ranges[0].getMin());
|
||||
assertEquals(31, ranges[0].getMax());
|
||||
assertEquals(32, ranges[1].getMin());
|
||||
assertEquals(32, ranges[1].getMax());
|
||||
assertEquals(1, ranges[2].getMin());
|
||||
assertEquals(10, ranges[2].getMax());
|
||||
assertEquals(33, ranges[3].getMin());
|
||||
assertFalse(ranges[3].hasMaxValue());
|
||||
}
|
||||
|
||||
|
||||
public void testSetAsTextWithNoSpaces() {
|
||||
pe.setAsText("15,32");
|
||||
|
||||
// result should be 15-31, 32-unbound
|
||||
assertEquals(2, ranges.length);
|
||||
assertEquals(15, ranges[0].getMin());
|
||||
assertEquals(31, ranges[0].getMax());
|
||||
assertEquals(32, ranges[1].getMin());
|
||||
assertFalse(ranges[1].hasMaxValue());
|
||||
}
|
||||
|
||||
public void testGetAsText() {
|
||||
|
||||
ranges = new Range[] {new Range(20),new Range(6,15),new Range(2),new Range(26,95)};
|
||||
|
||||
ranges = new Range[] { new Range(20), new Range(6, 15), new Range(2),
|
||||
new Range(26, 95) };
|
||||
assertEquals("20, 6-15, 2, 26-95", pe.getAsText());
|
||||
}
|
||||
|
||||
public void testValidateDisjointRanges() {
|
||||
|
||||
public void testValidDisjointRanges() {
|
||||
pe.setForceDisjointRanges(true);
|
||||
|
||||
//test disjoint ranges
|
||||
|
||||
// test disjoint ranges
|
||||
pe.setAsText("1-5,11-15");
|
||||
|
||||
|
||||
assertEquals(2, ranges.length);
|
||||
assertEquals(1,ranges[0].getMin());
|
||||
assertEquals(5,ranges[0].getMax());
|
||||
assertEquals(11,ranges[1].getMin());
|
||||
assertEquals(15,ranges[1].getMax());
|
||||
|
||||
//test joint ranges
|
||||
assertEquals(1, ranges[0].getMin());
|
||||
assertEquals(5, ranges[0].getMax());
|
||||
assertEquals(11, ranges[1].getMin());
|
||||
assertEquals(15, ranges[1].getMax());
|
||||
|
||||
}
|
||||
|
||||
public void testInvalidOverlappingRanges() {
|
||||
|
||||
pe.setForceDisjointRanges(true);
|
||||
|
||||
// test joint ranges
|
||||
try {
|
||||
pe.setAsText("1-10, 5-15");
|
||||
fail("Exception expected: ranges are not disjoint");
|
||||
@@ -65,9 +84,21 @@ public class RangeArrayPropertyEditorTests extends TestCase {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testValidOverlappingRanges() {
|
||||
|
||||
// test joint ranges
|
||||
pe.setAsText("1-10, 5-15");
|
||||
assertEquals(2, ranges.length);
|
||||
assertEquals(1, ranges[0].getMin());
|
||||
assertEquals(10, ranges[0].getMax());
|
||||
assertEquals(5, ranges[1].getMin());
|
||||
assertEquals(15, ranges[1].getMax());
|
||||
|
||||
}
|
||||
|
||||
public void testInvalidInput() {
|
||||
|
||||
|
||||
try {
|
||||
pe.setAsText("1-5, b");
|
||||
fail("Exception expected: 2nd range is invalid");
|
||||
|
||||
Reference in New Issue
Block a user