org.springframework.classify.PatternMatcher Maven / Gradle / Ivy
Show all versions of spring-retry Show documentation
/*
* Copyright 2006-2022 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
*
* https://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.classify;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
/**
* @author Dave Syer
* @author Dan Garrette
* @param the type of the thing to match a pattern on
*/
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
this.sorted = new ArrayList<>(map.keySet());
this.sorted.sort((o1, 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 matches
* against a pattern. The pattern may contain two special characters:
* '*' means zero or more characters
* '?' means one and only one character
* @param pattern pattern to match against. Must not be null
.
* @param str string which must be matched against the pattern. Must not be
* null
.
* @return true
if the string matches against the pattern, or
* false
otherwise.
*/
public static boolean match(String pattern, String str) {
char[] patArr = pattern.toCharArray();
char[] strArr = str.toCharArray();
int patIdxStart = 0;
int patIdxEnd = patArr.length - 1;
int strIdxStart = 0;
int strIdxEnd = strArr.length - 1;
char ch;
boolean containsStar = pattern.contains("*");
if (!containsStar) {
// No '*'s, so we make a shortcut
if (patIdxEnd != strIdxEnd) {
return false; // Pattern and string do not have the same size
}
for (int i = 0; i <= patIdxEnd; i++) {
ch = patArr[i];
if (ch != '?') {
if (ch != strArr[i]) {
return false;// Character mismatch
}
}
}
return true; // String matches against pattern
}
if (patIdxEnd == 0) {
return true; // Pattern contains only '*', which matches anything
}
// Process characters before first star
while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
if (ch != '?') {
if (ch != strArr[strIdxStart]) {
return false;// Character mismatch
}
}
patIdxStart++;
strIdxStart++;
}
if (strIdxStart > strIdxEnd) {
// All characters in the string are used. Check if only '*'s are
// left in the pattern. If so, we succeeded. Otherwise failure.
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
// Process characters after last star
while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
if (ch != '?') {
if (ch != strArr[strIdxEnd]) {
return false;// Character mismatch
}
}
patIdxEnd--;
strIdxEnd--;
}
if (strIdxStart > strIdxEnd) {
// All characters in the string are used. Check if only '*'s are
// left in the pattern. If so, we succeeded. Otherwise failure.
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
// process pattern between stars. padIdxStart and patIdxEnd point
// always to a '*'.
while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
int patIdxTmp = -1;
for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
if (patArr[i] == '*') {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == patIdxStart + 1) {
// Two stars next to each other, skip the first one.
patIdxStart++;
continue;
}
// Find the pattern between padIdxStart & padIdxTmp in str between
// strIdxStart & strIdxEnd
int patLength = (patIdxTmp - patIdxStart - 1);
int strLength = (strIdxEnd - strIdxStart + 1);
int foundIdx = -1;
strLoop: for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
ch = patArr[patIdxStart + j + 1];
if (ch != '?') {
if (ch != strArr[strIdxStart + i + j]) {
continue strLoop;
}
}
}
foundIdx = strIdxStart + i;
break;
}
if (foundIdx == -1) {
return false;
}
patIdxStart = patIdxTmp;
strIdxStart = foundIdx + patLength;
}
// All characters in the string are used. Check if only '*'s are left
// in the pattern. If so, we succeeded. Otherwise failure.
for (int i = patIdxStart; i <= patIdxEnd; i++) {
if (patArr[i] != '*') {
return false;
}
}
return true;
}
/**
*
* This method takes a String key and a map from Strings to values of any 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.
*
*
* If no matching prefix is found, a {@link IllegalStateException} will be thrown.
*
*
* Null keys are not allowed in the map.
* @param line An input string
* @return the value whose prefix matches the given line
*/
public S match(String line) {
S value = null;
Assert.notNull(line, "A non-null key must be provided to match against.");
for (String key : this.sorted) {
if (PatternMatcher.match(key, line)) {
value = this.map.get(key);
break;
}
}
if (value == null) {
throw new IllegalStateException("Could not find a matching pattern for key=[" + line + "]");
}
return value;
}
}