
org.apache.wicket.application.WildcardMatcherHelper Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.wicket.application;
import java.util.HashMap;
import java.util.Map;
/**
* This class is an utility class that perform wildcard-patterns matching and isolation.
*
* @version $Id: WildcardMatcherHelper.java 420832 2006-07-11 13:11:02Z cziegeler $
*/
public class WildcardMatcherHelper
{
// ~ Static fields/initializers
// -----------------------------------------------------------------
/** Escape character */
public static final char ESC = '\\';
/** Default path separator: "." */
public static final char PATHSEP = '.';
/** any char */
public static final char STAR = '*';
// ~ Methods
// ------------------------------------------------------------------------------------
/**
* Match a pattern against a string and isolates wildcard replacement into a Map
.
* Here is how the matching algorithm works:
*
*
* - The '*' character, meaning that zero or more characters (excluding the path separator
* '/') are to be matched.
* - The '**' sequence, meaning that zero or more characters (including the path separator
* '/') are to be matched.
* - The '\*' sequence is honored as a literal '*' character, not a wildcard
*
*
* When more than two '*' characters, not separated by another character, are found their value
* is considered as '**' and immediate succeeding '*' are skipped.
* The '**' wildcard is greedy and thus the following sample cannot match:
*
* - pattern
* - STAR,STAR,PATHSEP,STAR,PATHSEP,STAR,STAR (why can't I express it literally?)
*
- string
* - foo/bar/baz/bug
*
* The first '**' in the pattern will suck up until the last '/' in string and thus will not
* match.
* A more advance algorithm could certainly check whether there is an other literal later in the
* pattern to ev. match in string but I'll leave this exercise to someone else ATM if one needs
* such.
*
* @param pat
* The pattern string.
* @param str
* The string to math against the pattern
*
* @return a Map
containing the representation of the extracted pattern. The
* extracted patterns are keys in the Map
from left to right beginning with
* "1" for the left most, "2" for the next, a.s.o. The key "0" is the string itself. If
* the return value is null, string does not match to the pattern .
*/
public static Map match(final String pat, final String str)
{
final Matcher map = new Matcher(pat, str);
if (map.isMatch())
{
return map.getMap();
}
return null;
}
// ~ Inner Classes
// ------------------------------------------------------------------------------
/**
* The private matcher class
*/
private static class Matcher
{
// ~ Instance fields
// ------------------------------------------------------------------------
/** The character array of the pattern */
private final char[] apat;
/** The length of the character array of the pattern */
private final int lpat;
/** The character array of the string */
private final char[] astr;
/** The length of the character array of the string */
private final int lstr;
/** The Map
to be filled */
private final Map map = new HashMap();
/** Whether string matched to pattern */
private final boolean matched;
/** map index */
private int idx = 0;
/** index into pattern */
private int ipat = 0;
/** index into string */
private int istr = 0;
// ~ Constructors
// ---------------------------------------------------------------------------
/**
* Creates a new Matcher object.
*
* @param pat
* The pattern
* @param str
* The string
*/
public Matcher(final String pat, final String str)
{
apat = pat.toCharArray();
lpat = apat.length;
astr = str.toCharArray();
lstr = astr.length;
add(str);
matched = match();
}
// ~ Methods
// --------------------------------------------------------------------------------
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public Map getMap()
{
return map;
}
/**
* Has it matched?
*
* @return whether it has matched
*/
public boolean isMatch()
{
return matched;
}
/**
* Add a extracted substring to the map
*
* @param aStr
* The extracted substring
*/
private void add(final String aStr)
{
map.put(String.valueOf(idx++), aStr);
}
/**
* Scans the pattern and the search string from the start toward the end
*
* @return whether the string matches the pattern
*/
private boolean match()
{
// scan a common literal prefix
scanLiteralPrefix();
// if we are already at the end of both strings
// than the pattern matched
if (ipat >= lpat && istr >= lstr)
{
return true;
}
// if hole string has matched the pattern so far and the rest of the pattern only has
// wildcard(s)
// we match too otherwise we clearly don't match
if (ipat < lpat && istr >= lstr)
{
while (ipat < lpat && apat[ipat] == STAR)
{
ipat++;
}
if (ipat >= lpat)
{
add("");
return true;
}
else
{
return false;
}
}
// if hole pattern has matched the string so far but the string has more characters left
// we don't match
if (ipat >= lpat && istr < lstr)
{
return false;
}
// if we have not stopped at a wildcard character
// a character doesn't match and thus we do not match at all
if (apat[ipat] != STAR)
{
return false;
}
// if it is a double (or more) wildcard pattern
if (ipat < lpat - 1 && apat[ipat + 1] == STAR)
{
// skip to first non star character in the pattern
while (++ipat < lpat && apat[ipat] == STAR)
{
; // noop
}
// if we are at the end of the pattern we've matched and are finish scanning
if (ipat >= lpat)
{
add(new String(astr, istr, lstr - istr));
return true;
}
// Now we need to scan for the end of the literal characters in the pattern
final int sipat = ipat; // start position of a literal character used for substring
// operations
while (ipat < lpat && (apat[ipat] != STAR || (ipat > 0 && apat[ipat - 1] == ESC)))
{
ipat++;
}
// if we reached the end of the pattern just do a string compare with the
// corresponding part from
// the end of the string
if (ipat >= lpat)
{
return checkEnds(sipat, false);
}
// Now we need to check whether the literal substring of the pattern
// is contained in the string somewhere
final int l = ipat - sipat;
int eistr = lstr - l;
// because the '**' wildcard need to be greedy we scan from the end of the string
// for a match
while (istr < eistr && !strncmp(apat, sipat, astr, eistr, l))
{
eistr--;
}
if (istr >= eistr)
{
return false;
}
add(new String(astr, istr, eistr - istr));
istr = eistr + l;
}
else
{// if it is a single star pattern
// skip the star
++ipat;
// if we are at the beginning of the pattern we have to check there is no PATH_SEP
// in string
if (ipat >= lpat)
{
final int sistr = istr;
while (istr < lstr && (astr[istr] != PATHSEP))
{
istr++;
}
if (istr >= lstr)
{
add(new String(astr, sistr, lstr - sistr));
return true;
}
// otherwise we do not match
return false;
}
// Now we need to search for the start of either a path separator or another
// wildcard characters
// in the pattern
final int sipat = ipat;
while (ipat < lpat && apat[ipat] != STAR &&
(apat[ipat] != ESC || ipat < lpat - 1 && apat[ipat + 1] != STAR) &&
apat[ipat] != PATHSEP)
{
ipat++;
}
// if we reached the end of the pattern just do a string compare with the
// corresponding part from
// the end of the string
if (ipat >= lpat)
{
return checkEnds(sipat, true);
}
// If we stopped at an other wildcard
// we exclude it from being compared
if (apat[ipat] == STAR)
{
ipat--;
}
// Now we need to check whether the literal substring of the pattern
// is contained in the string somewhere
final int l = ipat - sipat + 1;
final int sistr = istr;
while (istr < lstr && !strncmp(apat, sipat, astr, istr, l))
{
istr++;
}
if (istr >= lstr)
{
return false;
}
add(new String(astr, sistr, istr - sistr));
ipat++;
istr += l;
}
return match();
}
/**
* Scan a possible common suffix
*/
private final void scanLiteralPrefix()
{
// scan a common literal suffix
while (ipat < lpat &&
istr < lstr &&
(apat[ipat] == ESC && ipat < lpat - 1 && apat[ipat + 1] == STAR &&
apat[++ipat] == astr[istr] || apat[ipat] != STAR && apat[ipat] == astr[istr]))
{
ipat++;
istr++;
}
}
/**
* Compare two character array from individual offsets
*
* @param a1
* The first character array
* @param o1
* The offset into the first character array
* @param a2
* The second character array
* @param o2
* The offset into the second character array
* @param l
* The length to compare
*
* @return Whether the all the mentioned characters match each other
*/
private final boolean strncmp(final char[] a1, final int o1, final char[] a2, final int o2,
final int l)
{
int i = 0;
while (i < l && o1 + i < a1.length && o2 + i < a2.length && a1[o1 + i] == a2[o2 + i])
{
i++;
}
return i == l;
}
private final boolean checkEnds(final int sipat, final boolean isSingleStart)
{
// if the remaining length of the string isn't the same as that found in the pattern
// we do not match
final int l = lpat - sipat; // calculate length of comparison
final int ostr = lstr - l; // calculate offset into string
if (ostr >= 0 && strncmp(apat, sipat, astr, ostr, l))
{
if (isSingleStart)
{
// if the ends matches make sure there isn't a PATHSEP in the candidate string
// part
int i = ostr - istr;
while (i > istr)
{
if (astr[--i] == PATHSEP)
{
return false; // we cannot match because of a PATHSEP in the matched
// part
}
}
}
add(new String(astr, istr, ostr - istr));
return true;
}
// otherwise we do not match
return false;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy