diff --git a/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/FixedLengthLineAggregator.java b/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/FixedLengthLineAggregator.java index 63cd77518..285d30876 100644 --- a/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/FixedLengthLineAggregator.java +++ b/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/FixedLengthLineAggregator.java @@ -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[])}.
+ * LineAggregator implementation which produces line by aggregating provided + * strings into columns with fixed length. Columns are specified by array of + * ranges ({@link #setColumns(Range[])}.
* * @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 CENTER, RIGHT, LEFT. - * An IllegalArgumentException is thrown in case the argument does not - * match any of the recognized values. + * Recognized alignments are CENTER, RIGHT, LEFT. 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; } } diff --git a/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/FixedLengthTokenizer.java b/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/FixedLengthTokenizer.java index 33c51997e..308045e31 100644 --- a/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/FixedLengthTokenizer.java +++ b/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/FixedLengthTokenizer.java @@ -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 * line. * - * @param line the line to be tokenised (can be null) + * @param line + * the line to be tokenised (can be null) * - * @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 = ""; } diff --git a/infrastructure/src/test/java/org/springframework/batch/io/file/support/transform/RangeArrayPropertyEditorTests.java b/infrastructure/src/test/java/org/springframework/batch/io/file/support/transform/RangeArrayPropertyEditorTests.java index dd5d44a7c..538df9aff 100644 --- a/infrastructure/src/test/java/org/springframework/batch/io/file/support/transform/RangeArrayPropertyEditorTests.java +++ b/infrastructure/src/test/java/org/springframework/batch/io/file/support/transform/RangeArrayPropertyEditorTests.java @@ -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");