diff --git a/infrastructure/src/main/java/org/springframework/batch/io/exception/FlatFileParsingException.java b/infrastructure/src/main/java/org/springframework/batch/io/exception/FlatFileParsingException.java
index c3cf23adf..e98a38e81 100644
--- a/infrastructure/src/main/java/org/springframework/batch/io/exception/FlatFileParsingException.java
+++ b/infrastructure/src/main/java/org/springframework/batch/io/exception/FlatFileParsingException.java
@@ -27,6 +27,8 @@ package org.springframework.batch.io.exception;
*/
public class FlatFileParsingException extends ParsingException {
+ private static final long serialVersionUID = 2529197834044942724L;
+
private String input;
private int lineNumber;
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/exception/ParsingException.java b/infrastructure/src/main/java/org/springframework/batch/io/exception/ParsingException.java
index 9bf5c3271..cdaf1659c 100644
--- a/infrastructure/src/main/java/org/springframework/batch/io/exception/ParsingException.java
+++ b/infrastructure/src/main/java/org/springframework/batch/io/exception/ParsingException.java
@@ -24,6 +24,8 @@ package org.springframework.batch.io.exception;
*/
public class ParsingException extends RuntimeException {
+ private static final long serialVersionUID = 2953386084409312312L;
+
public ParsingException(String message) {
super(message);
}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/file/FieldSet.java b/infrastructure/src/main/java/org/springframework/batch/io/file/FieldSet.java
index 03dbd973c..fc062c876 100644
--- a/infrastructure/src/main/java/org/springframework/batch/io/file/FieldSet.java
+++ b/infrastructure/src/main/java/org/springframework/batch/io/file/FieldSet.java
@@ -1,612 +1,625 @@
-/*
- * Copyright 2006-2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.io.file;
-
-import java.math.BigDecimal;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Properties;
-
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
-
-/**
- * @author Rob Harrop
- * @author Dave Syer
- */
-public final class FieldSet {
-
- private final static String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";
-
- /**
- * The fields wrapped by this 'FieldSet' instance.
- */
- private String[] tokens;
-
- private List names;
-
- public FieldSet(String[] tokens) {
- this.tokens = tokens;
- }
-
- public FieldSet(String[] tokens, String[] names) {
- if (tokens.length != names.length) {
- throw new IllegalArgumentException(
- "Field names must be same length as values: names="
- + Arrays.asList(names) + ", values="
- + Arrays.asList(tokens));
- }
- this.tokens = tokens;
- this.names = Arrays.asList(names);
- }
-
- /**
- * Read the {@link String} value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public String readString(int index) {
- return readAndTrim(index);
- }
-
- /**
- * Read the {@link String} value from column with given 'name'.
- *
- * @param name
- * the field name.
- */
- public String readString(String name) {
- return readString(indexOf(name));
- }
-
- /**
- * Read the 'boolean' value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public boolean readBoolean(int index) {
- return readBoolean(index, "true");
- }
-
- /**
- * Read the 'boolean' value from column with given 'name'.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public boolean readBoolean(String name) {
- return readBoolean(indexOf(name));
- }
-
- /**
- * Read the 'boolean' value at index 'index'.
- *
- * @param index
- * the field index.
- * @param trueValue
- * the value that signifies {@link Boolean#TRUE true};
- * case-sensitive.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds, or if the supplied
- * trueValue is null.
- */
- public boolean readBoolean(int index, String trueValue) {
- Assert.notNull(trueValue, "'trueValue' cannot be null.");
-
- String value = readAndTrim(index);
-
- return trueValue.equals(value) ? true : false;
- }
-
- /**
- * Read the 'boolean' value from column with given 'name'.
- *
- * @param name
- * the field name.
- * @param trueValue
- * the value that signifies {@link Boolean#TRUE true};
- * case-sensitive.
- * @throws IllegalArgumentException
- * if a column with given name is not defined, or if the
- * supplied trueValue is null.
- */
- public boolean readBoolean(String name, String trueValue) {
- return readBoolean(indexOf(name), trueValue);
- }
-
- /**
- * Read the 'char' value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public char readChar(int index) {
- String value = readAndTrim(index);
-
- Assert.isTrue(value.length() == 1, "Cannot convert field value '"
- + value + "' to char.");
-
- return value.charAt(0);
- }
-
- /**
- * Read the 'char' value from column with given 'name'.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public char readChar(String name) {
- return readChar(indexOf(name));
- }
-
- /**
- * Read the 'byte' value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public byte readByte(int index) {
- return Byte.parseByte(readAndTrim(index));
- }
-
- /**
- * Read the 'byte' value from column with given 'name'.
- *
- * @param name
- * the field name.
- */
- public byte readByte(String name) {
- return readByte(indexOf(name));
- }
-
- /**
- * Read the 'short' value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public short readShort(int index) {
- return Short.parseShort(readAndTrim(index));
- }
-
- /**
- * Read the 'short' value from column with given 'name'.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public short readShort(String name) {
- return readShort(indexOf(name));
- }
-
- /**
- * Read the 'int' value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public int readInt(int index) {
- return Integer.parseInt(readAndTrim(index));
- }
-
- /**
- * Read the 'int' value from column with given 'name'.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public int readInt(String name) {
- return readInt(indexOf(name));
- }
-
- /**
- * Read the 'int' value at index 'index',
- * using the supplied defaultValue if the field value is
- * blank.
- *
- * @param index
- * the field index..
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public int readInt(int index, int defaultValue) {
- String value = readAndTrim(index);
-
- return StringUtils.hasLength(value) ? Integer.parseInt(value)
- : defaultValue;
- }
-
- /**
- * Read the 'int' value from column with given 'name',
- * using the supplied defaultValue if the field value is
- * blank.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public int readInt(String name, int defaultValue) {
- return readInt(indexOf(name), defaultValue);
- }
-
- /**
- * Read the 'long' value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public long readLong(int index) {
- return Long.parseLong(readAndTrim(index));
- }
-
- /**
- * Read the 'long' value from column with given 'name'.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public long readLong(String name) {
- return readLong(indexOf(name));
- }
-
- /**
- * Read the 'long' value at index 'index',
- * using the supplied defaultValue if the field value is
- * blank.
- *
- * @param index
- * the field index..
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public long readLong(int index, long defaultValue) {
- String value = readAndTrim(index);
-
- return StringUtils.hasLength(value) ? Long.parseLong(value)
- : defaultValue;
- }
-
- /**
- * Read the 'long' value from column with given 'name',
- * using the supplied defaultValue if the field value is
- * blank.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public long readLong(String name, long defaultValue) {
- return readLong(indexOf(name), defaultValue);
- }
-
- /**
- * Read the 'float' value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public float readFloat(int index) {
- return Float.parseFloat(readAndTrim(index));
- }
-
- /**
- * Read the 'float' value from column with given 'name.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public float readFloat(String name) {
- return readFloat(indexOf(name));
- }
-
- /**
- * Read the 'double' value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public double readDouble(int index) {
- return Double.parseDouble(readAndTrim(index));
- }
-
- /**
- * Read the 'double' value from column with given 'name.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public double readDouble(String name) {
- return readDouble(indexOf(name));
- }
-
- /**
- * Read the {@link java.math.BigDecimal} value at index 'index'.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public BigDecimal readBigDecimal(int index) {
- return readBigDecimal(index, null);
- }
-
- /**
- * Read the {@link java.math.BigDecimal} value from column with given 'name.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public BigDecimal readBigDecimal(String name) {
- return readBigDecimal(name, null);
- }
-
- /**
- * Read the {@link BigDecimal} value at index 'index',
- * returning the supplied defaultValue if the trimmed string
- * value at index 'index' is blank.
- *
- * @param index
- * the field index.
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- */
- public BigDecimal readBigDecimal(int index, BigDecimal defaultValue) {
- String candidate = readAndTrim(index);
-
- try {
- return (StringUtils.hasText(candidate)) ? new BigDecimal(candidate)
- : defaultValue;
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("Unparseable number: "
- + candidate);
- }
- }
-
- /**
- * Read the {@link BigDecimal} value from column with given 'name,
- * returning the supplied defaultValue if the trimmed string
- * value at index 'index' is blank.
- *
- * @param name
- * the field name.
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- public BigDecimal readBigDecimal(String name, BigDecimal defaultValue) {
- try {
- return readBigDecimal(indexOf(name), defaultValue);
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException(e.getMessage() + ", name: ["
- + name + "]");
- }
- }
-
- /**
- * Read the java.util.Date value in default format at
- * designated column index.
- *
- * @param index
- * the field index.
- * @param pattern
- * the pattern describing the date and time format
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- * @see #DEFAULT_DATE_PATTERN
- */
- public Date readDate(int index) {
- return readDate(index, DEFAULT_DATE_PATTERN);
- }
-
- /**
- * Read the java.sql.Date value in given format from column
- * with given name.
- *
- * @param name
- * the field name.
- * @param pattern
- * the pattern describing the date and time format
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- * @see #DEFAULT_DATE_PATTERN
- */
- public Date readDate(String name) {
- return readDate(name, DEFAULT_DATE_PATTERN);
- }
-
- /**
- * Read the java.util.Date value in default format at
- * designated column index.
- *
- * @param index
- * the field index.
- * @param pattern
- * the pattern describing the date and time format
- * @throws IndexOutOfBoundsException
- * if the index is out of bounds.
- * @throws IllegalArgumentException
- * if the date cannot be parsed.
- *
- */
- public Date readDate(int index, String pattern) {
- SimpleDateFormat sdf = new SimpleDateFormat(pattern);
- Date date;
- String value = readAndTrim(index);
- try {
- date = sdf.parse(value);
- } catch (ParseException e) {
- throw new IllegalArgumentException(e.getMessage() + ", pattern: ["
- + pattern + "]");
- }
- return date;
- }
-
- /**
- * Read the java.sql.Date value in given format from column
- * with given name.
- *
- * @param name
- * the field name.
- * @param pattern
- * the pattern describing the date and time format
- * @throws IllegalArgumentException
- * if a column with given name is not defined or if the
- * specified field cannot be parsed
- *
- */
- public Date readDate(String name, String pattern) {
- try {
- return readDate(indexOf(name), pattern);
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException(e.getMessage() + ", name: ["
- + name + "]");
- }
- }
-
- /**
- * Return the number of fields in this 'FieldSet'.
- */
- public int getFieldCount() {
- return tokens.length;
- }
-
- /**
- * Read and trim the {@link String} value at 'index'.
- *
- * @throws NullPointerException
- * if the field value is null.
- */
- private String readAndTrim(int index) {
- String value = tokens[index];
-
- if (value != null) {
- return value.trim();
- } else {
- return value;
- }
- }
-
- /**
- * Read and trim the {@link String} value from column with given 'name.
- *
- * @throws IllegalArgumentException
- * if a column with given name is not defined.
- */
- private int indexOf(String name) {
- if (names == null) {
- throw new IllegalArgumentException(
- "Cannot access columns by name without meta data");
- }
- int index = names.indexOf(name);
- if (index >= 0) {
- return index;
- }
- throw new IllegalArgumentException("Cannot access column [" + name
- + "] from " + names);
- }
-
- public String toString() {
- if (names != null) {
- return getProperties().toString();
- }
- // TODO return "" instead of null?
- return tokens == null ? null : Arrays.asList(tokens).toString();
- }
-
- /**
- * @see java.lang.Object#equals(java.lang.Object)
- */
- public boolean equals(Object object) {
- if (object instanceof FieldSet) {
- FieldSet fs = (FieldSet) object;
-
- if (this.tokens == null) {
- return fs.tokens == null;
- } else {
- return Arrays.equals(this.tokens, fs.tokens);
- }
- }
-
- return false;
- }
-
- public int hashCode() {
- return (tokens == null) ? 0 : tokens.hashCode();
- }
-
- /**
- * Construct name-value pairs from the field names and string values.
- *
- * @return some properties representing the field set.
- *
- * @throws IllegalStateException
- * if the field name meta data is not available.
- */
- public Properties getProperties() {
- if (names == null) {
- throw new IllegalStateException(
- "Cannot create properties without meta data");
- }
- Properties props = new Properties();
- for (int i = 0; i < tokens.length; i++) {
- props.setProperty((String) names.get(i), readAndTrim(i));
- }
- return props;
- }
-
-}
+/*
+ * Copyright 2006-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.batch.io.file;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * @author Rob Harrop
+ * @author Dave Syer
+ */
+public final class FieldSet {
+
+ private final static String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";
+
+ /**
+ * The fields wrapped by this 'FieldSet' instance.
+ */
+ private String[] tokens;
+
+ private List names;
+
+ public FieldSet(String[] tokens) {
+ this.tokens = tokens == null ? null : (String[])tokens.clone();
+ }
+
+ public FieldSet(String[] tokens, String[] names) {
+ Assert.notNull(tokens);
+ Assert.notNull(names);
+ if (tokens.length != names.length) {
+ throw new IllegalArgumentException(
+ "Field names must be same length as values: names="
+ + Arrays.asList(names) + ", values="
+ + Arrays.asList(tokens));
+ }
+ this.tokens = (String[])tokens.clone();
+ this.names = Arrays.asList(names);
+ }
+
+ /**
+ * Read the {@link String} value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public String readString(int index) {
+ return readAndTrim(index);
+ }
+
+ /**
+ * Read the {@link String} value from column with given 'name'.
+ *
+ * @param name
+ * the field name.
+ */
+ public String readString(String name) {
+ return readString(indexOf(name));
+ }
+
+ /**
+ * Read the 'boolean' value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public boolean readBoolean(int index) {
+ return readBoolean(index, "true");
+ }
+
+ /**
+ * Read the 'boolean' value from column with given 'name'.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public boolean readBoolean(String name) {
+ return readBoolean(indexOf(name));
+ }
+
+ /**
+ * Read the 'boolean' value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @param trueValue
+ * the value that signifies {@link Boolean#TRUE true};
+ * case-sensitive.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds, or if the supplied
+ * trueValue is null.
+ */
+ public boolean readBoolean(int index, String trueValue) {
+ Assert.notNull(trueValue, "'trueValue' cannot be null.");
+
+ String value = readAndTrim(index);
+
+ return trueValue.equals(value) ? true : false;
+ }
+
+ /**
+ * Read the 'boolean' value from column with given 'name'.
+ *
+ * @param name
+ * the field name.
+ * @param trueValue
+ * the value that signifies {@link Boolean#TRUE true};
+ * case-sensitive.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined, or if the
+ * supplied trueValue is null.
+ */
+ public boolean readBoolean(String name, String trueValue) {
+ return readBoolean(indexOf(name), trueValue);
+ }
+
+ /**
+ * Read the 'char' value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public char readChar(int index) {
+ String value = readAndTrim(index);
+
+ Assert.isTrue(value.length() == 1, "Cannot convert field value '"
+ + value + "' to char.");
+
+ return value.charAt(0);
+ }
+
+ /**
+ * Read the 'char' value from column with given 'name'.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public char readChar(String name) {
+ return readChar(indexOf(name));
+ }
+
+ /**
+ * Read the 'byte' value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public byte readByte(int index) {
+ return Byte.parseByte(readAndTrim(index));
+ }
+
+ /**
+ * Read the 'byte' value from column with given 'name'.
+ *
+ * @param name
+ * the field name.
+ */
+ public byte readByte(String name) {
+ return readByte(indexOf(name));
+ }
+
+ /**
+ * Read the 'short' value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public short readShort(int index) {
+ return Short.parseShort(readAndTrim(index));
+ }
+
+ /**
+ * Read the 'short' value from column with given 'name'.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public short readShort(String name) {
+ return readShort(indexOf(name));
+ }
+
+ /**
+ * Read the 'int' value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public int readInt(int index) {
+ return Integer.parseInt(readAndTrim(index));
+ }
+
+ /**
+ * Read the 'int' value from column with given 'name'.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public int readInt(String name) {
+ return readInt(indexOf(name));
+ }
+
+ /**
+ * Read the 'int' value at index 'index',
+ * using the supplied defaultValue if the field value is
+ * blank.
+ *
+ * @param index
+ * the field index..
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public int readInt(int index, int defaultValue) {
+ String value = readAndTrim(index);
+
+ return StringUtils.hasLength(value) ? Integer.parseInt(value)
+ : defaultValue;
+ }
+
+ /**
+ * Read the 'int' value from column with given 'name',
+ * using the supplied defaultValue if the field value is
+ * blank.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public int readInt(String name, int defaultValue) {
+ return readInt(indexOf(name), defaultValue);
+ }
+
+ /**
+ * Read the 'long' value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public long readLong(int index) {
+ return Long.parseLong(readAndTrim(index));
+ }
+
+ /**
+ * Read the 'long' value from column with given 'name'.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public long readLong(String name) {
+ return readLong(indexOf(name));
+ }
+
+ /**
+ * Read the 'long' value at index 'index',
+ * using the supplied defaultValue if the field value is
+ * blank.
+ *
+ * @param index
+ * the field index..
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public long readLong(int index, long defaultValue) {
+ String value = readAndTrim(index);
+
+ return StringUtils.hasLength(value) ? Long.parseLong(value)
+ : defaultValue;
+ }
+
+ /**
+ * Read the 'long' value from column with given 'name',
+ * using the supplied defaultValue if the field value is
+ * blank.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public long readLong(String name, long defaultValue) {
+ return readLong(indexOf(name), defaultValue);
+ }
+
+ /**
+ * Read the 'float' value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public float readFloat(int index) {
+ return Float.parseFloat(readAndTrim(index));
+ }
+
+ /**
+ * Read the 'float' value from column with given 'name.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public float readFloat(String name) {
+ return readFloat(indexOf(name));
+ }
+
+ /**
+ * Read the 'double' value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public double readDouble(int index) {
+ return Double.parseDouble(readAndTrim(index));
+ }
+
+ /**
+ * Read the 'double' value from column with given 'name.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public double readDouble(String name) {
+ return readDouble(indexOf(name));
+ }
+
+ /**
+ * Read the {@link java.math.BigDecimal} value at index 'index'.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public BigDecimal readBigDecimal(int index) {
+ return readBigDecimal(index, null);
+ }
+
+ /**
+ * Read the {@link java.math.BigDecimal} value from column with given 'name.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public BigDecimal readBigDecimal(String name) {
+ return readBigDecimal(name, null);
+ }
+
+ /**
+ * Read the {@link BigDecimal} value at index 'index',
+ * returning the supplied defaultValue if the trimmed string
+ * value at index 'index' is blank.
+ *
+ * @param index
+ * the field index.
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ */
+ public BigDecimal readBigDecimal(int index, BigDecimal defaultValue) {
+ String candidate = readAndTrim(index);
+
+ try {
+ return (StringUtils.hasText(candidate)) ? new BigDecimal(candidate)
+ : defaultValue;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Unparseable number: "
+ + candidate);
+ }
+ }
+
+ /**
+ * Read the {@link BigDecimal} value from column with given 'name,
+ * returning the supplied defaultValue if the trimmed string
+ * value at index 'index' is blank.
+ *
+ * @param name
+ * the field name.
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ public BigDecimal readBigDecimal(String name, BigDecimal defaultValue) {
+ try {
+ return readBigDecimal(indexOf(name), defaultValue);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(e.getMessage() + ", name: ["
+ + name + "]");
+ }
+ }
+
+ /**
+ * Read the java.util.Date value in default format at
+ * designated column index.
+ *
+ * @param index
+ * the field index.
+ * @param pattern
+ * the pattern describing the date and time format
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ * @see #DEFAULT_DATE_PATTERN
+ */
+ public Date readDate(int index) {
+ return readDate(index, DEFAULT_DATE_PATTERN);
+ }
+
+ /**
+ * Read the java.sql.Date value in given format from column
+ * with given name.
+ *
+ * @param name
+ * the field name.
+ * @param pattern
+ * the pattern describing the date and time format
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ * @see #DEFAULT_DATE_PATTERN
+ */
+ public Date readDate(String name) {
+ return readDate(name, DEFAULT_DATE_PATTERN);
+ }
+
+ /**
+ * Read the java.util.Date value in default format at
+ * designated column index.
+ *
+ * @param index
+ * the field index.
+ * @param pattern
+ * the pattern describing the date and time format
+ * @throws IndexOutOfBoundsException
+ * if the index is out of bounds.
+ * @throws IllegalArgumentException
+ * if the date cannot be parsed.
+ *
+ */
+ public Date readDate(int index, String pattern) {
+ SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+ Date date;
+ String value = readAndTrim(index);
+ try {
+ date = sdf.parse(value);
+ } catch (ParseException e) {
+ throw new IllegalArgumentException(e.getMessage() + ", pattern: ["
+ + pattern + "]");
+ }
+ return date;
+ }
+
+ /**
+ * Read the java.sql.Date value in given format from column
+ * with given name.
+ *
+ * @param name
+ * the field name.
+ * @param pattern
+ * the pattern describing the date and time format
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined or if the
+ * specified field cannot be parsed
+ *
+ */
+ public Date readDate(String name, String pattern) {
+ try {
+ return readDate(indexOf(name), pattern);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(e.getMessage() + ", name: ["
+ + name + "]");
+ }
+ }
+
+ /**
+ * Return the number of fields in this 'FieldSet'.
+ */
+ public int getFieldCount() {
+ return tokens.length;
+ }
+
+ /**
+ * Read and trim the {@link String} value at 'index'.
+ *
+ * @throws NullPointerException
+ * if the field value is null.
+ */
+ private String readAndTrim(int index) {
+ String value = tokens[index];
+
+ if (value != null) {
+ return value.trim();
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * Read and trim the {@link String} value from column with given 'name.
+ *
+ * @throws IllegalArgumentException
+ * if a column with given name is not defined.
+ */
+ private int indexOf(String name) {
+ if (names == null) {
+ throw new IllegalArgumentException(
+ "Cannot access columns by name without meta data");
+ }
+ int index = names.indexOf(name);
+ if (index >= 0) {
+ return index;
+ }
+ throw new IllegalArgumentException("Cannot access column [" + name
+ + "] from " + names);
+ }
+
+ public String toString() {
+ if (names != null) {
+ return getProperties().toString();
+ }
+
+ return tokens == null ? "" : Arrays.asList(tokens).toString();
+ }
+
+ /**
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object object) {
+ if (object instanceof FieldSet) {
+ FieldSet fs = (FieldSet) object;
+
+ if (this.tokens == null) {
+ return fs.tokens == null;
+ } else {
+ return Arrays.equals(this.tokens, fs.tokens);
+ }
+ }
+
+ return false;
+ }
+
+ public int hashCode() {
+ //this algorithm was taken from java 1.5 jdk Arrays.hashCode(Object[])
+ if (tokens == null) {
+ return 0;
+ }
+
+ int result = 1;
+
+ for (int i = 0; i < tokens.length; i++) {
+ result = 31 * result + (tokens[i] == null ? 0 : tokens[i].hashCode());
+ }
+
+ return result;
+ }
+
+ /**
+ * Construct name-value pairs from the field names and string values.
+ *
+ * @return some properties representing the field set.
+ *
+ * @throws IllegalStateException
+ * if the field name meta data is not available.
+ */
+ public Properties getProperties() {
+ if (names == null) {
+ throw new IllegalStateException(
+ "Cannot create properties without meta data");
+ }
+ Properties props = new Properties();
+ for (int i = 0; i < tokens.length; i++) {
+ props.setProperty((String) names.get(i), readAndTrim(i));
+ }
+ return props;
+ }
+
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/file/support/FlatFileOutputSource.java b/infrastructure/src/main/java/org/springframework/batch/io/file/support/FlatFileOutputSource.java
index 333d11c8a..291501e1b 100644
--- a/infrastructure/src/main/java/org/springframework/batch/io/file/support/FlatFileOutputSource.java
+++ b/infrastructure/src/main/java/org/springframework/batch/io/file/support/FlatFileOutputSource.java
@@ -1,578 +1,578 @@
-/*
- * Copyright 2006-2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.io.file.support;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.charset.UnsupportedCharsetException;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.Properties;
-
-import org.springframework.batch.io.OutputSource;
-import org.springframework.batch.io.exception.BatchCriticalException;
-import org.springframework.batch.io.exception.BatchEnvironmentException;
-import org.springframework.batch.io.file.support.transform.Converter;
-import org.springframework.batch.item.ResourceLifecycle;
-import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
-import org.springframework.batch.restart.GenericRestartData;
-import org.springframework.batch.restart.RestartData;
-import org.springframework.batch.restart.Restartable;
-import org.springframework.batch.statistics.StatisticsProvider;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.beans.factory.InitializingBean;
-import org.springframework.core.io.Resource;
-import org.springframework.dao.DataAccessResourceFailureException;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationAdapter;
-import org.springframework.util.Assert;
-
-/**
- * This class is an output target that writes data to a file or stream. The
- * output source also provides restart, statistics and transaction features by
- * implementing corresponding interfaces where possible (with a file). The
- * location of the file is defined by a {@link Resource} and must represent a
- * writable file.
- *
- * Uses buffered writer to improve performance.
- *
- * Use {@link #write(String)} method to output a line to an output source.
- *
- * @author Waseem Malik
- * @author Tomas Slanina
- * @author Robert Kasanicky
- * @author Dave Syer
- */
-public class FlatFileOutputSource implements OutputSource, ResourceLifecycle, Restartable, StatisticsProvider, InitializingBean,
- DisposableBean {
-
- /**
- * @author dsyer
- *
- */
- public class BooleanHolder {
-
- public boolean value;
-
- }
-
- private static final String LINE_SEPARATOR = System.getProperty("line.separator");
-
- public static final String WRITTEN_STATISTICS_NAME = "Written";
-
- public static final String RESTART_COUNT_STATISTICS_NAME = "Restart count";
-
- public static final String RESTART_DATA_NAME = "flatfileoutputtemplate.currentLine";
-
- private Resource resource;
-
- private Properties statistics = new Properties();
-
- private RestartData restartData = new GenericRestartData(new Properties());
-
- private TransactionSynchronization transactionSynchronization = new FlatFileOutputTemplateTransactionSynchronization();
-
- private OutputState state = new OutputState();
-
- private Converter converter = new Converter() {
- public Object convert(Object input) {
- return "" + input;
- }
- };
-
- public void afterPropertiesSet() throws Exception {
- Assert.notNull(resource);
- File file = resource.getFile();
- Assert.state(!file.exists() || file.canWrite(), "Resource is not writable: [" + resource + "]");
- }
-
- /**
- * Public setter for the converter. If not-null this will be used to convert
- * the input data before it is output.
- *
- * @param converter the converter to set
- */
- public void setConverter(Converter converter) {
- this.converter = converter;
- }
-
- /**
- * Setter for resource. Represents a file that can be written.
- *
- * @param resource
- */
- public void setResource(Resource resource) {
- this.resource = resource;
- }
-
- /**
- * Commit the transaction.
- */
- private void transactionComitted() {
- getOutputState().mark();
- }
-
- /**
- * Rollback the transaction.
- */
- private void transactionRolledback() {
- getOutputState().checkFileSize();
- resetPositionForRestart();
- }
-
- // This method removes any information in the file before this reset point.
- private void resetPositionForRestart() {
- getOutputState().truncate();
- }
-
- /**
- * Writes out a string followed by a "new line", where the format of the new
- * line separator is determined by the underlying operating system. If the
- * input is not a String and a converter is available the converter will be
- * applied and then this method recursively called with the result. If the
- * input is an array or collection each value will be written to a separate
- * line (recursively calling this method for each value). If no converter is
- * supplied the input object's toString method will be used.
- *
- * @param data Object (a String or Object that can be converted) to be
- * written to output stream
- */
- public void write(Object data) {
- convertAndWrite(data, new BooleanHolder());
- }
-
- /**
- * Convert the date to a format that can be output and then write it out.
- * @param data
- * @param converted
- */
- private void convertAndWrite(Object data, BooleanHolder converted) {
-
- if (data instanceof Collection) {
- converted.value = false;
- for (Iterator iterator = ((Collection) data).iterator(); iterator.hasNext();) {
- Object value = (Object) iterator.next();
- // (recursive)
- write(value);
- }
- return;
- }
- if (data.getClass().isArray()) {
- converted.value = false;
- Object[] array = (Object[]) data;
- for (int i = 0; i < array.length; i++) {
- Object value = array[i];
- // (recursive)
- write(value);
- }
- return;
- }
- if (data instanceof String) {
- // This is where the output stream is actually written to
- getOutputState().write(data + LINE_SEPARATOR);
- }
- else if (!converted.value) {
- // (recursive)
- converted.value = true;
- convertAndWrite(converter.convert(data), converted);
- return;
- }
- else {
- // Should not happen...
- throw new IllegalStateException(
- "Infinite loop detected - converter did not convert to String or collection/array of objects convertible to String.");
- }
- }
-
- /**
- * @see ResourceLifecycle#close()
- */
- public void close() {
- getOutputState().close();
- }
-
- /**
- * Calls close to ensure that bean factories can close and always release
- * resources.
- *
- * @see org.springframework.beans.factory.DisposableBean#destroy()
- */
- public void destroy() throws Exception {
- close();
- }
-
- /**
- * Sets encoding for output template.
- */
- public void setEncoding(String newEncoding) {
- getOutputState().setEncoding(newEncoding);
- }
-
- /**
- * Sets buffer size for output template
- */
- public void setBufferSize(int newSize) {
- getOutputState().setBufferSize(newSize);
- }
-
- /**
- * @param shouldDeleteIfExists the shouldDeleteIfExists to set
- */
- public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
- getOutputState().setShouldDeleteIfExists(shouldDeleteIfExists);
- }
-
- /**
- * Initialize the Output Template.
- * @see ResourceLifecycle#open()
- */
- public void open() {
- registerSynchronization();
- }
-
- /**
- * @see StatisticsProvider
- */
- public Properties getStatistics() {
- final OutputState os = getOutputState();
-
- statistics.setProperty(WRITTEN_STATISTICS_NAME, String.valueOf(os.linesWritten));
- statistics.setProperty(RESTART_COUNT_STATISTICS_NAME, String.valueOf(os.restartCount));
- return statistics;
- }
-
- /**
- * @see Restartable#getRestartData()
- */
- public RestartData getRestartData() {
- final OutputState os = getOutputState();
-
- restartData.getProperties().setProperty(RESTART_DATA_NAME, String.valueOf(os.position()));
- return restartData;
- }
-
- /**
- * @see Restartable#restoreFrom(RestartData)
- */
- public void restoreFrom(RestartData data) {
- if (data == null)
- return;
-
- getOutputState().restoreFrom(data.getProperties());
-
- }
-
- // Registers a new transaction synchronization for the current thread.
- private void registerSynchronization() {
- BatchTransactionSynchronizationManager.registerSynchronization(this.transactionSynchronization);
- }
-
- // Returns object representing state.
- private OutputState getOutputState() {
- return (OutputState) state;
- }
-
- // added package visibility method so that tests can invoke transaction
- // events
- TransactionSynchronization getTransactionSynchronization() {
- return this.transactionSynchronization;
- }
-
- /**
- * Encapsulates the runtime state of the output source. All state changing
- * operations on the output source go through this class.
- */
- private class OutputState {
- // default encoding for writing to output files - set to UTF-8.
- private static final String DEFAULT_CHARSET = "UTF-8";
-
- private static final int DEFAULT_BUFFER_SIZE = 2048;
-
- // The bufferedWriter over the file channel that is actually written
- BufferedWriter outputBufferedWriter;
-
- FileChannel fileChannel;
-
- // this represents the charset encoding (if any is needed) for the
- // output file
- String encoding = DEFAULT_CHARSET;
-
- // Optional write buffer size
- int bufferSize = DEFAULT_BUFFER_SIZE;
-
- boolean restarted = false;
-
- boolean initialized = false;
-
- long lastMarkedByteOffsetPosition = 0;
-
- long linesWritten = 0;
-
- long restartCount = 0;
-
- boolean shouldDeleteIfExists = true;
-
- /**
- * Return the byte offset position of the cursor in the output file as a
- * long integer.
- */
- public long position() {
- long pos = 0;
-
- if (fileChannel == null) {
- return 0;
- }
-
- try {
- outputBufferedWriter.flush();
- pos = fileChannel.position();
- }
- catch (IOException e) {
- throw new BatchCriticalException("An Error occured while trying to get filechannel position", e);
- }
-
- return pos;
-
- }
-
- /**
- * @param properties
- */
- public void restoreFrom(Properties properties) {
- lastMarkedByteOffsetPosition = Long.parseLong(properties.getProperty(RESTART_DATA_NAME));
- restarted = true;
- }
-
- /**
- * @param shouldDeleteIfExists2
- */
- public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
- this.shouldDeleteIfExists = shouldDeleteIfExists;
- }
-
- /**
- * @param newSize
- */
- public void setBufferSize(int newSize) {
- bufferSize = newSize;
- }
-
- /**
- * @param newEncoding
- */
- public void setEncoding(String newEncoding) {
- encoding = newEncoding;
- }
-
- /**
- * Close the open resource and reset counters.
- */
- public void close() {
- initialized = false;
- restarted = false;
- try {
- if (outputBufferedWriter == null) {
- return;
- }
- outputBufferedWriter.close();
- fileChannel.close();
- }
- catch (IOException ioe) {
- throw new BatchEnvironmentException("Unable to close the the Output Source", ioe);
- }
- }
-
- /**
- * @param data
- * @param offset
- * @param length
- */
- public void write(String line) {
- if (!initialized) {
- initializeBufferedWriter();
- }
-
- try {
- outputBufferedWriter.write(line);
- outputBufferedWriter.flush();
- linesWritten++;
- }
- catch (IOException e) {
- throw new BatchCriticalException("An Error occured while trying to write to FileWriterOutputSource", e);
- }
- }
-
- /**
- * Truncate the output at the last known good point.
- */
- public void truncate() {
- try {
- fileChannel.truncate(lastMarkedByteOffsetPosition);
- fileChannel.position(lastMarkedByteOffsetPosition);
- }
- catch (Exception e) {
- throw new BatchCriticalException("An Error occured while reseting position in a file for restart", e);
- }
- }
-
- /**
- * Mark the current position.
- */
- public void mark() {
- lastMarkedByteOffsetPosition = this.position();
- }
-
- /**
- * Creates the buffered writer for the output file channel based on
- * configuration information.
- */
- private void initializeBufferedWriter() {
- File file;
-
- try {
- file = resource.getFile();
-
- // If the output source was restarted, keep existing file.
- // If the output source was not restarted, check following:
- // - if the file should be deleted, delete it if it was exiting
- // and create blank file,
- // - if the file should not be deleted, if it already exists,
- // throw an exception,
- // - if the file was not existing, create new.
- if (!restarted) {
- if (file.exists()) {
- if (shouldDeleteIfExists) {
- file.delete();
- }
- else {
- throw new BatchEnvironmentException("Resource already exists: " + resource);
- }
- }
- file.createNewFile();
- }
-
- }
- catch (IOException ioe) {
- throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]",
- ioe);
- }
-
- try {
- fileChannel = (new FileOutputStream(file.getAbsolutePath(), true)).getChannel();
- }
- catch (FileNotFoundException fnfe) {
- throw new BatchEnvironmentException("Bad filename property parameter " + file, fnfe);
- }
-
- outputBufferedWriter = getBufferedWriter(fileChannel, encoding, bufferSize);
-
- // in case of restarting reset position to last commited point
- if (restarted) {
- this.resetPosition();
- }
-
- initialized = true;
- linesWritten = 0;
- }
-
- /**
- * Returns the buffered writer opened to the beginning of the file
- * specified by the absolute path name contained in absoluteFileName.
- */
- private BufferedWriter getBufferedWriter(FileChannel fileChannel, String encoding, int bufferSize) {
- try {
-
- BufferedWriter outputBufferedWriter = null;
-
- // If a buffer was requested, allocate.
- if (bufferSize > 0) {
- outputBufferedWriter = new BufferedWriter(Channels.newWriter(fileChannel, encoding), bufferSize);
- }
- else {
- outputBufferedWriter = new BufferedWriter(Channels.newWriter(fileChannel, encoding));
- }
-
- return outputBufferedWriter;
- }
- catch (UnsupportedCharsetException ucse) {
- throw new BatchEnvironmentException("Bad encoding configuration for output file " + fileChannel, ucse);
- }
- }
-
- /**
- * Resets the file writer's current position to the point stored in the
- * last marked byte offset position variable. It first checks to make
- * sure the current size of the file is not less than the byte position
- * to be moved to (if it is, throws an environment exception), then it
- * truncates the file to that reset position, and set the cursor to
- * start writing at that point.
- */
- private void resetPosition() {
- checkFileSize();
- resetPositionForRestart();
- }
-
- /**
- * Checks (on setState) to make sure that the current output file's size
- * is not smaller than the last saved commit point. If it is, then the
- * file has been damaged in some way and whole task must be started over
- * again from the beginning.
- */
- public void checkFileSize() {
- long size = -1;
-
- try {
- outputBufferedWriter.flush();
- size = fileChannel.size();
- }
- catch (Exception e) {
- throw new BatchCriticalException("An Error occured while checking file size", e);
- }
-
- if (size < lastMarkedByteOffsetPosition) {
- throw new BatchCriticalException("Current file size is smaller than size at last commit");
- }
- }
-
- }
-
- /**
- * Encapsulates transaction events.
- */
- private class FlatFileOutputTemplateTransactionSynchronization extends TransactionSynchronizationAdapter {
- /**
- * TransactionSynchronization method indicating that a transaction has
- * completed.
- *
- * @param status indicates whether it was a rollback or commit
- */
- public void afterCompletion(int status) {
- if (status == TransactionSynchronization.STATUS_COMMITTED) {
- transactionComitted();
- }
- else if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
- transactionRolledback();
- }
- }
- }
-
-}
+/*
+ * Copyright 2006-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.batch.io.file.support;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Properties;
+
+import org.springframework.batch.io.OutputSource;
+import org.springframework.batch.io.exception.BatchCriticalException;
+import org.springframework.batch.io.exception.BatchEnvironmentException;
+import org.springframework.batch.io.file.support.transform.Converter;
+import org.springframework.batch.item.ResourceLifecycle;
+import org.springframework.batch.repeat.synch.BatchTransactionSynchronizationManager;
+import org.springframework.batch.restart.GenericRestartData;
+import org.springframework.batch.restart.RestartData;
+import org.springframework.batch.restart.Restartable;
+import org.springframework.batch.statistics.StatisticsProvider;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.core.io.Resource;
+import org.springframework.dao.DataAccessResourceFailureException;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationAdapter;
+import org.springframework.util.Assert;
+
+/**
+ * This class is an output target that writes data to a file or stream. The
+ * output source also provides restart, statistics and transaction features by
+ * implementing corresponding interfaces where possible (with a file). The
+ * location of the file is defined by a {@link Resource} and must represent a
+ * writable file.
+ *
+ * Uses buffered writer to improve performance.
+ *
+ * Use {@link #write(String)} method to output a line to an output source.
+ *
+ * @author Waseem Malik
+ * @author Tomas Slanina
+ * @author Robert Kasanicky
+ * @author Dave Syer
+ */
+public class FlatFileOutputSource implements OutputSource, ResourceLifecycle, Restartable, StatisticsProvider, InitializingBean,
+ DisposableBean {
+
+ /**
+ * @author dsyer
+ *
+ */
+ public static class BooleanHolder {
+
+ public boolean value;
+
+ }
+
+ private static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ public static final String WRITTEN_STATISTICS_NAME = "Written";
+
+ public static final String RESTART_COUNT_STATISTICS_NAME = "Restart count";
+
+ public static final String RESTART_DATA_NAME = "flatfileoutputtemplate.currentLine";
+
+ private Resource resource;
+
+ private Properties statistics = new Properties();
+
+ private RestartData restartData = new GenericRestartData(new Properties());
+
+ private TransactionSynchronization transactionSynchronization = new FlatFileOutputTemplateTransactionSynchronization();
+
+ private OutputState state = new OutputState();
+
+ private Converter converter = new Converter() {
+ public Object convert(Object input) {
+ return "" + input;
+ }
+ };
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(resource);
+ File file = resource.getFile();
+ Assert.state(!file.exists() || file.canWrite(), "Resource is not writable: [" + resource + "]");
+ }
+
+ /**
+ * Public setter for the converter. If not-null this will be used to convert
+ * the input data before it is output.
+ *
+ * @param converter the converter to set
+ */
+ public void setConverter(Converter converter) {
+ this.converter = converter;
+ }
+
+ /**
+ * Setter for resource. Represents a file that can be written.
+ *
+ * @param resource
+ */
+ public void setResource(Resource resource) {
+ this.resource = resource;
+ }
+
+ /**
+ * Commit the transaction.
+ */
+ private void transactionComitted() {
+ getOutputState().mark();
+ }
+
+ /**
+ * Rollback the transaction.
+ */
+ private void transactionRolledback() {
+ getOutputState().checkFileSize();
+ resetPositionForRestart();
+ }
+
+ // This method removes any information in the file before this reset point.
+ private void resetPositionForRestart() {
+ getOutputState().truncate();
+ }
+
+ /**
+ * Writes out a string followed by a "new line", where the format of the new
+ * line separator is determined by the underlying operating system. If the
+ * input is not a String and a converter is available the converter will be
+ * applied and then this method recursively called with the result. If the
+ * input is an array or collection each value will be written to a separate
+ * line (recursively calling this method for each value). If no converter is
+ * supplied the input object's toString method will be used.
+ *
+ * @param data Object (a String or Object that can be converted) to be
+ * written to output stream
+ */
+ public void write(Object data) {
+ convertAndWrite(data, new BooleanHolder());
+ }
+
+ /**
+ * Convert the date to a format that can be output and then write it out.
+ * @param data
+ * @param converted
+ */
+ private void convertAndWrite(Object data, BooleanHolder converted) {
+
+ if (data instanceof Collection) {
+ converted.value = false;
+ for (Iterator iterator = ((Collection) data).iterator(); iterator.hasNext();) {
+ Object value = (Object) iterator.next();
+ // (recursive)
+ write(value);
+ }
+ return;
+ }
+ if (data.getClass().isArray()) {
+ converted.value = false;
+ Object[] array = (Object[]) data;
+ for (int i = 0; i < array.length; i++) {
+ Object value = array[i];
+ // (recursive)
+ write(value);
+ }
+ return;
+ }
+ if (data instanceof String) {
+ // This is where the output stream is actually written to
+ getOutputState().write(data + LINE_SEPARATOR);
+ }
+ else if (!converted.value) {
+ // (recursive)
+ converted.value = true;
+ convertAndWrite(converter.convert(data), converted);
+ return;
+ }
+ else {
+ // Should not happen...
+ throw new IllegalStateException(
+ "Infinite loop detected - converter did not convert to String or collection/array of objects convertible to String.");
+ }
+ }
+
+ /**
+ * @see ResourceLifecycle#close()
+ */
+ public void close() {
+ getOutputState().close();
+ }
+
+ /**
+ * Calls close to ensure that bean factories can close and always release
+ * resources.
+ *
+ * @see org.springframework.beans.factory.DisposableBean#destroy()
+ */
+ public void destroy() throws Exception {
+ close();
+ }
+
+ /**
+ * Sets encoding for output template.
+ */
+ public void setEncoding(String newEncoding) {
+ getOutputState().setEncoding(newEncoding);
+ }
+
+ /**
+ * Sets buffer size for output template
+ */
+ public void setBufferSize(int newSize) {
+ getOutputState().setBufferSize(newSize);
+ }
+
+ /**
+ * @param shouldDeleteIfExists the shouldDeleteIfExists to set
+ */
+ public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
+ getOutputState().setShouldDeleteIfExists(shouldDeleteIfExists);
+ }
+
+ /**
+ * Initialize the Output Template.
+ * @see ResourceLifecycle#open()
+ */
+ public void open() {
+ registerSynchronization();
+ }
+
+ /**
+ * @see StatisticsProvider
+ */
+ public Properties getStatistics() {
+ final OutputState os = getOutputState();
+
+ statistics.setProperty(WRITTEN_STATISTICS_NAME, String.valueOf(os.linesWritten));
+ statistics.setProperty(RESTART_COUNT_STATISTICS_NAME, String.valueOf(os.restartCount));
+ return statistics;
+ }
+
+ /**
+ * @see Restartable#getRestartData()
+ */
+ public RestartData getRestartData() {
+ final OutputState os = getOutputState();
+
+ restartData.getProperties().setProperty(RESTART_DATA_NAME, String.valueOf(os.position()));
+ return restartData;
+ }
+
+ /**
+ * @see Restartable#restoreFrom(RestartData)
+ */
+ public void restoreFrom(RestartData data) {
+ if (data == null)
+ return;
+
+ getOutputState().restoreFrom(data.getProperties());
+
+ }
+
+ // Registers a new transaction synchronization for the current thread.
+ private void registerSynchronization() {
+ BatchTransactionSynchronizationManager.registerSynchronization(this.transactionSynchronization);
+ }
+
+ // Returns object representing state.
+ private OutputState getOutputState() {
+ return (OutputState) state;
+ }
+
+ // added package visibility method so that tests can invoke transaction
+ // events
+ TransactionSynchronization getTransactionSynchronization() {
+ return this.transactionSynchronization;
+ }
+
+ /**
+ * Encapsulates the runtime state of the output source. All state changing
+ * operations on the output source go through this class.
+ */
+ private class OutputState {
+ // default encoding for writing to output files - set to UTF-8.
+ private static final String DEFAULT_CHARSET = "UTF-8";
+
+ private static final int DEFAULT_BUFFER_SIZE = 2048;
+
+ // The bufferedWriter over the file channel that is actually written
+ BufferedWriter outputBufferedWriter;
+
+ FileChannel fileChannel;
+
+ // this represents the charset encoding (if any is needed) for the
+ // output file
+ String encoding = DEFAULT_CHARSET;
+
+ // Optional write buffer size
+ int bufferSize = DEFAULT_BUFFER_SIZE;
+
+ boolean restarted = false;
+
+ boolean initialized = false;
+
+ long lastMarkedByteOffsetPosition = 0;
+
+ long linesWritten = 0;
+
+ long restartCount = 0;
+
+ boolean shouldDeleteIfExists = true;
+
+ /**
+ * Return the byte offset position of the cursor in the output file as a
+ * long integer.
+ */
+ public long position() {
+ long pos = 0;
+
+ if (fileChannel == null) {
+ return 0;
+ }
+
+ try {
+ outputBufferedWriter.flush();
+ pos = fileChannel.position();
+ }
+ catch (IOException e) {
+ throw new BatchCriticalException("An Error occured while trying to get filechannel position", e);
+ }
+
+ return pos;
+
+ }
+
+ /**
+ * @param properties
+ */
+ public void restoreFrom(Properties properties) {
+ lastMarkedByteOffsetPosition = Long.parseLong(properties.getProperty(RESTART_DATA_NAME));
+ restarted = true;
+ }
+
+ /**
+ * @param shouldDeleteIfExists2
+ */
+ public void setShouldDeleteIfExists(boolean shouldDeleteIfExists) {
+ this.shouldDeleteIfExists = shouldDeleteIfExists;
+ }
+
+ /**
+ * @param newSize
+ */
+ public void setBufferSize(int newSize) {
+ bufferSize = newSize;
+ }
+
+ /**
+ * @param newEncoding
+ */
+ public void setEncoding(String newEncoding) {
+ encoding = newEncoding;
+ }
+
+ /**
+ * Close the open resource and reset counters.
+ */
+ public void close() {
+ initialized = false;
+ restarted = false;
+ try {
+ if (outputBufferedWriter == null) {
+ return;
+ }
+ outputBufferedWriter.close();
+ fileChannel.close();
+ }
+ catch (IOException ioe) {
+ throw new BatchEnvironmentException("Unable to close the the Output Source", ioe);
+ }
+ }
+
+ /**
+ * @param data
+ * @param offset
+ * @param length
+ */
+ public void write(String line) {
+ if (!initialized) {
+ initializeBufferedWriter();
+ }
+
+ try {
+ outputBufferedWriter.write(line);
+ outputBufferedWriter.flush();
+ linesWritten++;
+ }
+ catch (IOException e) {
+ throw new BatchCriticalException("An Error occured while trying to write to FileWriterOutputSource", e);
+ }
+ }
+
+ /**
+ * Truncate the output at the last known good point.
+ */
+ public void truncate() {
+ try {
+ fileChannel.truncate(lastMarkedByteOffsetPosition);
+ fileChannel.position(lastMarkedByteOffsetPosition);
+ }
+ catch (Exception e) {
+ throw new BatchCriticalException("An Error occured while reseting position in a file for restart", e);
+ }
+ }
+
+ /**
+ * Mark the current position.
+ */
+ public void mark() {
+ lastMarkedByteOffsetPosition = this.position();
+ }
+
+ /**
+ * Creates the buffered writer for the output file channel based on
+ * configuration information.
+ */
+ private void initializeBufferedWriter() {
+ File file;
+
+ try {
+ file = resource.getFile();
+
+ // If the output source was restarted, keep existing file.
+ // If the output source was not restarted, check following:
+ // - if the file should be deleted, delete it if it was exiting
+ // and create blank file,
+ // - if the file should not be deleted, if it already exists,
+ // throw an exception,
+ // - if the file was not existing, create new.
+ if (!restarted) {
+ if (file.exists()) {
+ if (shouldDeleteIfExists) {
+ file.delete();
+ }
+ else {
+ throw new BatchEnvironmentException("Resource already exists: " + resource);
+ }
+ }
+ file.createNewFile();
+ }
+
+ }
+ catch (IOException ioe) {
+ throw new DataAccessResourceFailureException("Unable to write to file resource: [" + resource + "]",
+ ioe);
+ }
+
+ try {
+ fileChannel = (new FileOutputStream(file.getAbsolutePath(), true)).getChannel();
+ }
+ catch (FileNotFoundException fnfe) {
+ throw new BatchEnvironmentException("Bad filename property parameter " + file, fnfe);
+ }
+
+ outputBufferedWriter = getBufferedWriter(fileChannel, encoding, bufferSize);
+
+ // in case of restarting reset position to last commited point
+ if (restarted) {
+ this.resetPosition();
+ }
+
+ initialized = true;
+ linesWritten = 0;
+ }
+
+ /**
+ * Returns the buffered writer opened to the beginning of the file
+ * specified by the absolute path name contained in absoluteFileName.
+ */
+ private BufferedWriter getBufferedWriter(FileChannel fileChannel, String encoding, int bufferSize) {
+ try {
+
+ BufferedWriter outputBufferedWriter = null;
+
+ // If a buffer was requested, allocate.
+ if (bufferSize > 0) {
+ outputBufferedWriter = new BufferedWriter(Channels.newWriter(fileChannel, encoding), bufferSize);
+ }
+ else {
+ outputBufferedWriter = new BufferedWriter(Channels.newWriter(fileChannel, encoding));
+ }
+
+ return outputBufferedWriter;
+ }
+ catch (UnsupportedCharsetException ucse) {
+ throw new BatchEnvironmentException("Bad encoding configuration for output file " + fileChannel, ucse);
+ }
+ }
+
+ /**
+ * Resets the file writer's current position to the point stored in the
+ * last marked byte offset position variable. It first checks to make
+ * sure the current size of the file is not less than the byte position
+ * to be moved to (if it is, throws an environment exception), then it
+ * truncates the file to that reset position, and set the cursor to
+ * start writing at that point.
+ */
+ private void resetPosition() {
+ checkFileSize();
+ resetPositionForRestart();
+ }
+
+ /**
+ * Checks (on setState) to make sure that the current output file's size
+ * is not smaller than the last saved commit point. If it is, then the
+ * file has been damaged in some way and whole task must be started over
+ * again from the beginning.
+ */
+ public void checkFileSize() {
+ long size = -1;
+
+ try {
+ outputBufferedWriter.flush();
+ size = fileChannel.size();
+ }
+ catch (Exception e) {
+ throw new BatchCriticalException("An Error occured while checking file size", e);
+ }
+
+ if (size < lastMarkedByteOffsetPosition) {
+ throw new BatchCriticalException("Current file size is smaller than size at last commit");
+ }
+ }
+
+ }
+
+ /**
+ * Encapsulates transaction events.
+ */
+ private class FlatFileOutputTemplateTransactionSynchronization extends TransactionSynchronizationAdapter {
+ /**
+ * TransactionSynchronization method indicating that a transaction has
+ * completed.
+ *
+ * @param status indicates whether it was a rollback or commit
+ */
+ public void afterCompletion(int status) {
+ if (status == TransactionSynchronization.STATUS_COMMITTED) {
+ transactionComitted();
+ }
+ else if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
+ transactionRolledback();
+ }
+ }
+ }
+
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/DelimitedLineTokenizer.java b/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/DelimitedLineTokenizer.java
index 306d6496b..a1e816c32 100644
--- a/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/DelimitedLineTokenizer.java
+++ b/infrastructure/src/main/java/org/springframework/batch/io/file/support/transform/DelimitedLineTokenizer.java
@@ -1,186 +1,183 @@
-/*
- * Copyright 2006-2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.io.file.support.transform;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.springframework.batch.io.exception.BatchConfigurationException;
-import org.springframework.util.StringUtils;
-
-/**
- *
- * @author Rob Harrop
- * @author Dave Syer
- *
- */
-public class DelimitedLineTokenizer extends AbstractLineTokenizer {
- /**
- * Convenient constant for the common case of a tab delimiter.
- */
- public static final char DELIMITER_TAB = '\t';
-
- /**
- * Convenient constant for the common case of a comma delimiter.
- */
- public static final char DELIMITER_COMMA = ',';
-
- /**
- * Convenient constant for the common case of a " character used to escape
- * delimiters or line endings.
- */
- public static final char DEFAULT_QUOTE_CHARACTER = '"';
-
- // the delimiter character used when reading input.
- private char delimiter;
-
- private char quoteCharacter = DEFAULT_QUOTE_CHARACTER;
-
- /**
- * Create a new instance of the {@link DelimitedLineTokenizer} class for the
- * common case where the delimiter is a {@link #DELIMITER_COMMA comma}.
- *
- * @see #DelimitedLineTokenizer(char)
- * @see #DELIMITER_COMMA
- */
- public DelimitedLineTokenizer() {
- this(DELIMITER_COMMA);
- }
-
- /**
- * Create a new instance of the {@link DelimitedLineTokenizer} class.
- *
- * @param delimiter the desired delimiter
- */
- public DelimitedLineTokenizer(char delimiter) {
- if (delimiter == DEFAULT_QUOTE_CHARACTER) {
- throw new BatchConfigurationException("'" + DEFAULT_QUOTE_CHARACTER
- + "' is not allowed as delimiter for tokenizers.");
- }
-
- this.delimiter = delimiter;
- }
-
- /**
- * Setter for the delimiter character.
- * @param delimiter
- */
- public void setDelimiter(char delimiter) {
- this.delimiter = delimiter;
- }
-
- /**
- * Public setter for the quoteCharacter. The quote character can be used to
- * extend a field across line endings or to enclose a String which contains
- * the delimiter. Inside a quoted token the quote character can be used to
- * escape itself, thus "a""b""c" is tokenized to a"b"c.
- *
- * @param quoteCharacter the quoteCharacter to set
- *
- * @see #DEFAULT_QUOTE_CHARACTER
- */
- public void setQuoteCharacter(char quoteCharacter) {
- this.quoteCharacter = quoteCharacter;
- }
-
- /**
- * Yields the tokens resulting from the splitting of the supplied
- * line.
- *
- * @param line the line to be tokenised (can be null)
- *
- * @return the resulting tokens
- */
- public List doTokenize(String line) {
-
- List tokens = new ArrayList();
-
- char[] chars = line.toCharArray();
- boolean inQuoted = false;
- char lastChar = 0;
- int lastCut = 0;
- int length = chars.length;
-
- // TODO if line was null there would be exception while getting chars
- // value
- if (line != null) {
-
- for (int i = 0; i < length; i++) {
-
- char currentChar = chars[i];
- boolean isEnd = (i == (length - 1));
-
- if ((isDelimiterCharacter(currentChar) && !inQuoted) || isEnd) {
- int endPosition = (isEnd ? (length - lastCut) : (i - lastCut));
-
- if (isEnd && isDelimiterCharacter(currentChar)) {
- endPosition--;
- }
-
- String value = null;
-
- if (isQuoteCharacter(lastChar) || isQuoteCharacter(currentChar)) {
- value = new String(chars, lastCut + 1, endPosition - 2);
- value = StringUtils.replace(value, "" + quoteCharacter + quoteCharacter, "" + quoteCharacter);
- }
- else {
- value = new String(chars, lastCut, endPosition);
- }
-
- tokens.add(value);
-
- if (isEnd && (isDelimiterCharacter(currentChar))) {
- tokens.add("");
- }
-
- lastCut = i + 1;
- }
- else if (isQuoteCharacter(currentChar)) {
- inQuoted = !inQuoted;
- }
-
- lastChar = currentChar;
- }
- }
-
- return tokens;
- }
-
- /**
- * Is the supplied character the delimiter character?
- *
- * @param c the character to be checked
- * @return true if the supplied character is the delimiter
- * character
- * @see DelimitedLineTokenizer#DelimitedLineTokenizer(char)
- */
- private boolean isDelimiterCharacter(char c) {
- return c == this.delimiter;
- }
-
- /**
- * Is the supplied character a quote character?
- *
- * @param c the character to be checked
- * @return true if the supplied character is an quote
- * character
- * @see #setQuoteCharacter(char)
- */
- protected boolean isQuoteCharacter(char c) {
- return c == quoteCharacter;
- }
-}
+/*
+ * Copyright 2006-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.batch.io.file.support.transform;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.batch.io.exception.BatchConfigurationException;
+import org.springframework.util.StringUtils;
+
+/**
+ *
+ * @author Rob Harrop
+ * @author Dave Syer
+ *
+ */
+public class DelimitedLineTokenizer extends AbstractLineTokenizer {
+ /**
+ * Convenient constant for the common case of a tab delimiter.
+ */
+ public static final char DELIMITER_TAB = '\t';
+
+ /**
+ * Convenient constant for the common case of a comma delimiter.
+ */
+ public static final char DELIMITER_COMMA = ',';
+
+ /**
+ * Convenient constant for the common case of a " character used to escape
+ * delimiters or line endings.
+ */
+ public static final char DEFAULT_QUOTE_CHARACTER = '"';
+
+ // the delimiter character used when reading input.
+ private char delimiter;
+
+ private char quoteCharacter = DEFAULT_QUOTE_CHARACTER;
+
+ /**
+ * Create a new instance of the {@link DelimitedLineTokenizer} class for the
+ * common case where the delimiter is a {@link #DELIMITER_COMMA comma}.
+ *
+ * @see #DelimitedLineTokenizer(char)
+ * @see #DELIMITER_COMMA
+ */
+ public DelimitedLineTokenizer() {
+ this(DELIMITER_COMMA);
+ }
+
+ /**
+ * Create a new instance of the {@link DelimitedLineTokenizer} class.
+ *
+ * @param delimiter the desired delimiter
+ */
+ public DelimitedLineTokenizer(char delimiter) {
+ if (delimiter == DEFAULT_QUOTE_CHARACTER) {
+ throw new BatchConfigurationException("'" + DEFAULT_QUOTE_CHARACTER
+ + "' is not allowed as delimiter for tokenizers.");
+ }
+
+ this.delimiter = delimiter;
+ }
+
+ /**
+ * Setter for the delimiter character.
+ * @param delimiter
+ */
+ public void setDelimiter(char delimiter) {
+ this.delimiter = delimiter;
+ }
+
+ /**
+ * Public setter for the quoteCharacter. The quote character can be used to
+ * extend a field across line endings or to enclose a String which contains
+ * the delimiter. Inside a quoted token the quote character can be used to
+ * escape itself, thus "a""b""c" is tokenized to a"b"c.
+ *
+ * @param quoteCharacter the quoteCharacter to set
+ *
+ * @see #DEFAULT_QUOTE_CHARACTER
+ */
+ public void setQuoteCharacter(char quoteCharacter) {
+ this.quoteCharacter = quoteCharacter;
+ }
+
+ /**
+ * Yields the tokens resulting from the splitting of the supplied
+ * line.
+ *
+ * @param line the line to be tokenized
+ *
+ * @return the resulting tokens
+ */
+ protected List doTokenize(String line) {
+
+ List tokens = new ArrayList();
+
+ //line is never null in current implementation
+ //line is checked in parent: AbstractLineTokenizer.tokenize()
+ char[] chars = line.toCharArray();
+ boolean inQuoted = false;
+ char lastChar = 0;
+ int lastCut = 0;
+ int length = chars.length;
+
+ for (int i = 0; i < length; i++) {
+
+ char currentChar = chars[i];
+ boolean isEnd = (i == (length - 1));
+
+ if ((isDelimiterCharacter(currentChar) && !inQuoted) || isEnd) {
+ int endPosition = (isEnd ? (length - lastCut) : (i - lastCut));
+
+ if (isEnd && isDelimiterCharacter(currentChar)) {
+ endPosition--;
+ }
+
+ String value = null;
+
+ if (isQuoteCharacter(lastChar) || isQuoteCharacter(currentChar)) {
+ value = new String(chars, lastCut + 1, endPosition - 2);
+ value = StringUtils.replace(value, "" + quoteCharacter + quoteCharacter, "" + quoteCharacter);
+ }
+ else {
+ value = new String(chars, lastCut, endPosition);
+ }
+
+ tokens.add(value);
+
+ if (isEnd && (isDelimiterCharacter(currentChar))) {
+ tokens.add("");
+ }
+
+ lastCut = i + 1;
+ }
+ else if (isQuoteCharacter(currentChar)) {
+ inQuoted = !inQuoted;
+ }
+
+ lastChar = currentChar;
+ }
+
+ return tokens;
+ }
+
+ /**
+ * Is the supplied character the delimiter character?
+ *
+ * @param c the character to be checked
+ * @return true if the supplied character is the delimiter
+ * character
+ * @see DelimitedLineTokenizer#DelimitedLineTokenizer(char)
+ */
+ private boolean isDelimiterCharacter(char c) {
+ return c == this.delimiter;
+ }
+
+ /**
+ * Is the supplied character a quote character?
+ *
+ * @param c the character to be checked
+ * @return true if the supplied character is an quote
+ * character
+ * @see #setQuoteCharacter(char)
+ */
+ protected boolean isQuoteCharacter(char c) {
+ return c == quoteCharacter;
+ }
+}
diff --git a/infrastructure/src/main/java/org/springframework/batch/io/support/AbstractDrivingQueryInputSource.java b/infrastructure/src/main/java/org/springframework/batch/io/support/AbstractDrivingQueryInputSource.java
index 338001693..7c64ebb37 100644
--- a/infrastructure/src/main/java/org/springframework/batch/io/support/AbstractDrivingQueryInputSource.java
+++ b/infrastructure/src/main/java/org/springframework/batch/io/support/AbstractDrivingQueryInputSource.java
@@ -159,7 +159,7 @@ public abstract class AbstractDrivingQueryInputSource implements InputSource, Re
keys = restoreKeys(data);
- if(keys != null & keys.size() > 0){
+ if(keys != null && keys.size() > 0){
keysIterator = keys.listIterator();
initialized = true;
}
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/file/support/mapping/TestObject.java b/infrastructure/src/test/java/org/springframework/batch/io/file/support/mapping/TestObject.java
index a6bf6f637..83c54f8a5 100644
--- a/infrastructure/src/test/java/org/springframework/batch/io/file/support/mapping/TestObject.java
+++ b/infrastructure/src/test/java/org/springframework/batch/io/file/support/mapping/TestObject.java
@@ -1,135 +1,135 @@
-/*
- * Copyright 2006-2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.io.file.support.mapping;
-
-import java.math.BigDecimal;
-import java.util.Date;
-
-public class TestObject {
- String varString;
-
- boolean varBoolean;
-
- char varChar;
-
- byte varByte;
-
- short varShort;
-
- int varInt;
-
- long varLong;
-
- float varFloat;
-
- double varDouble;
-
- BigDecimal varBigDecimal;
-
- Date varDate;
-
- public Date getVarDate() {
- return varDate;
- }
-
- public void setVarDate(Date varDate) {
- this.varDate = varDate;
- }
-
- public TestObject() {
- }
-
- public BigDecimal getVarBigDecimal() {
- return varBigDecimal;
- }
-
- public void setVarBigDecimal(BigDecimal varBigDecimal) {
- this.varBigDecimal = varBigDecimal;
- }
-
- public boolean isVarBoolean() {
- return varBoolean;
- }
-
- public void setVarBoolean(boolean varBoolean) {
- this.varBoolean = varBoolean;
- }
-
- public byte getVarByte() {
- return varByte;
- }
-
- public void setVarByte(byte varByte) {
- this.varByte = varByte;
- }
-
- public char getVarChar() {
- return varChar;
- }
-
- public void setVarChar(char varChar) {
- this.varChar = varChar;
- }
-
- public double getVarDouble() {
- return varDouble;
- }
-
- public void setVarDouble(double varDouble) {
- this.varDouble = varDouble;
- }
-
- public float getVarFloat() {
- return varFloat;
- }
-
- public void setVarFloat(float varFloat) {
- this.varFloat = varFloat;
- }
-
- public long getVarLong() {
- return varLong;
- }
-
- public void setVarLong(long varLong) {
- this.varLong = varLong;
- }
-
- public short getVarShort() {
- return varShort;
- }
-
- public void setVarShort(short varShort) {
- this.varShort = varShort;
- }
-
- public String getVarString() {
- return varString;
- }
-
- public void setVarString(String varString) {
- this.varString = varString;
- }
-
- public int getVarInt() {
- return varInt;
- }
-
- public void setVarInt(int varInt) {
- this.varInt = varInt;
- }
-}
+/*
+ * Copyright 2006-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.batch.io.file.support.mapping;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+public class TestObject {
+ String varString;
+
+ boolean varBoolean;
+
+ char varChar;
+
+ byte varByte;
+
+ short varShort;
+
+ int varInt;
+
+ long varLong;
+
+ float varFloat;
+
+ double varDouble;
+
+ BigDecimal varBigDecimal;
+
+ Date varDate;
+
+ public Date getVarDate() {
+ return (Date)varDate.clone();
+ }
+
+ public void setVarDate(Date varDate) {
+ this.varDate = varDate == null ? null : (Date)varDate.clone();
+ }
+
+ public TestObject() {
+ }
+
+ public BigDecimal getVarBigDecimal() {
+ return varBigDecimal;
+ }
+
+ public void setVarBigDecimal(BigDecimal varBigDecimal) {
+ this.varBigDecimal = varBigDecimal;
+ }
+
+ public boolean isVarBoolean() {
+ return varBoolean;
+ }
+
+ public void setVarBoolean(boolean varBoolean) {
+ this.varBoolean = varBoolean;
+ }
+
+ public byte getVarByte() {
+ return varByte;
+ }
+
+ public void setVarByte(byte varByte) {
+ this.varByte = varByte;
+ }
+
+ public char getVarChar() {
+ return varChar;
+ }
+
+ public void setVarChar(char varChar) {
+ this.varChar = varChar;
+ }
+
+ public double getVarDouble() {
+ return varDouble;
+ }
+
+ public void setVarDouble(double varDouble) {
+ this.varDouble = varDouble;
+ }
+
+ public float getVarFloat() {
+ return varFloat;
+ }
+
+ public void setVarFloat(float varFloat) {
+ this.varFloat = varFloat;
+ }
+
+ public long getVarLong() {
+ return varLong;
+ }
+
+ public void setVarLong(long varLong) {
+ this.varLong = varLong;
+ }
+
+ public short getVarShort() {
+ return varShort;
+ }
+
+ public void setVarShort(short varShort) {
+ this.varShort = varShort;
+ }
+
+ public String getVarString() {
+ return varString;
+ }
+
+ public void setVarString(String varString) {
+ this.varString = varString;
+ }
+
+ public int getVarInt() {
+ return varInt;
+ }
+
+ public void setVarInt(int varInt) {
+ this.varInt = varInt;
+ }
+}
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializerTests.java b/infrastructure/src/test/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializerTests.java
index a8b60aea5..06e5cc5fc 100644
--- a/infrastructure/src/test/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializerTests.java
+++ b/infrastructure/src/test/java/org/springframework/batch/io/oxm/MarshallingObjectToXmlSerializerTests.java
@@ -24,12 +24,12 @@ import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.Result;
+import junit.framework.TestCase;
+
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.XmlMappingException;
-import junit.framework.TestCase;
-
/**
*
*
@@ -67,7 +67,7 @@ public class MarshallingObjectToXmlSerializerTests extends TestCase {
}
}
- private class MockMarshaller implements Marshaller{
+ private static class MockMarshaller implements Marshaller{
private Object marshalledObject;
private boolean throwException = false;
@@ -93,7 +93,7 @@ public class MarshallingObjectToXmlSerializerTests extends TestCase {
}
}
- private class StubXmlEventWriter implements XMLEventWriter{
+ private static class StubXmlEventWriter implements XMLEventWriter{
public void add(XMLEvent arg0) throws XMLStreamException { }
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/sample/domain/Order.java b/infrastructure/src/test/java/org/springframework/batch/io/sample/domain/Order.java
index 1d010cacd..755a8c20c 100644
--- a/infrastructure/src/test/java/org/springframework/batch/io/sample/domain/Order.java
+++ b/infrastructure/src/test/java/org/springframework/batch/io/sample/domain/Order.java
@@ -1,83 +1,83 @@
-/*
- * Copyright 2006-2007 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.batch.io.sample.domain;
-
-import java.util.Date;
-import java.util.List;
-
-import org.apache.commons.lang.builder.EqualsBuilder;
-import org.apache.commons.lang.builder.HashCodeBuilder;
-import org.apache.commons.lang.builder.ToStringBuilder;
-
-/**
- * An XML order.
- *
- * This is a complex type.
- */
-public class Order {
- private Customer customer;
-
- private Date date;
-
- private List lineItems;
-
- private Shipper shipper;
-
- public Customer getCustomer() {
- return customer;
- }
-
- public void setCustomer(Customer customer) {
- this.customer = customer;
- }
-
- public Date getDate() {
- return date;
- }
-
- public void setDate(Date date) {
- this.date = date;
- }
-
- public List getLineItems() {
- return lineItems;
- }
-
- public void setLineItems(List lineItems) {
- this.lineItems = lineItems;
- }
-
- public Shipper getShipper() {
- return shipper;
- }
-
- public void setShipper(Shipper shipper) {
- this.shipper = shipper;
- }
-
- public boolean equals(Object obj) {
- return EqualsBuilder.reflectionEquals(obj, this);
- }
-
- public int hashCode() {
- return HashCodeBuilder.reflectionHashCode(this);
- }
-
- public String toString() {
- return ToStringBuilder.reflectionToString(this);
- }
-}
+/*
+ * Copyright 2006-2007 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.batch.io.sample.domain;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+/**
+ * An XML order.
+ *
+ * This is a complex type.
+ */
+public class Order {
+ private Customer customer;
+
+ private Date date;
+
+ private List lineItems;
+
+ private Shipper shipper;
+
+ public Customer getCustomer() {
+ return customer;
+ }
+
+ public void setCustomer(Customer customer) {
+ this.customer = customer;
+ }
+
+ public Date getDate() {
+ return (Date)date.clone();
+ }
+
+ public void setDate(Date date) {
+ this.date = date == null ? null : (Date)date.clone();
+ }
+
+ public List getLineItems() {
+ return lineItems;
+ }
+
+ public void setLineItems(List lineItems) {
+ this.lineItems = lineItems;
+ }
+
+ public Shipper getShipper() {
+ return shipper;
+ }
+
+ public void setShipper(Shipper shipper) {
+ this.shipper = shipper;
+ }
+
+ public boolean equals(Object obj) {
+ return EqualsBuilder.reflectionEquals(obj, this);
+ }
+
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this);
+ }
+}
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/sql/CompositeKeySqlDrivingQueryInputSourceIntegrationTests.java b/infrastructure/src/test/java/org/springframework/batch/io/sql/CompositeKeySqlDrivingQueryInputSourceIntegrationTests.java
index 5a1c88566..99f56afce 100644
--- a/infrastructure/src/test/java/org/springframework/batch/io/sql/CompositeKeySqlDrivingQueryInputSourceIntegrationTests.java
+++ b/infrastructure/src/test/java/org/springframework/batch/io/sql/CompositeKeySqlDrivingQueryInputSourceIntegrationTests.java
@@ -22,7 +22,6 @@ import java.util.List;
import java.util.Properties;
import org.springframework.batch.io.InputSource;
-import org.springframework.batch.io.sql.CompositeKeySqlDrivingQueryInputSource;
import org.springframework.batch.restart.GenericRestartData;
import org.springframework.batch.restart.RestartData;
import org.springframework.jdbc.core.RowMapper;
@@ -48,7 +47,7 @@ public class CompositeKeySqlDrivingQueryInputSourceIntegrationTests extends
return fooInputSource;
}
- private class FooRestartDataConverter implements CompositeKeyRestartDataConverter{
+ private static class FooRestartDataConverter implements CompositeKeyRestartDataConverter{
private static final String ID_RESTART_KEY = "FooRestartDataConverter.id";
private static final String VALUE_RESTART_KEY = "FooRestartDataConverter.value";
@@ -70,7 +69,7 @@ public class CompositeKeySqlDrivingQueryInputSourceIntegrationTests extends
}
}
- private class FooCompositeKeyMapper implements RowMapper{
+ private static class FooCompositeKeyMapper implements RowMapper{
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
List key = new ArrayList();
key.add(new Long(rs.getLong(1)));
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/sql/SingleKeySqlDrivingQueryInputSourceIntegrationTests.java b/infrastructure/src/test/java/org/springframework/batch/io/sql/SingleKeySqlDrivingQueryInputSourceIntegrationTests.java
index f009e7bfb..647d9177a 100644
--- a/infrastructure/src/test/java/org/springframework/batch/io/sql/SingleKeySqlDrivingQueryInputSourceIntegrationTests.java
+++ b/infrastructure/src/test/java/org/springframework/batch/io/sql/SingleKeySqlDrivingQueryInputSourceIntegrationTests.java
@@ -13,7 +13,8 @@ public class SingleKeySqlDrivingQueryInputSourceIntegrationTests extends Abstrac
*/
protected InputSource createInputSource() throws Exception {
- SingleKeySqlDrivingQueryInputSource inputSource = new SingleKeySqlDrivingQueryInputSource(getJdbcTemplate(), "SELECT ID from T_FOOS order by ID");
+ SingleKeySqlDrivingQueryInputSource inputSource = new SingleKeySqlDrivingQueryInputSource(getJdbcTemplate(),
+ "SELECT ID from T_FOOS order by ID");
inputSource.setRestartQuery("SELECT ID from T_FOOS where ID > ? order by ID");
return new FooInputSource(inputSource, getJdbcTemplate());
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/stax/AbstractEventReaderWrapperTests.java b/infrastructure/src/test/java/org/springframework/batch/io/stax/AbstractEventReaderWrapperTests.java
index fcd0bf9db..812642e90 100644
--- a/infrastructure/src/test/java/org/springframework/batch/io/stax/AbstractEventReaderWrapperTests.java
+++ b/infrastructure/src/test/java/org/springframework/batch/io/stax/AbstractEventReaderWrapperTests.java
@@ -19,12 +19,12 @@ import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.XMLEvent;
+import junit.framework.TestCase;
+
import org.easymock.MockControl;
import com.bea.xml.stream.events.StartDocumentEvent;
-import junit.framework.TestCase;
-
/**
* @author Lucas Ward
*
@@ -126,7 +126,7 @@ public class AbstractEventReaderWrapperTests extends TestCase {
mockEventReaderControl.verify();
}
- private class StubEventReader extends AbstractEventReaderWrapper{
+ private static class StubEventReader extends AbstractEventReaderWrapper{
public StubEventReader(XMLEventReader wrappedEventReader) {
super(wrappedEventReader);
}
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/stax/AbstractEventWriterWrapperTests.java b/infrastructure/src/test/java/org/springframework/batch/io/stax/AbstractEventWriterWrapperTests.java
index 565337807..d7d7165e5 100644
--- a/infrastructure/src/test/java/org/springframework/batch/io/stax/AbstractEventWriterWrapperTests.java
+++ b/infrastructure/src/test/java/org/springframework/batch/io/stax/AbstractEventWriterWrapperTests.java
@@ -126,7 +126,7 @@ public class AbstractEventWriterWrapperTests extends TestCase {
mockEventWriterControl.verify();
}
- private class StubEventWriter extends AbstractEventWriterWrapper{
+ private static class StubEventWriter extends AbstractEventWriterWrapper{
public StubEventWriter(XMLEventWriter wrappedEventWriter) {
super(wrappedEventWriter);
}
diff --git a/infrastructure/src/test/java/org/springframework/batch/io/stax/StaxEventReaderInputSourceTests.java b/infrastructure/src/test/java/org/springframework/batch/io/stax/StaxEventReaderInputSourceTests.java
index f0899fa5f..840b764dd 100644
--- a/infrastructure/src/test/java/org/springframework/batch/io/stax/StaxEventReaderInputSourceTests.java
+++ b/infrastructure/src/test/java/org/springframework/batch/io/stax/StaxEventReaderInputSourceTests.java
@@ -347,7 +347,7 @@ public class StaxEventReaderInputSourceTests extends TestCase {
}
- private class MockStaxEventReaderInputSource extends StaxEventReaderInputSource {
+ private static class MockStaxEventReaderInputSource extends StaxEventReaderInputSource {
private boolean openCalled = false;
diff --git a/infrastructure/src/test/java/org/springframework/batch/item/provider/InputSourceItemProviderTests.java b/infrastructure/src/test/java/org/springframework/batch/item/provider/InputSourceItemProviderTests.java
index 099819f58..c5b084198 100644
--- a/infrastructure/src/test/java/org/springframework/batch/item/provider/InputSourceItemProviderTests.java
+++ b/infrastructure/src/test/java/org/springframework/batch/item/provider/InputSourceItemProviderTests.java
@@ -22,7 +22,6 @@ import junit.framework.TestCase;
import org.springframework.batch.io.InputSource;
import org.springframework.batch.io.Skippable;
-import org.springframework.batch.item.provider.InputSourceItemProvider;
import org.springframework.batch.restart.GenericRestartData;
import org.springframework.batch.restart.RestartData;
import org.springframework.batch.restart.Restartable;
@@ -99,7 +98,7 @@ public class InputSourceItemProviderTests extends TestCase {
assertEquals("after skip", itemProvider.next());
}
- private class MockInputSource implements InputSource, StatisticsProvider, Restartable, Skippable {
+ private static class MockInputSource implements InputSource, StatisticsProvider, Restartable, Skippable {
private Object value;
diff --git a/infrastructure/src/test/java/org/springframework/batch/item/provider/ValidatingItemProviderTests.java b/infrastructure/src/test/java/org/springframework/batch/item/provider/ValidatingItemProviderTests.java
index 13ebc0769..a57dbeada 100644
--- a/infrastructure/src/test/java/org/springframework/batch/item/provider/ValidatingItemProviderTests.java
+++ b/infrastructure/src/test/java/org/springframework/batch/item/provider/ValidatingItemProviderTests.java
@@ -15,13 +15,13 @@
*/
package org.springframework.batch.item.provider;
+import junit.framework.TestCase;
+
import org.easymock.MockControl;
import org.springframework.batch.io.InputSource;
import org.springframework.batch.io.exception.ValidationException;
import org.springframework.batch.item.validator.Validator;
-import junit.framework.TestCase;
-
/**
* @author Lucas Ward
*
@@ -99,7 +99,7 @@ public class ValidatingItemProviderTests extends TestCase {
validatorControl.verify();
}
- private class MockInputSource implements InputSource{
+ private static class MockInputSource implements InputSource{
Object value;