diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/PrefixMatchingCompositeLineMapper.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/PrefixMatchingCompositeLineMapper.java
new file mode 100644
index 000000000..b40acc67c
--- /dev/null
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/mapping/PrefixMatchingCompositeLineMapper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.item.file.mapping;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.springframework.batch.item.file.transform.LineTokenizer;
+import org.springframework.batch.item.file.transform.PrefixMatchingCompositeLineTokenizer;
+import org.springframework.util.Assert;
+
+/**
+ *
+ * A {@link LineMapper} implementation that stores a mapping of String prefixes
+ * to delegate {@link LineTokenizer}s as well as a mapping of String prefixes
+ * to delegate {@link LineTokenizer}s. Each line received will be tokenized and
+ * then mapped to a field set.
+ *
+ *
+ * Both the tokenizing and the mapping work in a similar way. The line will be
+ * checked for its prefix. If the prefix matches a key in the map of delegates,
+ * then the corresponding delegate will be used. Otherwise, the default
+ * tokenizer or mapper will be used. The default can be configured in the
+ * delegate map by setting its corresponding prefix to the empty string.
+ *
+ * @author Dan Garrette
+ */
+public class PrefixMatchingCompositeLineMapper extends PrefixMatchingCompositeLineTokenizer implements LineMapper {
+
+ private Map> fieldSetMappers = null;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.item.file.mapping.LineMapper#mapLine(java.lang.String,
+ * int)
+ */
+ public T mapLine(String line, int lineNumber) throws Exception {
+ return this.matchPrefix(line, this.fieldSetMappers).mapFieldSet(this.tokenize(line));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ */
+ public void afterPropertiesSet() throws Exception {
+ super.afterPropertiesSet();
+ Assert.isTrue(this.fieldSetMappers != null && this.fieldSetMappers.size() > 0,
+ "The 'fieldSetMappers' property must be non-empty");
+ }
+
+ public void setFieldSetMappers(Map> fieldSetMappers) {
+ this.fieldSetMappers = new LinkedHashMap>(fieldSetMappers);
+ }
+}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/PrefixMatchingCompositeLineTokenizer.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/PrefixMatchingCompositeLineTokenizer.java
index 93179cb1c..21fed00b8 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/PrefixMatchingCompositeLineTokenizer.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/file/transform/PrefixMatchingCompositeLineTokenizer.java
@@ -16,10 +16,11 @@
package org.springframework.batch.item.file.transform;
-import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
/**
* A {@link LineTokenizer} implementation that stores a mapping of String
@@ -30,46 +31,69 @@ import java.util.Map;
* {@link LineTokenizer} can be configured in the delegate map by setting its
* corresponding prefix to the empty string.
*
+ * @author Dan Garrette
*/
-public class PrefixMatchingCompositeLineTokenizer implements LineTokenizer {
+public class PrefixMatchingCompositeLineTokenizer implements LineTokenizer, InitializingBean {
- private Map tokenizers = new HashMap();
+ private Map tokenizers = null;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.item.file.transform.LineTokenizer#tokenize(java.lang.String)
+ */
+ public FieldSet tokenize(String line) {
+ return this.matchPrefix(line, this.tokenizers).tokenize(line);
+ }
+
+ /**
+ * @param line
+ * @return the delegate whose prefix matches the given line
+ */
+ protected S matchPrefix(String line, Map delegates) {
+ S delegate = null;
+ S defaultDelegate = null;
+
+ if (line != null) {
+ for (String key : delegates.keySet()) {
+ if ("".equals(key)) {
+ defaultDelegate = delegates.get(key);
+ // don't break here or the delegate may not be found
+ }
+ else if (line.startsWith(key)) {
+ delegate = delegates.get(key);
+ break;
+ }
+ }
+
+ if (delegate == null) {
+ delegate = defaultDelegate;
+ }
+ }
+ else if (delegates.containsKey(null)) {
+ delegate = delegates.get(null);
+ }
+ else {
+ throw new IllegalStateException("Could not handle a null line");
+ }
+
+ if (delegate == null) {
+ throw new IllegalStateException("Could not find a matching prefix for line=[" + line + "]");
+ }
+ return delegate;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
+ */
+ public void afterPropertiesSet() throws Exception {
+ Assert.isTrue(this.tokenizers != null && this.tokenizers.size() > 0,
+ "The 'tokenizers' property must be non-empty");
+ }
public void setTokenizers(Map tokenizers) {
this.tokenizers = new LinkedHashMap(tokenizers);
}
-
- public FieldSet tokenize(String line) {
-
- if (line == null) {
- return new DefaultFieldSet(new String[0]);
- }
-
- LineTokenizer tokenizer = null;
- LineTokenizer defaultTokenizer = null;
-
- for (String key : tokenizers.keySet()) {
-
- if ("".equals(key)) {
- defaultTokenizer = (LineTokenizer) tokenizers.get(key);
- // don't break here or the tokenizer may not be found
- continue;
- }
- if (line.startsWith(key)) {
- tokenizer = (LineTokenizer) tokenizers.get(key);
- break;
- }
- }
-
- if (tokenizer == null) {
- tokenizer = defaultTokenizer;
- }
-
- if (tokenizer == null) {
- throw new IllegalStateException("Could not match record to tokenizer for line=[" + line + "]");
- }
-
- return tokenizer.tokenize(line);
- }
-
}
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/PrefixMatchingCompositeLineMapperTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/PrefixMatchingCompositeLineMapperTests.java
new file mode 100644
index 000000000..e6882ab8d
--- /dev/null
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/PrefixMatchingCompositeLineMapperTests.java
@@ -0,0 +1,137 @@
+/*
+ * 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.item.file.mapping;
+
+import static junit.framework.Assert.assertEquals;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Test;
+import org.springframework.batch.item.file.transform.DefaultFieldSet;
+import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
+import org.springframework.batch.item.file.transform.FieldSet;
+import org.springframework.batch.item.file.transform.LineTokenizer;
+import org.springframework.batch.item.file.transform.Name;
+
+/**
+ * @author Dan Garrette
+ */
+public class PrefixMatchingCompositeLineMapperTests {
+
+ private PrefixMatchingCompositeLineMapper mapper = new PrefixMatchingCompositeLineMapper();
+
+ @Test(expected = IllegalArgumentException.class)
+ public void test_NoMappers() throws Exception {
+ mapper.setTokenizers(Collections.singletonMap("", (LineTokenizer) new DelimitedLineTokenizer()));
+ Map> fieldSetMappers = Collections.emptyMap();
+ mapper.setFieldSetMappers(fieldSetMappers);
+ mapper.afterPropertiesSet();
+ }
+
+ @Test
+ public void test_NullLine() throws Exception {
+ Map tokenizers = new HashMap();
+ tokenizers.put(null, new LineTokenizer() {
+ public FieldSet tokenize(String line) {
+ return new DefaultFieldSet(new String[] { "a", "b" });
+ }
+ });
+ tokenizers.put("bar", new LineTokenizer() {
+ public FieldSet tokenize(String line) {
+ return new DefaultFieldSet(new String[] { "c", "d" });
+ }
+ });
+ mapper.setTokenizers(tokenizers);
+
+ Map> fieldSetMappers = new HashMap>();
+ fieldSetMappers.put(null, new FieldSetMapper() {
+ public Name mapFieldSet(FieldSet fs) {
+ return new Name(fs.readString(0), fs.readString(1), 0);
+ }
+ });
+ fieldSetMappers.put("bar", new FieldSetMapper() {
+ public Name mapFieldSet(FieldSet fs) {
+ return new Name(fs.readString(1), fs.readString(0), 0);
+ }
+ });
+ mapper.setFieldSetMappers(fieldSetMappers);
+
+ Name name = mapper.mapLine(null, 1);
+ assertEquals(new Name("a", "b", 0), name);
+ }
+
+ @Test
+ public void test_KeyFound() throws Exception {
+ Map tokenizers = new HashMap();
+ tokenizers.put("foo", new LineTokenizer() {
+ public FieldSet tokenize(String line) {
+ return new DefaultFieldSet(new String[] { "a", "b" });
+ }
+ });
+ tokenizers.put("bar", new LineTokenizer() {
+ public FieldSet tokenize(String line) {
+ return new DefaultFieldSet(new String[] { "c", "d" });
+ }
+ });
+ mapper.setTokenizers(tokenizers);
+
+ Map> fieldSetMappers = new HashMap>();
+ fieldSetMappers.put("foo", new FieldSetMapper() {
+ public Name mapFieldSet(FieldSet fs) {
+ return new Name(fs.readString(0), fs.readString(1), 0);
+ }
+ });
+ fieldSetMappers.put("bar", new FieldSetMapper() {
+ public Name mapFieldSet(FieldSet fs) {
+ return new Name(fs.readString(1), fs.readString(0), 0);
+ }
+ });
+ mapper.setFieldSetMappers(fieldSetMappers);
+
+ Name name = mapper.mapLine("bar", 1);
+ assertEquals(new Name("d", "c", 0), name);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void test_MapperKeyNotFound() throws Exception {
+ Map tokenizers = new HashMap();
+ tokenizers.put("foo", new LineTokenizer() {
+ public FieldSet tokenize(String line) {
+ return new DefaultFieldSet(new String[] { "a", "b" });
+ }
+ });
+ tokenizers.put("bar", new LineTokenizer() {
+ public FieldSet tokenize(String line) {
+ return new DefaultFieldSet(new String[] { "c", "d" });
+ }
+ });
+ mapper.setTokenizers(tokenizers);
+
+ Map> fieldSetMappers = new HashMap>();
+ fieldSetMappers.put("foo", new FieldSetMapper() {
+ public Name mapFieldSet(FieldSet fs) {
+ return new Name(fs.readString(0), fs.readString(1), 0);
+ }
+ });
+ mapper.setFieldSetMappers(fieldSetMappers);
+
+ Name name = mapper.mapLine("bar", 1);
+ assertEquals(new Name("d", "c", 0), name);
+ }
+}
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/Name.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/Name.java
index 9ee3793a3..b440f893d 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/Name.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/Name.java
@@ -1,5 +1,7 @@
package org.springframework.batch.item.file.transform;
+import org.apache.commons.lang.builder.EqualsBuilder;
+
public class Name {
private String first;
private String last;
@@ -38,4 +40,8 @@ public class Name {
public void setBorn(int born) {
this.born = born;
}
+
+ public boolean equals(Object o) {
+ return EqualsBuilder.reflectionEquals(this, o);
+ }
}
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/PrefixMatchingCompositeLineTokenizerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/PrefixMatchingCompositeLineTokenizerTests.java
index 20ca3b068..bca65b7df 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/PrefixMatchingCompositeLineTokenizerTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/PrefixMatchingCompositeLineTokenizerTests.java
@@ -16,33 +16,52 @@
package org.springframework.batch.item.file.transform;
+import static junit.framework.Assert.assertEquals;
+
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
-import junit.framework.TestCase;
+import org.junit.Test;
+/**
+ * @author Dan Garrette
+ */
+public class PrefixMatchingCompositeLineTokenizerTests {
-public class PrefixMatchingCompositeLineTokenizerTests extends TestCase {
+ private PrefixMatchingCompositeLineTokenizer tokenizer = new PrefixMatchingCompositeLineTokenizer();
- PrefixMatchingCompositeLineTokenizer tokenizer = new PrefixMatchingCompositeLineTokenizer();
-
+ @Test(expected = IllegalArgumentException.class)
public void testNoTokenizers() throws Exception {
- try {
- tokenizer.tokenize("a line");
- fail("Expected IllegalStateException");
- } catch (IllegalStateException e) {
- // expected
- }
+ tokenizer.afterPropertiesSet();
+ tokenizer.tokenize("a line");
}
-
- public void testNullLine() throws Exception {
- tokenizer.setTokenizers(Collections.singletonMap("foo", (LineTokenizer) new DelimitedLineTokenizer()));
+
+ @Test(expected = IllegalStateException.class)
+ public void testNullLineNoKey() throws Exception {
+ tokenizer.setTokenizers(Collections.singletonMap("foo", (LineTokenizer) new DelimitedLineTokenizer()));
+ tokenizer.afterPropertiesSet();
FieldSet fields = tokenizer.tokenize(null);
assertEquals(0, fields.getFieldCount());
}
-
+
+ @Test
+ public void testNullLineWithKey() throws Exception {
+ Map map = new HashMap();
+ map.put(null, new DelimitedLineTokenizer());
+ map.put("foo", new LineTokenizer() {
+ public FieldSet tokenize(String line) {
+ return null;
+ }
+ });
+ tokenizer.setTokenizers(map);
+ tokenizer.afterPropertiesSet();
+ FieldSet fields = tokenizer.tokenize(null);
+ assertEquals(0, fields.getFieldCount());
+ }
+
+ @Test
public void testEmptyKeyMatchesAnyLine() throws Exception {
Map map = new HashMap();
map.put("", new DelimitedLineTokenizer());
@@ -51,13 +70,15 @@ public class PrefixMatchingCompositeLineTokenizerTests extends TestCase {
return null;
}
});
- tokenizer.setTokenizers(map);
+ tokenizer.setTokenizers(map);
+ tokenizer.afterPropertiesSet();
FieldSet fields = tokenizer.tokenize("abc");
assertEquals(1, fields.getFieldCount());
}
+ @Test
public void testEmptyKeyDoesNotMatchWhenAlternativeAvailable() throws Exception {
-
+
Map map = new LinkedHashMap();
map.put("", new LineTokenizer() {
public FieldSet tokenize(String line) {
@@ -65,27 +86,27 @@ public class PrefixMatchingCompositeLineTokenizerTests extends TestCase {
}
});
map.put("foo", new DelimitedLineTokenizer());
- tokenizer.setTokenizers(map);
+ tokenizer.setTokenizers(map);
+ tokenizer.afterPropertiesSet();
FieldSet fields = tokenizer.tokenize("foo,bar");
assertEquals("bar", fields.readString(1));
}
+ @Test(expected = IllegalStateException.class)
public void testNoMatch() throws Exception {
- tokenizer.setTokenizers(Collections.singletonMap("foo", (LineTokenizer) new DelimitedLineTokenizer()));
- try {
- tokenizer.tokenize("nomatch");
- fail("Expected IllegalStateException");
- } catch (IllegalStateException e) {
- // expected
- }
+ tokenizer.setTokenizers(Collections.singletonMap("foo", (LineTokenizer) new DelimitedLineTokenizer()));
+ tokenizer.afterPropertiesSet();
+ tokenizer.tokenize("nomatch");
}
-
+
+ @Test
public void testMatchWithPrefix() throws Exception {
tokenizer.setTokenizers(Collections.singletonMap("foo", (LineTokenizer) new LineTokenizer() {
public FieldSet tokenize(String line) {
- return new DefaultFieldSet(new String[] {line});
+ return new DefaultFieldSet(new String[] { line });
}
}));
+ tokenizer.afterPropertiesSet();
FieldSet fields = tokenizer.tokenize("foo bar");
assertEquals(1, fields.getFieldCount());
assertEquals("foo bar", fields.readString(0));
diff --git a/spring-batch-samples/src/main/resources/jobs/iosample/multiRecordType.xml b/spring-batch-samples/src/main/resources/jobs/iosample/multiRecordType.xml
index 2d1dd29f8..c06a2b019 100644
--- a/spring-batch-samples/src/main/resources/jobs/iosample/multiRecordType.xml
+++ b/spring-batch-samples/src/main/resources/jobs/iosample/multiRecordType.xml
@@ -21,58 +21,48 @@
-
-
-
-
-
-
+
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+ class="org.springframework.batch.sample.domain.trade.internal.TradeFieldSetMapper" />
+
-
+
-
+
@@ -82,8 +72,7 @@
-
@@ -93,8 +82,7 @@
-
diff --git a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/internal/PrefixMatchingCompositeFieldSetMapper.java b/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/internal/PrefixMatchingCompositeFieldSetMapper.java
deleted file mode 100644
index 0557a112f..000000000
--- a/spring-batch-samples/src/test/java/org/springframework/batch/sample/iosample/internal/PrefixMatchingCompositeFieldSetMapper.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.sample.iosample.internal;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.springframework.batch.item.file.mapping.FieldSetMapper;
-import org.springframework.batch.item.file.transform.FieldSet;
-
-/**
- * This class is used to delegate to a {@link FieldSetMapper} based on one field
- * in the {@link FieldSet}.
- *
- * @author Dan Garrette
- * @since 2.0
- */
-public class PrefixMatchingCompositeFieldSetMapper implements FieldSetMapper {
-
- private Map> mappers = new HashMap>();
-
- public T mapFieldSet(FieldSet fieldSet) {
- String prefix = fieldSet.readString("prefix");
- return this.mappers.get(prefix).mapFieldSet(fieldSet);
- }
-
- public void setMappers(Map> mappers) {
- this.mappers = mappers;
- }
-}