You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
281 lines
8.0 KiB
281 lines
8.0 KiB
7 years ago
|
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
|
||
|
// All rights reserved.
|
||
|
//
|
||
|
// Redistribution and use in source and binary forms, with or without
|
||
|
// modification, are permitted provided that the following conditions are met:
|
||
|
//
|
||
|
// 1. Redistributions of source code must retain the above copyright notice,
|
||
|
// this list of conditions and the following disclaimer.
|
||
|
//
|
||
|
// 2. Redistributions in binary form must reproduce the above copyright
|
||
|
// notice, this list of conditions and the following disclaimer in the
|
||
|
// documentation and/or other materials provided with the distribution.
|
||
|
//
|
||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||
|
// POSSIBILITY OF SUCH DAMAGE.
|
||
|
|
||
|
package com.fr.third.jodd.util;
|
||
|
|
||
|
/**
|
||
|
* Checks whether a string or path matches a given wildcard pattern.
|
||
|
* Possible patterns allow to match single characters ('?') or any count of
|
||
|
* characters ('*'). Wildcard characters can be escaped (by an '\').
|
||
|
* When matching path, deep tree wildcard also can be used ('**').
|
||
|
* <p>
|
||
|
* This method uses recursive matching, as in linux or windows. regexp works the same.
|
||
|
* This method is very fast, comparing to similar implementations.
|
||
|
*/
|
||
|
public class Wildcard {
|
||
|
|
||
|
/**
|
||
|
* Checks whether a string matches a given wildcard pattern.
|
||
|
*
|
||
|
* @param string input string
|
||
|
* @param pattern pattern to match
|
||
|
* @return <code>true</code> if string matches the pattern, otherwise <code>false</code>
|
||
|
*/
|
||
|
public static boolean match(CharSequence string, CharSequence pattern) {
|
||
|
return match(string, pattern, 0, 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks if two strings are equals or if they {@link #match(CharSequence, CharSequence)}.
|
||
|
* Useful for cases when matching a lot of equal strings and speed is important.
|
||
|
*/
|
||
|
public static boolean equalsOrMatch(CharSequence string, CharSequence pattern) {
|
||
|
if (string.equals(pattern)) {
|
||
|
return true;
|
||
|
}
|
||
|
return match(string, pattern, 0, 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Internal matching recursive function.
|
||
|
*/
|
||
|
private static boolean match(CharSequence string, CharSequence pattern, int sNdx, int pNdx) {
|
||
|
int pLen = pattern.length();
|
||
|
if (pLen == 1) {
|
||
|
if (pattern.charAt(0) == '*') { // speed-up
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
int sLen = string.length();
|
||
|
boolean nextIsNotWildcard = false;
|
||
|
|
||
|
while (true) {
|
||
|
|
||
|
// check if end of string and/or pattern occurred
|
||
|
if ((sNdx >= sLen)) { // end of string still may have pending '*' in pattern
|
||
|
while ((pNdx < pLen) && (pattern.charAt(pNdx) == '*')) {
|
||
|
pNdx++;
|
||
|
}
|
||
|
return pNdx >= pLen;
|
||
|
}
|
||
|
if (pNdx >= pLen) { // end of pattern, but not end of the string
|
||
|
return false;
|
||
|
}
|
||
|
char p = pattern.charAt(pNdx); // pattern char
|
||
|
|
||
|
// perform logic
|
||
|
if (!nextIsNotWildcard) {
|
||
|
|
||
|
if (p == '\\') {
|
||
|
pNdx++;
|
||
|
nextIsNotWildcard = true;
|
||
|
continue;
|
||
|
}
|
||
|
if (p == '?') {
|
||
|
sNdx++; pNdx++;
|
||
|
continue;
|
||
|
}
|
||
|
if (p == '*') {
|
||
|
char pNext = 0; // next pattern char
|
||
|
if (pNdx + 1 < pLen) {
|
||
|
pNext = pattern.charAt(pNdx + 1);
|
||
|
}
|
||
|
if (pNext == '*') { // double '*' have the same effect as one '*'
|
||
|
pNdx++;
|
||
|
continue;
|
||
|
}
|
||
|
int i;
|
||
|
pNdx++;
|
||
|
|
||
|
// find recursively if there is any substring from the end of the
|
||
|
// line that matches the rest of the pattern !!!
|
||
|
for (i = string.length(); i >= sNdx; i--) {
|
||
|
if (match(string, pattern, i, pNdx)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
nextIsNotWildcard = false;
|
||
|
}
|
||
|
|
||
|
// check if pattern char and string char are equals
|
||
|
if (p != string.charAt(sNdx)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// everything matches for now, continue
|
||
|
sNdx++; pNdx++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// ---------------------------------------------------------------- utilities
|
||
|
|
||
|
/**
|
||
|
* Matches string to at least one pattern.
|
||
|
* Returns index of matched pattern, or <code>-1</code> otherwise.
|
||
|
* @see #match(CharSequence, CharSequence)
|
||
|
*/
|
||
|
public static int matchOne(String src, String[] patterns) {
|
||
|
for (int i = 0; i < patterns.length; i++) {
|
||
|
if (match(src, patterns[i])) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Matches path to at least one pattern.
|
||
|
* Returns index of matched pattern or <code>-1</code> otherwise.
|
||
|
* @see #matchPath(String, String)
|
||
|
*/
|
||
|
public static int matchPathOne(String path, String[] patterns) {
|
||
|
for (int i = 0; i < patterns.length; i++) {
|
||
|
if (matchPath(path, patterns[i])) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// ---------------------------------------------------------------- path
|
||
|
|
||
|
protected static final String PATH_MATCH = "**";
|
||
|
protected static final String PATH_SEPARATORS = "/\\";
|
||
|
|
||
|
/**
|
||
|
* Matches path against pattern using *, ? and ** wildcards.
|
||
|
* Both path and the pattern are tokenized on path separators (both \ and /).
|
||
|
* '**' represents deep tree wildcard, as in Ant.
|
||
|
*/
|
||
|
public static boolean matchPath(String path, String pattern) {
|
||
|
String[] pathElements = StringUtil.splitc(path, PATH_SEPARATORS);
|
||
|
String[] patternElements = StringUtil.splitc(pattern, PATH_SEPARATORS);
|
||
|
return matchTokens(pathElements, patternElements);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Match tokenized string and pattern.
|
||
|
*/
|
||
|
protected static boolean matchTokens(String[] tokens, String[] patterns) {
|
||
|
int patNdxStart = 0;
|
||
|
int patNdxEnd = patterns.length - 1;
|
||
|
int tokNdxStart = 0;
|
||
|
int tokNdxEnd = tokens.length - 1;
|
||
|
|
||
|
while ((patNdxStart <= patNdxEnd) && (tokNdxStart <= tokNdxEnd)) { // find first **
|
||
|
String patDir = patterns[patNdxStart];
|
||
|
if (patDir.equals(PATH_MATCH)) {
|
||
|
break;
|
||
|
}
|
||
|
if (!match(tokens[tokNdxStart], patDir)) {
|
||
|
return false;
|
||
|
}
|
||
|
patNdxStart++;
|
||
|
tokNdxStart++;
|
||
|
}
|
||
|
if (tokNdxStart > tokNdxEnd) {
|
||
|
for (int i = patNdxStart; i <= patNdxEnd; i++) { // string is finished
|
||
|
if (!patterns[i].equals(PATH_MATCH)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
if (patNdxStart > patNdxEnd) {
|
||
|
return false; // string is not finished, but pattern is
|
||
|
}
|
||
|
|
||
|
while ((patNdxStart <= patNdxEnd) && (tokNdxStart <= tokNdxEnd)) { // to the last **
|
||
|
String patDir = patterns[patNdxEnd];
|
||
|
if (patDir.equals(PATH_MATCH)) {
|
||
|
break;
|
||
|
}
|
||
|
if (!match(tokens[tokNdxEnd], patDir)) {
|
||
|
return false;
|
||
|
}
|
||
|
patNdxEnd--;
|
||
|
tokNdxEnd--;
|
||
|
}
|
||
|
if (tokNdxStart > tokNdxEnd) {
|
||
|
for (int i = patNdxStart; i <= patNdxEnd; i++) { // string is finished
|
||
|
if (!patterns[i].equals(PATH_MATCH)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
while ((patNdxStart != patNdxEnd) && (tokNdxStart <= tokNdxEnd)) {
|
||
|
int patIdxTmp = -1;
|
||
|
for (int i = patNdxStart + 1; i <= patNdxEnd; i++) {
|
||
|
if (patterns[i].equals(PATH_MATCH)) {
|
||
|
patIdxTmp = i;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (patIdxTmp == patNdxStart + 1) {
|
||
|
patNdxStart++; // skip **/** situation
|
||
|
continue;
|
||
|
}
|
||
|
// find the pattern between padIdxStart & padIdxTmp in str between strIdxStart & strIdxEnd
|
||
|
int patLength = (patIdxTmp - patNdxStart - 1);
|
||
|
int strLength = (tokNdxEnd - tokNdxStart + 1);
|
||
|
int ndx = -1;
|
||
|
strLoop:
|
||
|
for (int i = 0; i <= strLength - patLength; i++) {
|
||
|
for (int j = 0; j < patLength; j++) {
|
||
|
String subPat = patterns[patNdxStart + j + 1];
|
||
|
String subStr = tokens[tokNdxStart + i + j];
|
||
|
if (!match(subStr, subPat)) {
|
||
|
continue strLoop;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ndx = tokNdxStart + i;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (ndx == -1) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
patNdxStart = patIdxTmp;
|
||
|
tokNdxStart = ndx + patLength;
|
||
|
}
|
||
|
|
||
|
for (int i = patNdxStart; i <= patNdxEnd; i++) {
|
||
|
if (!patterns[i].equals(PATH_MATCH)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|