BATCH-1033: Moved matchPrefix method to PatternMatcher class. Also modified matchPrefix to ensure that the most specific prefix wins. Changed Prefix*LineMapper to have Prefix*LineTokenizer as a property.
This commit is contained in:
@@ -21,26 +21,32 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.batch.item.file.transform.LineTokenizer;
|
||||
import org.springframework.batch.item.file.transform.PrefixMatchingCompositeLineTokenizer;
|
||||
import org.springframework.batch.support.PatternMatcher;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* to delegate {@link FieldSetMapper}s. Each line received will be tokenized
|
||||
* and then mapped to a field set.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* then the corresponding delegate will be used. Prefixes are sorted starting
|
||||
* with the most specific, and the first match always succeeds.
|
||||
*
|
||||
* @see PrefixMatchingCompositeLineTokenizer
|
||||
* @see PatternMatcher#matchPrefix(String, Map)
|
||||
*
|
||||
* @author Dan Garrette
|
||||
* @since 2.0
|
||||
*/
|
||||
public class PrefixMatchingCompositeLineMapper<T> extends PrefixMatchingCompositeLineTokenizer implements LineMapper<T> {
|
||||
public class PrefixMatchingCompositeLineMapper<T> implements LineMapper<T>, InitializingBean {
|
||||
|
||||
private PrefixMatchingCompositeLineTokenizer tokenizer = new PrefixMatchingCompositeLineTokenizer();
|
||||
private Map<String, FieldSetMapper<T>> fieldSetMappers = null;
|
||||
|
||||
/*
|
||||
@@ -50,7 +56,7 @@ public class PrefixMatchingCompositeLineMapper<T> extends PrefixMatchingComposit
|
||||
* int)
|
||||
*/
|
||||
public T mapLine(String line, int lineNumber) throws Exception {
|
||||
return this.matchPrefix(line, this.fieldSetMappers).mapFieldSet(this.tokenize(line));
|
||||
return PatternMatcher.matchPrefix(line, this.fieldSetMappers).mapFieldSet(this.tokenizer.tokenize(line));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -59,11 +65,15 @@ public class PrefixMatchingCompositeLineMapper<T> extends PrefixMatchingComposit
|
||||
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
|
||||
*/
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
super.afterPropertiesSet();
|
||||
this.tokenizer.afterPropertiesSet();
|
||||
Assert.isTrue(this.fieldSetMappers != null && this.fieldSetMappers.size() > 0,
|
||||
"The 'fieldSetMappers' property must be non-empty");
|
||||
}
|
||||
|
||||
public void setTokenizers(Map<String, LineTokenizer> tokenizers) {
|
||||
this.tokenizer.setTokenizers(tokenizers);
|
||||
}
|
||||
|
||||
public void setFieldSetMappers(Map<String, FieldSetMapper<T>> fieldSetMappers) {
|
||||
this.fieldSetMappers = new LinkedHashMap<String, FieldSetMapper<T>>(fieldSetMappers);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.batch.item.file.transform;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.batch.support.PatternMatcher;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -26,11 +27,13 @@ import org.springframework.util.Assert;
|
||||
* A {@link LineTokenizer} implementation that stores a mapping of String
|
||||
* prefixes to delegate {@link LineTokenizer}s. Each line tokenizied will be
|
||||
* checked for its prefix. If the prefix matches a key in the map of delegates,
|
||||
* then the corresponding delegate {@link LineTokenizer} will be used.
|
||||
* Otherwise, the default {@link LineTokenizer} will be used. The default
|
||||
* {@link LineTokenizer} can be configured in the delegate map by setting its
|
||||
* corresponding prefix to the empty string.
|
||||
* then the corresponding delegate {@link LineTokenizer} will be used. Prefixes
|
||||
* are sorted starting with the most specific, and the first match always
|
||||
* succeeds.
|
||||
*
|
||||
* @see PatternMatcher#matchPrefix(String, Map)
|
||||
*
|
||||
* @author Ben Hale
|
||||
* @author Dan Garrette
|
||||
*/
|
||||
public class PrefixMatchingCompositeLineTokenizer implements LineTokenizer, InitializingBean {
|
||||
@@ -43,45 +46,7 @@ public class PrefixMatchingCompositeLineTokenizer implements LineTokenizer, Init
|
||||
* @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> S matchPrefix(String line, Map<String, S> delegates) {
|
||||
S delegate = null;
|
||||
S defaultDelegate = null;
|
||||
|
||||
if (line != null) {
|
||||
for (String key : delegates.keySet()) {
|
||||
if (key != null) {
|
||||
if ("".equals(key)) {
|
||||
defaultDelegate = delegates.get(key);
|
||||
}
|
||||
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;
|
||||
return PatternMatcher.matchPrefix(line, this.tokenizers).tokenize(line);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -15,23 +15,41 @@
|
||||
*/
|
||||
package org.springframework.batch.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
* @author Dan Garrette
|
||||
*/
|
||||
public class PatternMatcher {
|
||||
|
||||
/**
|
||||
* Used for reverse-sorting strings
|
||||
*/
|
||||
private static Comparator<String> STRING_REVERSE_COMPARATOR = new Comparator<String>() {
|
||||
public int compare(String o1, String o2) {
|
||||
return o2.compareTo(o1);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Lifted from AntPathMatcher in Spring Core. Tests whether or not a string
|
||||
* matches against a pattern. The pattern may contain two special
|
||||
* characters:<br>
|
||||
* '*' means zero or more characters<br>
|
||||
* '?' means one and only one character
|
||||
* @param pattern pattern to match against. Must not be <code>null</code>.
|
||||
* @param str string which must be matched against the pattern. Must not be
|
||||
* <code>null</code>.
|
||||
*
|
||||
* @param pattern
|
||||
* pattern to match against. Must not be <code>null</code>.
|
||||
* @param str
|
||||
* string which must be matched against the pattern. Must not be
|
||||
* <code>null</code>.
|
||||
* @return <code>true</code> if the string matches against the pattern, or
|
||||
* <code>false</code> otherwise.
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
public static boolean match(String pattern, String str) {
|
||||
char[] patArr = pattern.toCharArray();
|
||||
@@ -159,4 +177,48 @@ public class PatternMatcher {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This method takes a String line and a map from String prefixes to values
|
||||
* of any type. During processing, the method will identify the most
|
||||
* specific prefix in the map that matches the beginning of the line. Once
|
||||
* the correct is identified, its value is returned. Note that if the map
|
||||
* contains the empty string as a prefix, then it will serve as the
|
||||
* "default" case, matching every non-null line given.
|
||||
*
|
||||
* <p>
|
||||
* If no matching prefix is found, a {@link IllegalStateException} will be
|
||||
* thrown.
|
||||
*
|
||||
* <p>
|
||||
* Null keys are not allowed in the map.
|
||||
*
|
||||
* @param line
|
||||
* An input string
|
||||
* @param map
|
||||
* A map with String prefixes as keys.
|
||||
* @return the value whose prefix matches the given line
|
||||
*/
|
||||
public static <S> S matchPrefix(String line, Map<String, S> map) {
|
||||
S value = null;
|
||||
|
||||
if (line == null) {
|
||||
throw new IllegalStateException("Could not handle a null line");
|
||||
}
|
||||
|
||||
// Sort keys to start with the most specific
|
||||
List<String> sorted = new ArrayList<String>(map.keySet());
|
||||
Collections.sort(sorted, STRING_REVERSE_COMPARATOR);
|
||||
for (String key : sorted) {
|
||||
if (line.startsWith(key)) {
|
||||
value = map.get(key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
throw new IllegalStateException("Could not find a matching prefix for line=[" + line + "]");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user