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:
dsyer
2007-10-10 10:09:17 +00:00
parent c4cf4a8910
commit f2d1dabb06
3 changed files with 155 additions and 107 deletions

View File

@@ -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;
}
}

View File

@@ -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 = "";
}

View File

@@ -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");