OPEN - issue BATCH-1108: Add composite ItemWriter/Processor based on Classifier

First draft of classifier features added.  Also refactored MethodInvoker abstraction into infrastructure.
This commit is contained in:
dsyer
2009-03-03 11:55:03 +00:00
parent b8208dcd8a
commit 998f6f592e
25 changed files with 819 additions and 132 deletions

View File

@@ -28,9 +28,9 @@ 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 FieldSetMapper}s. Each line received will be tokenized
* and then mapped to a field set.
* to delegate {@link LineTokenizer}s as well as a mapping of String prefixes 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
@@ -39,7 +39,6 @@ import org.springframework.util.Assert;
* with the most specific, and the first match always succeeds.
*
* @see PrefixMatchingCompositeLineTokenizer
* @see PatternMatcher#match(String, Map)
*
* @author Dan Garrette
* @since 2.0
@@ -47,27 +46,29 @@ import org.springframework.util.Assert;
public class PrefixMatchingCompositeLineMapper<T> implements LineMapper<T>, InitializingBean {
private PrefixMatchingCompositeLineTokenizer tokenizer = new PrefixMatchingCompositeLineTokenizer();
private Map<String, FieldSetMapper<T>> fieldSetMappers = null;
private PatternMatcher<FieldSetMapper<T>> patternMatcher;
/*
* (non-Javadoc)
*
* @see org.springframework.batch.item.file.mapping.LineMapper#mapLine(java.lang.String,
* int)
* @see
* org.springframework.batch.item.file.mapping.LineMapper#mapLine(java.lang
* .String, int)
*/
public T mapLine(String line, int lineNumber) throws Exception {
return PatternMatcher.match(line, this.fieldSetMappers).mapFieldSet(this.tokenizer.tokenize(line));
return patternMatcher.match(line).mapFieldSet(this.tokenizer.tokenize(line));
}
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
* @see
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
this.tokenizer.afterPropertiesSet();
Assert.isTrue(this.fieldSetMappers != null && this.fieldSetMappers.size() > 0,
"The 'fieldSetMappers' property must be non-empty");
Assert.isTrue(this.patternMatcher != null, "The 'fieldSetMappers' property must be non-empty");
}
public void setTokenizers(Map<String, LineTokenizer> tokenizers) {
@@ -75,13 +76,15 @@ public class PrefixMatchingCompositeLineMapper<T> implements LineMapper<T>, Init
}
public void setFieldSetMappers(Map<String, FieldSetMapper<T>> fieldSetMappers) {
this.fieldSetMappers = new LinkedHashMap<String, FieldSetMapper<T>>();
Assert.isTrue(!fieldSetMappers.isEmpty(), "The 'fieldSetMappers' property must be non-empty");
LinkedHashMap<String, FieldSetMapper<T>> map = new LinkedHashMap<String, FieldSetMapper<T>>();
for (String key : fieldSetMappers.keySet()) {
FieldSetMapper<T> value = fieldSetMappers.get(key);
if (!key.endsWith("*")) {
key = key + "*";
}
this.fieldSetMappers.put(key, value);
map.put(key, value);
}
this.patternMatcher = new PatternMatcher<FieldSetMapper<T>>(map);
}
}

View File

@@ -31,43 +31,45 @@ import org.springframework.util.Assert;
* are sorted starting with the most specific, and the first match always
* succeeds.
*
* @see PatternMatcher#match(String, Map)
*
* @author Ben Hale
* @author Dan Garrette
* @author Dave Syer
*/
public class PrefixMatchingCompositeLineTokenizer implements LineTokenizer, InitializingBean {
private Map<String, LineTokenizer> tokenizers = null;
private PatternMatcher<LineTokenizer> tokenizers = null;
/*
* (non-Javadoc)
*
* @see org.springframework.batch.item.file.transform.LineTokenizer#tokenize(java.lang.String)
* @see
* org.springframework.batch.item.file.transform.LineTokenizer#tokenize(
* java.lang.String)
*/
public FieldSet tokenize(String line) {
return PatternMatcher.match(line, this.tokenizers).tokenize(line);
return tokenizers.match(line).tokenize(line);
}
/*
* (non-Javadoc)
*
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
* @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");
Assert.isTrue(this.tokenizers != null, "The 'tokenizers' property must be non-empty");
}
public void setTokenizers(Map<String, LineTokenizer> tokenizers) {
this.tokenizers = new LinkedHashMap<String, LineTokenizer>();
Assert.isTrue(!tokenizers.isEmpty(), "The 'tokenizers' property must be non-empty");
LinkedHashMap<String, LineTokenizer> map = new LinkedHashMap<String, LineTokenizer>();
for (String key : tokenizers.keySet()) {
LineTokenizer value = tokenizers.get(key);
if (!key.endsWith("*")) {
key = key + "*";
}
this.tokenizers.put(key, value);
map.put(key, value);
}
this.tokenizers = new PatternMatcher<LineTokenizer>(map);
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.support;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.support.Classifier;
import org.springframework.batch.support.ClassifierSupport;
/**
* Calls one of a collection of ItemWriters for each item, based on a router
* pattern implemented through the provided {@link Classifier}.
*
* The implementation is thread-safe if all delegates are thread-safe.
*
* @author Dave Syer
*/
public class ClassifierCompositeItemWriter<T> implements ItemWriter<T> {
private Classifier<T, ItemWriter<? super T>> classifier = new ClassifierSupport<T, ItemWriter<? super T>>(null);
/**
* @param classifier the classifier to set
*/
public void setClassifier(Classifier<T, ItemWriter<? super T>> classifier) {
this.classifier = classifier;
}
/**
* Delegates to injected {@link ItemWriter} instances according to their
* classification by the {@link Classifier}.
*/
public void write(List<? extends T> items) throws Exception {
Map<ItemWriter<? super T>, List<T>> map = new HashMap<ItemWriter<? super T>, List<T>>();
for (T item : items) {
ItemWriter<? super T> key = classifier.classify(item);
if (!map.containsKey(key)) {
map.put(key, new ArrayList<T>());
}
map.get(key).add(item);
}
for (ItemWriter<? super T> writer : map.keySet()) {
writer.write(map.get(writer));
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2002-2008 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.support;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* MethodResolver implementation that finds a <em>single</em> Method on the
* given Class that contains the specified annotation type.
*
* @author Mark Fisher
*/
public class AnnotationMethodResolver implements MethodResolver {
private Class<? extends Annotation> annotationType;
/**
* Create a MethodResolver for the specified Method-level annotation type
*/
public AnnotationMethodResolver(Class<? extends Annotation> annotationType) {
Assert.notNull(annotationType, "annotationType must not be null");
Assert.isTrue(ObjectUtils.containsElement(
annotationType.getAnnotation(Target.class).value(), ElementType.METHOD),
"Annotation [" + annotationType + "] is not a Method-level annotation.");
this.annotationType = annotationType;
}
/**
* Find a <em>single</em> Method on the Class of the given candidate object
* that contains the annotation type for which this resolver is searching.
*
* @param candidate the instance whose Class will be checked for the
* annotation
*
* @return a single matching Method instance or <code>null</code> if the
* candidate's Class contains no Methods with the specified annotation
*
* @throws IllegalArgumentException if more than one Method has the
* specified annotation
*/
public Method findMethod(Object candidate) {
Assert.notNull(candidate, "candidate object must not be null");
Class<?> targetClass = AopUtils.getTargetClass(candidate);
if (targetClass == null) {
targetClass = candidate.getClass();
}
return this.findMethod(targetClass);
}
/**
* Find a <em>single</em> Method on the given Class that contains the
* annotation type for which this resolver is searching.
*
* @param clazz the Class instance to check for the annotation
*
* @return a single matching Method instance or <code>null</code> if the
* Class contains no Methods with the specified annotation
*
* @throws IllegalArgumentException if more than one Method has the
* specified annotation
*/
public Method findMethod(final Class<?> clazz) {
Assert.notNull(clazz, "class must not be null");
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
if (annotation != null) {
Assert.isNull(annotatedMethod.get(), "found more than one method on target class ["
+ clazz + "] with the annotation type [" + annotationType + "]");
annotatedMethod.set(method);
}
}
});
return annotatedMethod.get();
}
}

View File

@@ -0,0 +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.support;
import java.util.Map;
/**
* A special purpose {@link Classifier} with easy configuration options for
* mapping from one arbitrary type of object to another via a pattern matcher.
*
* @author Dave Syer
*
*/
public class BackToBackPatternClassifier<C, T> implements Classifier<C, T> {
private Classifier<C, String> router;
private Classifier<String, T> matcher;
/**
* Default constructor, provided as a convenience for people using setter
* injection.
*/
public BackToBackPatternClassifier() {
}
/**
* Set up a classifier with input to the router and output from the matcher.
*
* @param router see {@link #setRouterDelegate(Object)}
* @param matcher see {@link #setMatcherMap(Map)}
*/
public BackToBackPatternClassifier(Classifier<C, String> router, Classifier<String, T> matcher) {
super();
this.router = router;
this.matcher = matcher;
}
/**
* A convenience method for creating a pattern matching classifier for the
* matcher component.
*
* @param map maps pattern keys with wildcards to output values
*/
public void setMatcherMap(Map<String, T> map) {
this.matcher = new PatternMatchingClassifier<T>(map);
}
/**
* A convenience method of creating a router classifier based on a plain old
* Java Object. The object provided must have precisely one public method
* that either has the @Classifier annotation or accepts a single argument
* and outputs a String. This will be used to create an input classifier for
* the router component. <br/>
*
* @param delegate the delegate object used to create a router classifier
*/
public void setRouterDelegate(Object delegate) {
this.router = new ClassifierAdapter<C,String>(delegate);
}
/**
* Classify the input and map to a String, then take that and put it into a
* pattern matcher to match to an output value.
*/
public T classify(C classifiable) {
return matcher.classify(router.classify(classifiable));
}
}

View File

@@ -26,8 +26,8 @@ package org.springframework.batch.support;
public interface Classifier<C, T> {
/**
* Classify the given object and return an object. The return type depends
* on the implementation.
* Classify the given object and return an object of a different type,
* possibly an enumerated type.
*
* @param classifiable the input object. Can be null.
* @return an object. Can be null, but implementations should declare if

View File

@@ -0,0 +1,99 @@
/*
* 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.support;
import org.springframework.util.Assert;
/**
* Wrapper for an object to adapt it to the {@link Classifier} interface.
*
* @author Dave Syer
*
*/
public class ClassifierAdapter<C, T> implements Classifier<C, T> {
private MethodInvoker invoker;
private Classifier<C, T> classifier;
/**
* Default constructor for use with setter injection.
*/
public ClassifierAdapter() {
super();
}
/**
* Create a new {@link Classifier} from the delegate provided. Use the
* constructor as an alternative to the {@link #setDelegate(Object)} method.
*
* @param delegate
*/
public ClassifierAdapter(Object delegate) {
setDelegate(delegate);
}
/**
* Create a new {@link Classifier} from the delegate provided. Use the
* constructor as an alternative to the {@link #setDelegate(Classifier)}
* method.
*
* @param delegate
*/
public ClassifierAdapter(Classifier<C, T> delegate) {
classifier = delegate;
}
public void setDelegate(Classifier<C, T> delegate) {
classifier = delegate;
invoker = null;
}
/**
* Search for the
* {@link org.springframework.batch.support.annotation.Classifier
* Classifier} annotation on a method in the supplied delegate and use that
* to create a {@link Classifier} from the parameter type to the return
* type. If the annotation is not found a unique non-void method with a
* single parameter will be used, if it exists. The signature of the method
* cannot be checked here, so might be a runtime exception when the method
* is invoked if the signature doesn't match the classifier types.
*
* @param delegate an object with an annotated method
*/
public void setDelegate(Object delegate) {
classifier = null;
invoker = MethodInvokerUtils.getMethodInvokerByAnnotation(
org.springframework.batch.support.annotation.Classifier.class, delegate);
if (invoker == null) {
invoker = MethodInvokerUtils.<C, T> getMethodInvokerForSingleArgument(delegate);
}
Assert.state(invoker != null, "No single argument public method with or without "
+ "@Classifier was found in delegate of type " + delegate.getClass());
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public T classify(C classifiable) {
if (classifier != null) {
return classifier.classify(classifiable);
}
return (T) invoker.invokeMethod(classifiable);
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2002-2008 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.support;
/**
* A strategy interface for invoking a method.
* Typically used by adapters.
*
* @author Mark Fisher
*/
public interface MethodInvoker {
Object invokeMethod(Object ... args);
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright 2002-2008 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.support;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
/**
* Utility methods for create MethodInvoker instances.
*
* @author Lucas Ward
* @since 2.0
*/
public class MethodInvokerUtils {
/**
* Create a {@link MethodInvoker} using the provided method name to search.
*
* @param object to be invoked
* @param methodName of the method to be invoked
* @param paramsRequired boolean indicating whether the parameters are
* required, if false, a no args version of the method will be searched for.
* @param paramTypes - parameter types of the method to search for.
* @return MethodInvoker if the method is found, null if it is not.
*/
public static MethodInvoker createMethodInvokerByName(Object object, String methodName, boolean paramsRequired,
Class<?>... paramTypes) {
Assert.notNull(object, "Object to invoke must not be null");
Method method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);
// if no method was found for the given parameters, and the parameters
// aren't required
if (method == null && !paramsRequired) {
// try with no params
method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, new Class[] {});
}
if (method == null) {
return null;
}
else {
return new SimpleMethodInvoker(object, method);
}
}
/**
* Create a {@link MethodInvoker} using the provided interface, and method
* name from that interface.
*
* @param cls the interface to search for the method named
* @param methodName of the method to be invoked
* @param object to be invoked
* @param paramTypes - parameter types of the method to search for.
* @return MethodInvoker if the method is found, null if it is not.
*/
public static MethodInvoker getMethodInvokerForInterface(Class<?> cls, String methodName, Object object,
Class<?>... paramTypes) {
if (cls.isAssignableFrom(object.getClass())) {
return MethodInvokerUtils.createMethodInvokerByName(object, methodName, true, paramTypes);
}
else {
return null;
}
}
/**
* Create {@link MethodInvoker} for the method with the provided annotation
* on the provided object. It should be noted that annotations that cannot
* be applied to methods (i.e. that aren't annotated with an element type of
* METHOD) will cause an exception to be thrown.
*
* @param annotationType to be searched for
* @param candidate to be invoked
* @return MethodInvoker for the provided annotation, null if none is found.
*/
public static MethodInvoker getMethodInvokerByAnnotation(final Class<? extends Annotation> annotationType,
final Object candidate) {
Assert.notNull(candidate, "class must not be null");
Assert.notNull(annotationType, "annotationType must not be null");
Assert.isTrue(ObjectUtils.containsElement(annotationType.getAnnotation(Target.class).value(),
ElementType.METHOD), "Annotation [" + annotationType + "] is not a Method-level annotation.");
final AtomicReference<Method> annotatedMethod = new AtomicReference<Method>();
ReflectionUtils.doWithMethods(candidate.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Annotation annotation = AnnotationUtils.findAnnotation(method, annotationType);
if (annotation != null) {
Assert.isNull(annotatedMethod.get(), "found more than one method on target class [" + candidate
+ "] with the annotation type [" + candidate + "]");
annotatedMethod.set(method);
}
}
});
Method method = annotatedMethod.get();
if (method == null) {
return null;
}
else {
return new SimpleMethodInvoker(candidate, annotatedMethod.get());
}
}
/**
* Create a {@link MethodInvoker} for the delegate from a single public
* method with the signature provided.
*
* @param candidate an object to search for an appropriate method
* @return a MethodInvoker that calls a method on the delegate
*/
public static <C, T> MethodInvoker getMethodInvokerForSingleArgument(Object candidate) {
final AtomicReference<Method> methodHolder = new AtomicReference<Method>();
ReflectionUtils.doWithMethods(candidate.getClass(), new ReflectionUtils.MethodCallback() {
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
if (method.getParameterTypes() == null || method.getParameterTypes().length != 1) {
return;
}
if (method.getReturnType().equals(Void.TYPE) || ReflectionUtils.isEqualsMethod(method)) {
return;
}
Assert.state(methodHolder.get() == null,
"More than one non-void public method detected with single argument.");
methodHolder.set(method);
}
});
Method method = methodHolder.get();
return new SimpleMethodInvoker(candidate, method);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2002-2008 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.support;
import java.lang.reflect.Method;
/**
* Strategy interface for detecting a single Method on a Class.
*
* @author Mark Fisher
*/
public interface MethodResolver {
/**
* Find a single Method on the provided Object that matches this resolver's
* criteria.
*
* @param candidate the candidate Object whose Class should be searched for
* a Method
*
* @return a single Method or <code>null</code> if no Method matching this
* resolver's criteria can be found.
*
* @throws IllegalArgumentException if more than one Method defined on the
* given candidate's Class matches this resolver's criteria
*/
Method findMethod(Object candidate) throws IllegalArgumentException;
/**
* Find a <em>single</em> Method on the given Class that matches this
* resolver's criteria.
*
* @param clazz the Class instance on which to search for a Method
*
* @return a single Method or <code>null</code> if no Method matching this
* resolver's criteria can be found.
*
* @throws IllegalArgumentException if more than one Method defined on the
* given Class matches this resolver's criteria
*/
Method findMethod(Class<?> clazz);
}

View File

@@ -18,6 +18,7 @@ package org.springframework.batch.support;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -27,7 +28,28 @@ import org.springframework.util.Assert;
* @author Dave Syer
* @author Dan Garrette
*/
public class PatternMatcher {
public class PatternMatcher<S> {
private Map<String, S> map = new HashMap<String, S>();
private List<String> sorted = new ArrayList<String>();
/**
* Initialize a new {@link PatternMatcher} with a map of patterns to values
* @param map a map from String patterns to values
*/
public PatternMatcher(Map<String, S> map) {
super();
this.map = map;
// Sort keys to start with the most specific
sorted = new ArrayList<String>(map.keySet());
Collections.sort(sorted, new Comparator<String>() {
public int compare(String o1, String o2) {
String s1 = o1; // .replace('?', '{');
String s2 = o2; // .replace('*', '}');
return s2.compareTo(s1);
}
});
}
/**
* Lifted from AntPathMatcher in Spring Core. Tests whether or not a string
@@ -174,8 +196,8 @@ public class PatternMatcher {
* type. During processing, the method will identify the most specific key
* in the map that matches the line. Once the correct is identified, its
* value is returned. Note that if the map contains the wildcard string "*"
* as a key, then it will serve as the "default" case, matching every
* line that does not match anything else.
* as a key, then it will serve as the "default" case, matching every line
* that does not match anything else.
*
* <p>
* If no matching prefix is found, a {@link IllegalStateException} will be
@@ -185,22 +207,13 @@ public class PatternMatcher {
* 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 match(String line, Map<String, S> map) {
S value = null;
Assert.notNull(line, "A non-null key must be provided.");
public S match(String line) {
S value = null;
Assert.notNull(line, "A non-null key must be provided to match against.");
// Sort keys to start with the most specific
List<String> sorted = new ArrayList<String>(map.keySet());
Collections.sort(sorted, new Comparator<String>() {
public int compare(String o1, String o2) {
String s1 = o1; // .replace('?', '{');
String s2 = o2; // .replace('*', '}');
return s2.compareTo(s1);
}
});
for (String key : sorted) {
if (PatternMatcher.match(key, line)) {
value = map.get(key);
@@ -212,6 +225,7 @@ public class PatternMatcher {
throw new IllegalStateException("Could not find a matching pattern for key=[" + line + "]");
}
return value;
}
}

View File

@@ -0,0 +1,75 @@
/*
* 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.support;
import java.util.HashMap;
import java.util.Map;
/**
* A {@link Classifier} that maps from String patterns with wildcards to a set
* of values of a given type. An input String is matched with the most specific
* pattern possible to the corresponding value in an input map. A default value
* should be specified with a pattern key of "*".
*
* @author Dave Syer
*
*/
public class PatternMatchingClassifier<T> implements Classifier<String, T> {
private PatternMatcher<T> values;
/**
* Default constructor. Use the setter or the other constructor to create a
* sensible classifier, otherwise all inputs will cause an exception.
*/
public PatternMatchingClassifier() {
this(new HashMap<String, T>());
}
/**
* Create a classifier from the provided map. The keys are patterns, using
* '?' as a single character and '*' as multi-character wildcard.
*
* @param values
*/
public PatternMatchingClassifier(Map<String, T> values) {
super();
this.values = new PatternMatcher<T>(values);
}
/**
* A map from pattern to value
* @param values the pattern map to set
*/
public void setPatternMap(Map<String, T> values) {
this.values = new PatternMatcher<T>(values);
}
/**
* Classify the input by matching it against the patterns provided in
* {@link #setPatternMap(Map)}. The most specific pattern that matches will
* be used to locate a value.
*
* @return the value matching the most specific pattern possible
*
* @throws IllegalStateException if no matching value is found.
*/
public T classify(String classifiable) {
T value = values.match(classifiable);
return value;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2002-2008 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.
*/
/*
* Copyright 2002-2008 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.support;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Simple implementation of the {@link MethodInvoker} interface that invokes a method on an
* object. If the method has no arguments, but arguments are provided, they are ignored and the method
* is invoked anyway. If there are more arguments than there are provided, then an exception is thrown.
*
* @author Lucas Ward
* @since 2.0
*/
public class SimpleMethodInvoker implements MethodInvoker {
private final Object object;
private Method method;
public SimpleMethodInvoker(Object object, Method method) {
Assert.notNull(object, "Object to invoke must not be null");
Assert.notNull(method, "Method to invoke must not be null");
this.object = object;
this.method = method;
}
public SimpleMethodInvoker(Object object, String methodName, Class<?>... paramTypes){
Assert.notNull(object, "Object to invoke must not be null");
this.object = object;
this.method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, paramTypes);
if(this.method == null){
//try with no params
this.method = ClassUtils.getMethodIfAvailable(object.getClass(), methodName, new Class[]{});
}
if(this.method == null){
throw new IllegalArgumentException("No methods found for name: [" + methodName + "] in class: [" +
object.getClass() + "] with arguments of type: [" + Arrays.toString(paramTypes) + "]");
}
}
/* (non-Javadoc)
* @see org.springframework.batch.core.configuration.util.MethodInvoker#invokeMethod(java.lang.Object[])
*/
public Object invokeMethod(Object... args) {
Class<?>[] parameterTypes = method.getParameterTypes();
Object[] invokeArgs;
if(parameterTypes.length == 0){
invokeArgs = new Object[]{};
}
else if(parameterTypes.length != args.length){
throw new IllegalArgumentException("Wrong number of arguments, expected no more than: [" + parameterTypes.length + "]");
}
else{
invokeArgs = args;
}
method.setAccessible(true);
try {
return method.invoke(object, invokeArgs);
} catch (Exception e) {
throw new IllegalArgumentException("Unable to invoke method: [" + method + "] on object: [" +
object + "] with arguments: [" + Arrays.toString(args) + "]", e);
}
}
@Override
public boolean equals(Object obj) {
if(!(obj instanceof SimpleMethodInvoker)){
return false;
}
if(obj == this){
return true;
}
SimpleMethodInvoker rhs = (SimpleMethodInvoker) obj;
return (rhs.method.equals(this.method)) && (rhs.object.equals(this.object));
}
@Override
public int hashCode() {
return new HashCodeBuilder(25, 37).append(object.hashCode()).append(method.hashCode()).toHashCode();
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.support.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Mark a method as capable of classifying its input to an instance of its
* output. Should only be used on non-void methods with one parameter.
*
* @author Dave Syer
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Classifier {
}