From 998f6f592ea286f02f1fbcee5fb02b4886fb706c Mon Sep 17 00:00:00 2001 From: dsyer Date: Tue, 3 Mar 2009 11:55:03 +0000 Subject: [PATCH] OPEN - issue BATCH-1108: Add composite ItemWriter/Processor based on Classifier First draft of classifier features added. Also refactored MethodInvoker abstraction into infrastructure. --- .../core/listener/JobListenerFactoryBean.java | 8 +- .../MethodInvokerMethodInterceptor.java | 2 +- .../listener/StepListenerFactoryBean.java | 8 +- .../repository/dao/JdbcStepExecutionDao.java | 2 +- .../xml/AnnotationMethodResolverTests.java | 2 +- .../StepListenerMethodInterceptorTests.java | 6 +- .../PrefixMatchingCompositeLineMapper.java | 29 ++-- .../PrefixMatchingCompositeLineTokenizer.java | 22 +-- .../ClassifierCompositeItemWriter.java | 69 ++++++++++ .../support}/AnnotationMethodResolver.java | 2 +- .../support/BackToBackPatternClassifier.java | 83 ++++++++++++ .../batch/support/Classifier.java | 4 +- .../batch/support/ClassifierAdapter.java | 99 ++++++++++++++ .../batch/support}/MethodInvoker.java | 2 +- .../batch/support}/MethodInvokerUtils.java | 93 ++++++++----- .../batch/support}/MethodResolver.java | 2 +- .../batch/support/PatternMatcher.java | 46 ++++--- .../support/PatternMatchingClassifier.java | 75 +++++++++++ .../batch/support}/SimpleMethodInvoker.java | 4 +- .../batch/support/annotation/Classifier.java | 38 ++++++ .../BackToBackPatternClassifierTests.java | 70 ++++++++++ .../batch/support/ClassifierAdapterTests.java | 127 ++++++++++++++++++ .../batch/support/PatternMatcherTests.java | 36 +++-- .../PatternMatchingClassifierTests.java | 57 ++++++++ .../support}/SimpleMethodInvokerTests.java | 65 +++++---- 25 files changed, 819 insertions(+), 132 deletions(-) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ClassifierCompositeItemWriter.java rename {spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util => spring-batch-infrastructure/src/main/java/org/springframework/batch/support}/AnnotationMethodResolver.java (98%) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/support/BackToBackPatternClassifier.java create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ClassifierAdapter.java rename {spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util => spring-batch-infrastructure/src/main/java/org/springframework/batch/support}/MethodInvoker.java (93%) rename {spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util => spring-batch-infrastructure/src/main/java/org/springframework/batch/support}/MethodInvokerUtils.java (58%) rename {spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util => spring-batch-infrastructure/src/main/java/org/springframework/batch/support}/MethodResolver.java (96%) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatchingClassifier.java rename {spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util => spring-batch-infrastructure/src/main/java/org/springframework/batch/support}/SimpleMethodInvoker.java (97%) create mode 100644 spring-batch-infrastructure/src/main/java/org/springframework/batch/support/annotation/Classifier.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/support/BackToBackPatternClassifierTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/support/ClassifierAdapterTests.java create mode 100644 spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PatternMatchingClassifierTests.java rename {spring-batch-core/src/test/java/org/springframework/batch/core/configuration/util => spring-batch-infrastructure/src/test/java/org/springframework/batch/support}/SimpleMethodInvokerTests.java (65%) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java index e2bdbfd4b..acd827933 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/JobListenerFactoryBean.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.listener; -import static org.springframework.batch.core.configuration.util.MethodInvokerUtils.getMethodInvokerByAnnotation; -import static org.springframework.batch.core.configuration.util.MethodInvokerUtils.getMethodInvokerForInterface; +import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerByAnnotation; +import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerForInterface; import java.util.HashMap; import java.util.HashSet; @@ -28,8 +28,8 @@ import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionListener; -import org.springframework.batch.core.configuration.util.MethodInvoker; -import org.springframework.batch.core.configuration.util.MethodInvokerUtils; +import org.springframework.batch.support.MethodInvoker; +import org.springframework.batch.support.MethodInvokerUtils; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java index 08c20ce20..8ed87fd6f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MethodInvokerMethodInterceptor.java @@ -22,7 +22,7 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.configuration.util.MethodInvoker; +import org.springframework.batch.support.MethodInvoker; /** * {@link MethodInterceptor} that, given a map of method names and diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java index 9d4faa78e..243030c45 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerFactoryBean.java @@ -15,8 +15,8 @@ */ package org.springframework.batch.core.listener; -import static org.springframework.batch.core.configuration.util.MethodInvokerUtils.getMethodInvokerByAnnotation; -import static org.springframework.batch.core.configuration.util.MethodInvokerUtils.getMethodInvokerForInterface; +import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerByAnnotation; +import static org.springframework.batch.support.MethodInvokerUtils.getMethodInvokerForInterface; import java.util.HashMap; import java.util.HashSet; @@ -27,8 +27,8 @@ import java.util.Map.Entry; import org.springframework.aop.framework.ProxyFactory; import org.springframework.aop.support.DefaultPointcutAdvisor; import org.springframework.batch.core.StepListener; -import org.springframework.batch.core.configuration.util.MethodInvoker; -import org.springframework.batch.core.configuration.util.MethodInvokerUtils; +import org.springframework.batch.support.MethodInvoker; +import org.springframework.batch.support.MethodInvokerUtils; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java index a4c4526af..ced7517bf 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/repository/dao/JdbcStepExecutionDao.java @@ -136,7 +136,7 @@ public class JdbcStepExecutionDao extends AbstractJdbcBatchMetadataDao implement * Validate StepExecution. At a minimum, JobId, StartTime, and Status cannot * be null. EndTime can be null for an unfinished job. * - * @param jobExecution + * @param value * @throws IllegalArgumentException */ private void validateStepExecution(StepExecution stepExecution) { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AnnotationMethodResolverTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AnnotationMethodResolverTests.java index 0b50d9415..b3f036741 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AnnotationMethodResolverTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/AnnotationMethodResolverTests.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; import java.lang.reflect.Method; import org.junit.Test; -import org.springframework.batch.core.configuration.util.AnnotationMethodResolver; +import org.springframework.batch.support.AnnotationMethodResolver; /** * @author Mark Fisher diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerMethodInterceptorTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerMethodInterceptorTests.java index 2832de19d..2fa697806 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerMethodInterceptorTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerMethodInterceptorTests.java @@ -13,9 +13,9 @@ import org.aopalliance.intercept.MethodInvocation; import org.junit.Before; import org.junit.Test; import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.configuration.util.MethodInvoker; -import org.springframework.batch.core.configuration.util.MethodInvokerUtils; -import org.springframework.batch.core.configuration.util.SimpleMethodInvoker; +import org.springframework.batch.support.MethodInvoker; +import org.springframework.batch.support.MethodInvokerUtils; +import org.springframework.batch.support.SimpleMethodInvoker; public class StepListenerMethodInterceptorTests { 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 index f7dac303d..7ea94b059 100644 --- 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 @@ -28,9 +28,9 @@ 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 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. * *

* 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 implements LineMapper, InitializingBean { private PrefixMatchingCompositeLineTokenizer tokenizer = new PrefixMatchingCompositeLineTokenizer(); - private Map> fieldSetMappers = null; + + private PatternMatcher> 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 tokenizers) { @@ -75,13 +76,15 @@ public class PrefixMatchingCompositeLineMapper implements LineMapper, Init } public void setFieldSetMappers(Map> fieldSetMappers) { - this.fieldSetMappers = new LinkedHashMap>(); + Assert.isTrue(!fieldSetMappers.isEmpty(), "The 'fieldSetMappers' property must be non-empty"); + LinkedHashMap> map = new LinkedHashMap>(); for (String key : fieldSetMappers.keySet()) { FieldSetMapper value = fieldSetMappers.get(key); if (!key.endsWith("*")) { key = key + "*"; } - this.fieldSetMappers.put(key, value); + map.put(key, value); } + this.patternMatcher = new PatternMatcher>(map); } } 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 6c2f7d2a8..aa3863b82 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 @@ -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 tokenizers = null; + private PatternMatcher 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 tokenizers) { - this.tokenizers = new LinkedHashMap(); + Assert.isTrue(!tokenizers.isEmpty(), "The 'tokenizers' property must be non-empty"); + LinkedHashMap map = new LinkedHashMap(); 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(map); } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ClassifierCompositeItemWriter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ClassifierCompositeItemWriter.java new file mode 100644 index 000000000..968b9d1b5 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/ClassifierCompositeItemWriter.java @@ -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 implements ItemWriter { + + private Classifier> classifier = new ClassifierSupport>(null); + + /** + * @param classifier the classifier to set + */ + public void setClassifier(Classifier> classifier) { + this.classifier = classifier; + } + + /** + * Delegates to injected {@link ItemWriter} instances according to their + * classification by the {@link Classifier}. + */ + public void write(List items) throws Exception { + + Map, List> map = new HashMap, List>(); + + for (T item : items) { + ItemWriter key = classifier.classify(item); + if (!map.containsKey(key)) { + map.put(key, new ArrayList()); + } + map.get(key).add(item); + } + + for (ItemWriter writer : map.keySet()) { + writer.write(map.get(writer)); + } + + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/AnnotationMethodResolver.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/AnnotationMethodResolver.java similarity index 98% rename from spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/AnnotationMethodResolver.java rename to spring-batch-infrastructure/src/main/java/org/springframework/batch/support/AnnotationMethodResolver.java index 37dd88a00..ea5ace787 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/AnnotationMethodResolver.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/AnnotationMethodResolver.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core.configuration.util; +package org.springframework.batch.support; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/BackToBackPatternClassifier.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/BackToBackPatternClassifier.java new file mode 100644 index 000000000..5d9793b8d --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/BackToBackPatternClassifier.java @@ -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 implements Classifier { + + private Classifier router; + + private Classifier 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 router, Classifier 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 map) { + this.matcher = new PatternMatchingClassifier(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.
+ * + * @param delegate the delegate object used to create a router classifier + */ + public void setRouterDelegate(Object delegate) { + this.router = new ClassifierAdapter(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)); + } + +} diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/Classifier.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/Classifier.java index 4b3ddd1c6..8e9719bea 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/Classifier.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/Classifier.java @@ -26,8 +26,8 @@ package org.springframework.batch.support; public interface Classifier { /** - * 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 diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ClassifierAdapter.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ClassifierAdapter.java new file mode 100644 index 000000000..796a8452f --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/ClassifierAdapter.java @@ -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 implements Classifier { + + private MethodInvoker invoker; + + private Classifier 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 delegate) { + classifier = delegate; + } + + public void setDelegate(Classifier 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. 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); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodInvoker.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvoker.java similarity index 93% rename from spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodInvoker.java rename to spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvoker.java index 0b797095a..46f3992af 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodInvoker.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvoker.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core.configuration.util; +package org.springframework.batch.support; /** * A strategy interface for invoking a method. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodInvokerUtils.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java similarity index 58% rename from spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodInvokerUtils.java rename to spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java index 33735a812..4b6a5b25b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodInvokerUtils.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodInvokerUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.configuration.util; +package org.springframework.batch.support; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; @@ -36,82 +36,113 @@ import org.springframework.util.ReflectionUtils; public class MethodInvokerUtils { /** - * Create a {@link MethodInvoker} using the provided method name to search. + * 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 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){ + 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 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){ + if (method == null) { return null; } - else{ + else { return new SimpleMethodInvoker(object, method); } } - + /** - * Create a {@link MethodInvoker} using the provided interface, and string methodname from that interface. + * Create a {@link MethodInvoker} using the provided interface, and method + * name from that interface. * - * @param object to be invoked + * @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 iFace, String methodName, - Object object, Class... paramTypes){ - - if(iFace.isAssignableFrom(object.getClass())){ + 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{ + 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. + * 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 annotationType, final Object candidate){ + public static MethodInvoker getMethodInvokerByAnnotation(final Class 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."); + Assert.isTrue(ObjectUtils.containsElement(annotationType.getAnnotation(Target.class).value(), + ElementType.METHOD), "Annotation [" + annotationType + "] is not a Method-level annotation."); final AtomicReference annotatedMethod = new AtomicReference(); 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 + "]"); + 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){ + if (method == null) { return null; } - else{ + 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 MethodInvoker getMethodInvokerForSingleArgument(Object candidate) { + final AtomicReference methodHolder = new AtomicReference(); + 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); + } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodResolver.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodResolver.java similarity index 96% rename from spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodResolver.java rename to spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodResolver.java index cf63278ff..3cab3454f 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/MethodResolver.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/MethodResolver.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.batch.core.configuration.util; +package org.springframework.batch.support; import java.lang.reflect.Method; diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatcher.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatcher.java index 12cc51056..074983435 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatcher.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatcher.java @@ -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 { + + private Map map = new HashMap(); + private List sorted = new ArrayList(); + + /** + * Initialize a new {@link PatternMatcher} with a map of patterns to values + * @param map a map from String patterns to values + */ + public PatternMatcher(Map map) { + super(); + this.map = map; + // Sort keys to start with the most specific + sorted = new ArrayList(map.keySet()); + Collections.sort(sorted, new Comparator() { + 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. * *

* 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 match(String line, Map 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 sorted = new ArrayList(map.keySet()); - Collections.sort(sorted, new Comparator() { - 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; + } } diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatchingClassifier.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatchingClassifier.java new file mode 100644 index 000000000..c019236bd --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/PatternMatchingClassifier.java @@ -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 implements Classifier { + + private PatternMatcher 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()); + } + + /** + * 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 values) { + super(); + this.values = new PatternMatcher(values); + } + + /** + * A map from pattern to value + * @param values the pattern map to set + */ + public void setPatternMap(Map values) { + this.values = new PatternMatcher(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; + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java similarity index 97% rename from spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java rename to spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java index f4325799b..8d4ec66e6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/util/SimpleMethodInvoker.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/SimpleMethodInvoker.java @@ -29,7 +29,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.configuration.util; +package org.springframework.batch.support; import java.lang.reflect.Method; import java.util.Arrays; @@ -82,7 +82,7 @@ public class SimpleMethodInvoker implements MethodInvoker { if(parameterTypes.length == 0){ invokeArgs = new Object[]{}; } - else if(parameterTypes.length > args.length){ + else if(parameterTypes.length != args.length){ throw new IllegalArgumentException("Wrong number of arguments, expected no more than: [" + parameterTypes.length + "]"); } else{ diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/annotation/Classifier.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/annotation/Classifier.java new file mode 100644 index 000000000..d28ca042b --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/support/annotation/Classifier.java @@ -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 { + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/BackToBackPatternClassifierTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/BackToBackPatternClassifierTests.java new file mode 100644 index 000000000..03c84f3ff --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/BackToBackPatternClassifierTests.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.support; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.batch.support.annotation.Classifier; + +/** + * @author Dave Syer + * + */ +public class BackToBackPatternClassifierTests { + + private BackToBackPatternClassifier classifier = new BackToBackPatternClassifier(); + + private Map map; + + @Before + public void createMap() { + map = new HashMap(); + map.put("foo", "bar"); + map.put("*", "spam"); + } + + @Test(expected=NullPointerException.class) + public void testNoClassifiers() { + classifier.classify("foo"); + } + + @Test + public void testCreateFromConstructor() { + classifier = new BackToBackPatternClassifier(new PatternMatchingClassifier(Collections + .singletonMap("oof", "bucket")), new PatternMatchingClassifier(map)); + assertEquals("spam", classifier.classify("oof")); + } + + @Test + public void testSetRouterDelegate() { + classifier.setRouterDelegate(new Object() { + @SuppressWarnings("unused") + @Classifier + public String convert(String value) { + return "bucket"; + } + }); + classifier.setMatcherMap(map); + assertEquals("spam", classifier.classify("oof")); + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/ClassifierAdapterTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/ClassifierAdapterTests.java new file mode 100644 index 000000000..7579da47b --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/ClassifierAdapterTests.java @@ -0,0 +1,127 @@ +/* + * 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 static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.springframework.batch.support.annotation.Classifier; + +/** + * @author Dave Syer + * + */ +public class ClassifierAdapterTests { + + private ClassifierAdapter adapter = new ClassifierAdapter(); + + @Test + public void testClassifierAdapterObject() { + adapter = new ClassifierAdapter(new Object() { + @SuppressWarnings("unused") + @Classifier + public Integer getValue(String key) { + return Integer.parseInt(key); + } + + @SuppressWarnings("unused") + public Integer getAnother(String key) { + throw new UnsupportedOperationException("Not allowed"); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test(expected = IllegalStateException.class) + public void testClassifierAdapterObjectWithNoAnnotation() { + adapter = new ClassifierAdapter(new Object() { + @SuppressWarnings("unused") + public Integer getValue(String key) { + return Integer.parseInt(key); + } + + @SuppressWarnings("unused") + public Integer getAnother(String key) { + throw new UnsupportedOperationException("Not allowed"); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test + public void testClassifierAdapterObjectSingleMethodWithNoAnnotation() { + adapter = new ClassifierAdapter(new Object() { + @SuppressWarnings("unused") + public Integer getValue(String key) { + return Integer.parseInt(key); + } + @SuppressWarnings("unused") + public void doNothing(String key) { + } + @SuppressWarnings("unused") + public String doNothing(String key, int value) { + return "foo"; + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test + public void testClassifierAdapterClassifier() { + adapter = new ClassifierAdapter( + new org.springframework.batch.support.Classifier() { + public Integer classify(String classifiable) { + return Integer.valueOf(classifiable); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test + public void testClassifyWithSetter() { + adapter.setDelegate(new Object() { + @SuppressWarnings("unused") + @Classifier + public Integer getValue(String key) { + return Integer.parseInt(key); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test(expected=IllegalArgumentException.class) + public void testClassifyWithWrongType() { + adapter.setDelegate(new Object() { + @SuppressWarnings("unused") + @Classifier + public String getValue(Integer key) { + return key.toString(); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + + @Test + public void testClassifyWithClassifier() { + adapter.setDelegate(new org.springframework.batch.support.Classifier() { + public Integer classify(String classifiable) { + return Integer.valueOf(classifiable); + } + }); + assertEquals(23, adapter.classify("23").intValue()); + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PatternMatcherTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PatternMatcherTests.java index 48df26683..7debe99e6 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PatternMatcherTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PatternMatcherTests.java @@ -76,6 +76,26 @@ public class PatternMatcherTests { assertTrue(PatternMatcher.match("a*c", "abdegc")); } + @Test + public void testMatchTwoStars() { + assertTrue(PatternMatcher.match("a*d*", "abcdeg")); + } + + @Test + public void testMatchPastEnd() { + assertFalse(PatternMatcher.match("a*de", "abcdeg")); + } + + @Test + public void testMatchPastEndTwoStars() { + assertTrue(PatternMatcher.match("a*d*g*", "abcdeg")); + } + + @Test + public void testMatchStarAtEnd() { + assertTrue(PatternMatcher.match("ab*", "ab")); + } + @Test public void testMatchStarNo() { assertFalse(PatternMatcher.match("a*c", "abdeg")); @@ -83,36 +103,36 @@ public class PatternMatcherTests { @Test public void testMatchPrefixSubsumed() { - assertEquals(2, PatternMatcher.match("apple", map).intValue()); + assertEquals(2, new PatternMatcher(map).match("apple").intValue()); } @Test public void testMatchPrefixSubsuming() { - assertEquals(3, PatternMatcher.match("animal", map).intValue()); + assertEquals(3, new PatternMatcher(map).match("animal").intValue()); } @Test public void testMatchPrefixUnrelated() { - assertEquals(4, PatternMatcher.match("biggest", map).intValue()); + assertEquals(4, new PatternMatcher(map).match("biggest").intValue()); } @Test(expected = IllegalStateException.class) - public void testMatchPrefix_noMatch() { - PatternMatcher.match("bat", map); + public void testMatchPrefixNoMatch() { + new PatternMatcher(map).match("bat"); } @Test public void testMatchPrefixDefaultValueUnrelated() { - assertEquals(5, PatternMatcher.match("biggest", defaultMap).intValue()); + assertEquals(5, new PatternMatcher(defaultMap).match("biggest").intValue()); } @Test public void testMatchPrefixDefaultValueEmptyString() { - assertEquals(1, PatternMatcher.match("", defaultMap).intValue()); + assertEquals(1, new PatternMatcher(defaultMap).match("").intValue()); } @Test public void testMatchPrefixDefaultValueNoMatch() { - assertEquals(1, PatternMatcher.match("bat", defaultMap).intValue()); + assertEquals(1, new PatternMatcher(defaultMap).match("bat").intValue()); } } diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PatternMatchingClassifierTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PatternMatchingClassifierTests.java new file mode 100644 index 000000000..98b1d616d --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/PatternMatchingClassifierTests.java @@ -0,0 +1,57 @@ +/* + * 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 static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +/** + * @author Dave Syer + * + */ +public class PatternMatchingClassifierTests { + + private PatternMatchingClassifier classifier = new PatternMatchingClassifier(); + + private Map map; + + @Before + public void createMap() { + map = new HashMap(); + map.put("foo", "bar"); + map.put("*", "spam"); + } + + @Test + public void testSetPatternMap() { + classifier.setPatternMap(map); + assertEquals("bar", classifier.classify("foo")); + assertEquals("spam", classifier.classify("bucket")); + } + + @Test + public void testCreateFromMap() { + classifier = new PatternMatchingClassifier(map); + assertEquals("bar", classifier.classify("foo")); + assertEquals("spam", classifier.classify("bucket")); + } + +} \ No newline at end of file diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/util/SimpleMethodInvokerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/SimpleMethodInvokerTests.java similarity index 65% rename from spring-batch-core/src/test/java/org/springframework/batch/core/configuration/util/SimpleMethodInvokerTests.java rename to spring-batch-infrastructure/src/test/java/org/springframework/batch/support/SimpleMethodInvokerTests.java index cbefc80a8..bd059b637 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/util/SimpleMethodInvokerTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/support/SimpleMethodInvokerTests.java @@ -29,16 +29,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.batch.core.configuration.util; +package org.springframework.batch.support; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.lang.reflect.Method; import org.junit.Before; import org.junit.Test; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; import org.springframework.util.Assert; /** @@ -48,8 +48,7 @@ import org.springframework.util.Assert; public class SimpleMethodInvokerTests { TestClass testClass; - JobExecution jobExecution = new JobExecution(11L); - + String value = "foo"; @Before public void setUp(){ testClass = new TestClass(); @@ -58,48 +57,48 @@ public class SimpleMethodInvokerTests { @Test public void testMethod() throws Exception{ - Method method = TestClass.class.getMethod("beforeJob"); + Method method = TestClass.class.getMethod("before"); MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, method); - methodInvoker.invokeMethod(jobExecution); - assertTrue(testClass.beforeJobCalled); + methodInvoker.invokeMethod(value); + assertTrue(testClass.beforeCalled); } @Test public void testMethodByName() throws Exception{ - MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, "beforeJob", JobExecutionListener.class); - methodInvoker.invokeMethod(jobExecution); - assertTrue(testClass.beforeJobCalled); + MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, "before", String.class); + methodInvoker.invokeMethod(value); + assertTrue(testClass.beforeCalled); } @Test public void testMethodWithExecution() throws Exception{ - Method method = TestClass.class.getMethod("beforeJobWithExecution", JobExecution.class); + Method method = TestClass.class.getMethod("beforeWithArgument", String.class); MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, method); - methodInvoker.invokeMethod(jobExecution); - assertTrue(testClass.beforeJobCalled); + methodInvoker.invokeMethod(value); + assertTrue(testClass.beforeCalled); } @Test public void testMethodByNameWithExecution() throws Exception{ - MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, "beforeJobWithExecution", JobExecution.class); - methodInvoker.invokeMethod(jobExecution); - assertTrue(testClass.beforeJobCalled); + MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, "beforeWithArgument", String.class); + methodInvoker.invokeMethod(value); + assertTrue(testClass.beforeCalled); } @Test(expected=IllegalArgumentException.class) public void testMethodWithTooManyArguments() throws Exception{ - Method method = TestClass.class.getMethod("beforeJobWithTooManyArguments", JobExecution.class, int.class); + Method method = TestClass.class.getMethod("beforeWithTooManyArguments", String.class, int.class); MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, method); - methodInvoker.invokeMethod(jobExecution); - assertFalse(testClass.beforeJobCalled); + methodInvoker.invokeMethod(value); + assertFalse(testClass.beforeCalled); } @Test(expected=IllegalArgumentException.class) public void testMethodByNameWithTooManyArguments() throws Exception{ - MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, "beforeJobWithTooManyArguments", JobExecution.class); - methodInvoker.invokeMethod(jobExecution); - assertFalse(testClass.beforeJobCalled); + MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, "beforeWithTooManyArguments", String.class); + methodInvoker.invokeMethod(value); + assertFalse(testClass.beforeCalled); } @Test @@ -111,29 +110,29 @@ public class SimpleMethodInvokerTests { @Test public void testEquals() throws Exception{ - Method method = TestClass.class.getMethod("beforeJobWithExecution", JobExecution.class); + Method method = TestClass.class.getMethod("beforeWithArgument", String.class); MethodInvoker methodInvoker = new SimpleMethodInvoker(testClass, method); - method = TestClass.class.getMethod("beforeJobWithExecution", JobExecution.class); + method = TestClass.class.getMethod("beforeWithArgument", String.class); MethodInvoker methodInvoker2 = new SimpleMethodInvoker(testClass, method); assertEquals(methodInvoker, methodInvoker2); } private class TestClass{ - boolean beforeJobCalled = false; + boolean beforeCalled = false; boolean argumentTestCalled = false; - public void beforeJob(){ - beforeJobCalled = true; + public void before(){ + beforeCalled = true; } - public void beforeJobWithExecution(JobExecution jobExecution){ - beforeJobCalled = true; + public void beforeWithArgument(String value){ + beforeCalled = true; } - public void beforeJobWithTooManyArguments(JobExecution jobExecution, int someInt){ - beforeJobCalled = true; + public void beforeWithTooManyArguments(String value, int someInt){ + beforeCalled = true; } public void argumentTest(Object object){