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 f5f5434401..ff02568912 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2021 the original author or authors.
+ * Copyright 2006-2023 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.
@@ -19,6 +19,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Map;
+import java.util.regex.Pattern;
import org.springframework.util.Assert;
@@ -26,6 +27,7 @@
* @author Dave Syer
* @author Dan Garrette
* @author Marten Deinum
+ * @author Injae Kim
*/
public class PatternMatcher {
@@ -180,6 +182,19 @@ public static boolean match(String pattern, String str) {
return true;
}
+ /**
+ * Tests whether or not a string matches against a regular expression.
+ * @param regex regular expression to match against. Must not be {@code null}.
+ * @param str string which must be matched against the regular expression. Must not be {@code null}.
+ * @return {@code true} if the string matches against the regular expression, or {@code false} otherwise.
+ */
+ public static boolean matchRegex(String regex, String str) {
+ Assert.notNull(regex, "Regex must not be null");
+ Assert.notNull(str, "Str must not be null");
+
+ return Pattern.matches(regex, str);
+ }
+
/**
*
* This method takes a String key and a map from Strings to values of any type. During
@@ -202,12 +217,23 @@ public S match(String line) {
Assert.notNull(line, "A non-null key must be provided to match against.");
for (String key : sorted) {
- if (PatternMatcher.match(key, line)) {
+ if (match(key, line)) {
value = map.get(key);
break;
}
}
+ if (value == null) {
+ for (String key : sorted) {
+ try {
+ if (matchRegex(key, line)) {
+ value = map.get(key);
+ break;
+ }
+ } catch (Throwable ignored) {}
+ }
+ }
+
if (value == null) {
throw new IllegalStateException("Could not find a matching pattern for key=[" + line + "]");
}
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/PatternMatchingCompositeLineMapperTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/PatternMatchingCompositeLineMapperTests.java
index 734171b4c8..8fa1750dca 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/PatternMatchingCompositeLineMapperTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/mapping/PatternMatchingCompositeLineMapperTests.java
@@ -33,6 +33,7 @@
* @author Dan Garrette
* @author Dave Syer
* @author Mahmoud Ben Hassine
+ * @author Injae Kim
* @since 2.0
*/
class PatternMatchingCompositeLineMapperTests {
@@ -51,6 +52,7 @@ void testKeyFound() throws Exception {
Map tokenizers = new HashMap<>();
tokenizers.put("foo*", line -> new DefaultFieldSet(new String[] { "a", "b" }));
tokenizers.put("bar*", line -> new DefaultFieldSet(new String[] { "c", "d" }));
+ tokenizers.put("regex.*", line -> new DefaultFieldSet(new String[] { "e", "f" }));
mapper.setTokenizers(tokenizers);
Map> fieldSetMappers = new HashMap<>();
@@ -62,15 +64,35 @@ void testKeyFound() throws Exception {
assertEquals(new Name("d", "c", 0), name);
}
+ @Test
+ void testKeyFoundByRegex() throws Exception {
+ Map tokenizers = new HashMap<>();
+ tokenizers.put("foo*", line -> new DefaultFieldSet(new String[] { "a", "b" }));
+ tokenizers.put("bar*", line -> new DefaultFieldSet(new String[] { "c", "d" }));
+ tokenizers.put("regex.*", line -> new DefaultFieldSet(new String[] { "e", "f" }));
+ mapper.setTokenizers(tokenizers);
+
+ Map> fieldSetMappers = new HashMap<>();
+ fieldSetMappers.put("foo*", fs -> new Name(fs.readString(0), fs.readString(1), 0));
+ fieldSetMappers.put("bar*", fs -> new Name(fs.readString(1), fs.readString(0), 0));
+ fieldSetMappers.put("regex.*", fs -> new Name(fs.readString(0), fs.readString(1), 0));
+ mapper.setFieldSetMappers(fieldSetMappers);
+
+ Name name = mapper.mapLine("regex-ABC_12345", 1);
+ assertEquals(new Name("e", "f", 0), name);
+ }
+
@Test
void testMapperKeyNotFound() {
Map tokenizers = new HashMap<>();
tokenizers.put("foo*", line -> new DefaultFieldSet(new String[] { "a", "b" }));
tokenizers.put("bar*", line -> new DefaultFieldSet(new String[] { "c", "d" }));
+ tokenizers.put("regex.*", line -> new DefaultFieldSet(new String[] { "e", "f" }));
mapper.setTokenizers(tokenizers);
Map> fieldSetMappers = new HashMap<>();
fieldSetMappers.put("foo*", fs -> new Name(fs.readString(0), fs.readString(1), 0));
+ fieldSetMappers.put("regex.*", fs -> new Name(fs.readString(0), fs.readString(1), 0));
mapper.setFieldSetMappers(fieldSetMappers);
assertThrows(IllegalStateException.class, () -> mapper.mapLine("bar", 1));
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/PatternMatchingCompositeLineTokenizerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/PatternMatchingCompositeLineTokenizerTests.java
index 6002782b00..06cc59f519 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/PatternMatchingCompositeLineTokenizerTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/file/transform/PatternMatchingCompositeLineTokenizerTests.java
@@ -30,6 +30,7 @@
* @author Ben Hale
* @author Dan Garrette
* @author Dave Syer
+ * @author Injae Kim
*/
class PatternMatchingCompositeLineTokenizerTests {
@@ -45,6 +46,7 @@ void testEmptyKeyMatchesAnyLine() throws Exception {
Map map = new HashMap<>();
map.put("*", new DelimitedLineTokenizer());
map.put("foo", line -> null);
+ map.put("regex.*", line -> null);
tokenizer.setTokenizers(map);
tokenizer.afterPropertiesSet();
FieldSet fields = tokenizer.tokenize("abc");
@@ -53,16 +55,29 @@ void testEmptyKeyMatchesAnyLine() throws Exception {
@Test
void testEmptyKeyDoesNotMatchWhenAlternativeAvailable() throws Exception {
-
Map map = new LinkedHashMap<>();
map.put("*", line -> null);
map.put("foo*", new DelimitedLineTokenizer());
+ map.put("regex.*", line -> null);
tokenizer.setTokenizers(map);
tokenizer.afterPropertiesSet();
FieldSet fields = tokenizer.tokenize("foo,bar");
assertEquals("bar", fields.readString(1));
}
+ @Test
+ void testMatchRegex() throws Exception {
+ Map map = new HashMap<>();
+ map.put("foo", line -> null);
+ map.put("regex.*", new DelimitedLineTokenizer());
+ tokenizer.setTokenizers(map);
+ tokenizer.afterPropertiesSet();
+ FieldSet fields = tokenizer.tokenize("regex-ABC_12345,REGEX");
+ assertEquals(2, fields.getFieldCount());
+ assertEquals("regex-ABC_12345", fields.readString(0));
+ assertEquals("REGEX", fields.readString(1));
+ }
+
@Test
void testNoMatch() throws Exception {
tokenizer.setTokenizers(Collections.singletonMap("foo", (LineTokenizer) new DelimitedLineTokenizer()));
@@ -70,6 +85,13 @@ void testNoMatch() throws Exception {
assertThrows(IllegalStateException.class, () -> tokenizer.tokenize("nomatch"));
}
+ @Test
+ void testNoMatchRegex() throws Exception {
+ tokenizer.setTokenizers(Collections.singletonMap("foo.*", (LineTokenizer) new DelimitedLineTokenizer()));
+ tokenizer.afterPropertiesSet();
+ assertThrows(IllegalStateException.class, () -> tokenizer.tokenize("nomatch"));
+ }
+
@Test
void testMatchWithPrefix() throws Exception {
tokenizer.setTokenizers(
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 2a55ad3906..3dab717e27 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2022 the original author or authors.
+ * Copyright 2006-2023 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.
@@ -22,11 +22,13 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.regex.PatternSyntaxException;
import org.junit.jupiter.api.Test;
/**
* @author Dan Garrette
+ * @author Injae Kim
* @since 2.0
*/
class PatternMatcherTests {
@@ -37,6 +39,7 @@ class PatternMatcherTests {
map.put("an*", 3);
map.put("a*", 2);
map.put("big*", 4);
+ map.put("bcd.*", 5);
}
private static final Map defaultMap = new HashMap<>();
@@ -49,6 +52,15 @@ class PatternMatcherTests {
defaultMap.put("*", 1);
}
+ private static final Map regexMap = new HashMap<>();
+
+ static {
+ regexMap.put("abc.*", 1);
+ regexMap.put("a...e", 2);
+ regexMap.put("123.[0-9][0-9]\\d", 3);
+ regexMap.put("*............", 100); // invalid regex format
+ }
+
@Test
void testMatchNoWildcardYes() {
assertTrue(PatternMatcher.match("abc", "abc"));
@@ -104,6 +116,29 @@ void testMatchStarNo() {
assertFalse(PatternMatcher.match("a*c", "abdeg"));
}
+ @Test
+ void testMatchRegex() {
+ assertTrue(PatternMatcher.matchRegex("abc.*", "abcde"));
+ }
+
+ @Test
+ void testMatchRegex_notMatched() {
+ assertFalse(PatternMatcher.matchRegex("abc.*", "cdefg"));
+ assertFalse(PatternMatcher.matchRegex("abc.", "abcde"));
+ }
+
+ @Test
+ void testMatchRegex_thrown_invalidRegexFormat() {
+ assertThrows(PatternSyntaxException.class, () -> PatternMatcher.matchRegex("*..", "abc"));
+ }
+
+ @Test
+ void testMatchRegex_thrown_notNullParam() {
+ assertThrows(IllegalArgumentException.class, () -> PatternMatcher.matchRegex("regex", null));
+ assertThrows(IllegalArgumentException.class, () -> PatternMatcher.matchRegex(null, "str"));
+ }
+
+
@Test
void testMatchPrefixSubsumed() {
assertEquals(2, new PatternMatcher<>(map).match("apple").intValue());
@@ -119,6 +154,11 @@ void testMatchPrefixUnrelated() {
assertEquals(4, new PatternMatcher<>(map).match("biggest").intValue());
}
+ @Test
+ void testMatchByRegex() {
+ assertEquals(5, new PatternMatcher<>(map).match("bcdef12345").intValue());
+ }
+
@Test
void testMatchPrefixNoMatch() {
PatternMatcher matcher = new PatternMatcher<>(map);
@@ -140,4 +180,24 @@ void testMatchPrefixDefaultValueNoMatch() {
assertEquals(1, new PatternMatcher<>(defaultMap).match("bat").intValue());
}
+ @Test
+ void testMatchRegexPrefix() {
+ assertEquals(1, new PatternMatcher<>(regexMap).match("abcdefg").intValue());
+ }
+
+ @Test
+ void testMatchRegexWildCards() {
+ assertEquals(2, new PatternMatcher<>(regexMap).match("a12De").intValue());
+ }
+
+ @Test
+ void testMatchRegexDigits() {
+ assertEquals(3, new PatternMatcher<>(regexMap).match("123-789").intValue());
+ }
+
+ @Test
+ void testMatchRegexNotMatched() {
+ assertThrows(IllegalStateException.class, () -> new PatternMatcher<>(regexMap).match("Hello world!"));
+ }
+
}