//============================================================================== // FilenamePattern.java //============================================================================== package tribble.util; import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; import java.lang.Character; import java.lang.Cloneable; import java.lang.Exception; import java.lang.IllegalArgumentException; import java.lang.NullPointerException; import java.lang.String; import java.lang.System; /******************************************************************************* * Filename pattern matcher. * *

* A filename pattern is a kind of regular expression that is used * for matching filenames. * *

* The {@link #matches(String) matches()} method is used to match a pattern * against a filename. A static {@link #matches(String, String) matches()} * method is also provided for convenience. * *

* Methods {@link #accept(File) accept(File)} and * {@link #accept(File, String) accept(File, String)} are also provided, which * implement the java.io.FileFilter and java.io.FilenameFilter * interfaces, respectively. * * *

*


* *

* Filename Pattern Syntax * *

* A filename pattern is composed of regular characters and * special pattern matching characters, which are: * *

*
*
*

*

? *
Matches any single character. * *

*

* *
Matches zero or more characters. * *

*

[abc] *
Matches a single character from character set * {a,b,c}. * *

*

[a-b] *
Matches a single character from the character range * {a...b}. Note that character a must be * lexicographically less than or equal to character b. * *

*

[^a] *
Matches a single character that is not from character set or range * {a}. Note that the ^ character must occur * immediately to the right of the opening bracket. * *

*

/ *
Matches one or more directory path separator characters. * This is sensitive to the underlying operating system, so that this will * match the native directory path separator character for underlying system; * for example, it will match '/' on Unix systems and match both * '/' and '\' on MS-Windows systems. This makes it * possible to use the same filename pattern on any operating system. * *

*

!pat *
Negates the sense of the rest of pattern pat. * For example, pattern "a*" matches filename "abc", but * pattern "!a*" does not. * *

*

\c *
Removes (escapes) any special meaning of character c. * *
*
*
* *

* The special pattern matching characters listed above are the default settings * for a given filename pattern matcher object, but these can be changed by * calling method {@link #setSpecialChar setSpecialChar()}. * *

* By default, a filename pattern matcher performs case-sensitive matching * depending on the underlying operating system (e.g., matching is case-sensitive * for Unix, but case-insensitive for MS/Windows). This behavior can be changed * by calling method {@link #setIgnoreCase setIgnoreCase()}. * *

* An empty string ("") is not a valid filename pattern. * *

* No filename pattern matches an empty filename (""). * * *

*


* *

* Examples * *

*

*
*
*
abc *
Matches "abc". * If case-insensitive matching is enabled, it also matches * "Abc", "ABC", "aBc", etc. * *
a?c *
Matches "abc", "a2c", "a.c", etc. * *
a* *
Matches "a", "abc", "a3.p.txt", etc. * *
a.* *
Matches "a.", "a.txt", "a.old.java", etc. * *
a*x *
Matches "ax", "ab37x", "a.txt.x", etc. * *
*a* *
Matches "a", "ax", "claw", "bra", * etc. * *
a.[ch] *
Matches "a.c" and "a.h". * *
a.[ch]?? *
Matches "a.cpp", "a.hxx", "a.hlp", etc. * *
a.[d-fm] *
Matches "a.d", "a.e", "a.f", and * "a.m". * *
a.[^a-cg-z0-9] *
Matches "a.d", "a.e", "a.f", etc. * *
[mb][ey]* *
Matches "me", "mybook", "be.o", * "bean.cnt", "byebye", etc. * *
*.!c *
Matches "foo.h", "a.java", etc., but * not "foo.c", "a.c", etc. * *
a*!*x *
Matches "aye", "axe7", "a.txt", etc., but * not "bake", "ax", "abo.x", etc. * *
src/*.? *
Matches "src/foo.c", "src/x.h", "src//b.o", * etc. * *
*/*.txt *
Matches "my/foo.txt", "/x.txt", "a//b.txt", * etc. * *
/*/*.txt *
Matches "/my/foo.txt", "//a//b.txt", etc. * *
!* *
Degenerate case that does not match any filename. * *
*
*
* * *

*


* *

* References * *

* This pattern matching implementation is based on the filename pattern matching * of Unix (POSIX). * *

* This pattern matching implementation is not based on the regular * expression matching capabilities provided by the Java standard library * (in JRE 1.4+, see java.lang.String.matches() for details). * * *

*


* *

* Future Enhancements * *

* A future revision of this code may support a special pattern of "**", * which matches zero or more directory path prefixes. * * *

*
Source code:
*
* http://david.tribble.com/src/java/tribble/util/FilenamePattern.java *
*
Documentation:
*
* http://david.tribble.com/docs/tribble/util/FilenamePattern.html *
*
* * * @version $Revision: 1.9 $ $Date: 2008/10/10 21:53:21 $ * @since 2003-02-09 * @author David R. Tribble (david@tribble.com). *
* Copyright ©2003-2008 by David R. Tribble, all rights reserved.
* Permission is granted to any person or entity except those designated by * by the United States Department of State as a terrorist, or terrorist * government or agency, to use and distribute this source code provided * that the original copyright notice remains present and unaltered. */ public class FilenamePattern implements java.lang.Cloneable, java.io.FileFilter, java.io.FilenameFilter { static final String REV = "@(#)tribble/util/FilenamePattern.java $Revision: 1.9 $ $Date: 2008/10/10 21:53:21 $\n"; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constants //---------------------------------- // Default special filename pattern matching characters /** Default pattern character: Escape any special meaning. */ public static final char PAT_ESCAPE = '\\'; /** Default pattern character: Any single character. */ public static final char PAT_ANY = '?'; /** Default pattern character: Zero or more characters. */ public static final char PAT_CLOSURE = '*'; /** Default pattern character: Character set open. */ public static final char PAT_SET_OPEN = '['; /** Default pattern character: Character set close. */ public static final char PAT_SET_CLOSE = ']'; /** Default pattern character: Character set range. */ public static final char PAT_SET_THRU = '-'; /** Default pattern character: Character set exclusion. */ public static final char PAT_SET_EXCL = '^'; /** Default pattern character: Directory path separator. */ public static final char PAT_DIR_SEP = '/'; /** Default pattern character: Negation. */ public static final char PAT_NEGATE = '!'; //---------------------------------- // Other constants /** System-dependent directory path separator character (1). */ protected static final char DIR_SEP1; /** System-dependent directory path separator character (2). */ protected static final char DIR_SEP2; /** System-dependent filename case-sensitivity flag. */ protected static final boolean IGNORE_CASE; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Static class initializers /*************************************************************************** * Static class initializer. * *

* This block is responsible for initializing the {@link #DIR_SEP1}, * {@link #DIR_SEP2}, and {@link #IGNORE_CASE} constants. * *

* Note that this code is written to handle Unix and MS/Windows systems. * It may need to be modified to handle other operating systems. * * @since 1.1, 2003-02-09 */ static { char sep; // Initialize DIR_SEP1 and DIR_SEP2 sep = File.separatorChar; DIR_SEP1 = sep; if (sep == '\\') { DIR_SEP2 = '/'; IGNORE_CASE = true; } else if (sep == '/') { DIR_SEP2 = sep; IGNORE_CASE = false; } else { DIR_SEP2 = sep; IGNORE_CASE = true; } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Static methods /*************************************************************************** * Determine if a filename matches a filename pattern. * *

* Note * *

* The filename is matched against a filename pattern matcher that uses the * default settings for its special pattern matching characters. * * @param pat * A filename pattern string. * * @param fname * A filename to compare against pat. * Note that empty filenames ("") are not matched by any filename * pattern. * * @return * True if fname matches pat, otherwise false. * * @throws IllegalArgumentException (unchecked) * Thrown if pat is malformed. * * @throws NullPointerException (unchecked) * Thrown if pat or fname is null. * * @since 1.1, 2003-02-09 */ public static boolean matches(String pat, String fname) //throws IllegalArgumentException, NullPointerException { // Match a filename against a filename pattern with defaults return (new FilenamePattern(pat)).matches(fname); } /*************************************************************************** * Determine if a character falls within a given character range, ignoring * case. * *

* Note that this method might exhibit slightly different behavior than the * String.compareToIgnoreCase() method for certain regional * characters. * * @param ch * A character to compare. * * @param lo * The lower character value in the range to compare. * * @param hi * The upper character value in the range to compare. * * @return * True if character ch falls within the range [lo,hi], * ignoring case, otherwise false. * * @see #matchSubpattern matchSubpattern() * * @since 1.2, 2003-07-04 */ protected static boolean compareCharIgnoreCase(char ch, char lo, char hi) { char ch2; char lo2; char hi2; // Compare lowercase equivalents ch2 = Character.toLowerCase(ch); lo2 = Character.toLowerCase(lo); hi2 = lo2; if (lo != hi) hi2 = Character.toLowerCase(hi); if (ch2 >= lo2 && ch2 <= hi2) return (true); // Compare uppercase equivalents ch2 = Character.toUpperCase(ch); lo2 = Character.toUpperCase(lo); hi2 = lo2; if (lo != hi) hi2 = Character.toUpperCase(hi); return (ch2 >= lo2 && ch2 <= hi2); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Variables /** * Filename pattern. * This is stored as a character array instead of a String object to * achieve a slight improvement in speed. */ protected char[] m_pat; /** Ignore case when matching filenames. */ protected boolean m_ignoreCase = IGNORE_CASE; /** This pattern contains directory path separator characters. */ protected boolean m_hasDirSep; //---------------------------------- // Special pattern matching characters /** Special pattern character: Escape any special meaning. */ protected char m_escape = PAT_ESCAPE; /** Special pattern character: Match any single character. */ protected char m_any = PAT_ANY; /** Special pattern character: Match zero or more characters. */ protected char m_closure = PAT_CLOSURE; /** Special pattern character: Character set open. */ protected char m_setOpen = PAT_SET_OPEN; /** Special pattern character: Character set close. */ protected char m_setClose = PAT_SET_CLOSE; /** Special pattern character: Character set range. */ protected char m_setThru = PAT_SET_THRU; /** Special pattern character: Character set exclusion. */ protected char m_setExcl = PAT_SET_EXCL; /** Special pattern character: Directory path separator. */ protected char m_dirSep = PAT_DIR_SEP; /** Special pattern character: Negation. */ protected char m_negate = PAT_NEGATE; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors /*************************************************************************** * Default constructor. * * @see #setPattern setPattern() * * @since 1.1, 2003-02-09 */ public FilenamePattern() { // Do nothing } /*************************************************************************** * Constructor. * * @param pat * A filename pattern. * * @throws IllegalArgumentException (unchecked) * Thrown if pat is malformed. * * @throws NullPointerException (unchecked) * Thrown if pat is null. * * @see #setPattern setPattern() * * @since 1.1, 2003-02-09 */ public FilenamePattern(String pat) //throws IllegalArgumentException, NullPointerException { // Establish the filename pattern setPattern(pat); } /*************************************************************************** * Constructor. * * @param pat * A filename pattern. * * @param ignCase * If true, case is ignored when matching alphabetic characters in filenames * (e.g, the names "abc", "Abc", and "ABC" all * compare equal); if false, filename comparisons are case-sensitive and * alphabetic characters must match exactly. * * @throws IllegalArgumentException (unchecked) * Thrown if pat is malformed. * * @throws NullPointerException (unchecked) * Thrown if pat is null. * * @see #setPattern setPattern() * @see #setIgnoreCase setIgnoreCase() * * @since 1.6, 2004-10-19 */ public FilenamePattern(String pat, boolean ignCase) //throws IllegalArgumentException, NullPointerException { // Establish the filename pattern setIgnoreCase(ignCase); setPattern(pat); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Methods /*************************************************************************** * Retrieve the pattern for the filename pattern matcher. * * @return * The filename pattern for this pattern matcher. * Note that this will be null if the pattern has not been established yet. * * @see #setPattern setPattern() * * @since 1.7, 2004-10-21 */ public String getPattern() { // Get the pattern if (m_pat == null) return (null); return (new String(m_pat)); } /*************************************************************************** * Establish the pattern for the filename pattern matcher. * *

* This method is provided so that this filename pattern matcher can be * reused with different patterns. * * @param pat * A filename pattern. * This is verified to be a well-formed pattern, using the current special * character settings of the filename pattern. * * @throws IllegalArgumentException (unchecked) * Thrown if pat is malformed. * * @throws NullPointerException (unchecked) * Thrown if pat is null. * * @see #getPattern getPattern() * * @since 1.1, 2003-02-09 */ public void setPattern(String pat) //throws IllegalArgumentException, NullPointerException { // Validate the filename pattern if (pat == null) throw new NullPointerException("Null pattern"); if (!isValidPattern(pat)) throw new IllegalArgumentException("Malformed pattern: \"" + pat + "\""); // Set the filename pattern m_pat = pat.toCharArray(); // Check for directory path separator characters in the pattern m_hasDirSep = false; if (m_dirSep != '\0') { int len; len = m_pat.length; for (int i = 0; i < len; i++) { char ch; if (m_pat[i] == '\0') ; // NULs are allowed else if (m_pat[i] == m_escape) i++; else if (m_pat[i] == m_dirSep) { m_hasDirSep = true; break; } } } } /*************************************************************************** * Modify a special pattern matching character for the filename pattern * matcher. * *

* This method should be called prior to calling * {@link #setPattern setPattern()}. * * @param type * The type of the special pattern matching character to be modified, which * is one of the {@link #PAT_ESCAPE PAT_XXX} constants. * * @param ch * The character to use as the special pattern matching character. * If this character is '\0' (NUL), the pattern matcher * will not recognize any character as the special pattern matching character * of type type, effectively disabling that kind of pattern * matching. * * @return * The previous value for the special pattern matching character. * * @throws IllegalArgumentException (unchecked) * Thrown if type is not a valid special pattern character type. * * @since 1.1, 2003-02-09 */ public char setSpecialChar(int type, char ch) //throws IllegalArgumentException { char prev; // Set the appropriate special pattern matching character switch (type) { case PAT_ESCAPE: prev = m_escape; m_escape = ch; return (prev); case PAT_ANY: prev = m_any; m_any = ch; return (prev); case PAT_CLOSURE: prev = m_closure; m_closure = ch; return (prev); case PAT_SET_OPEN: prev = m_setOpen; m_setOpen = ch; return (prev); case PAT_SET_CLOSE: prev = m_setClose; m_setClose = ch; return (prev); case PAT_SET_THRU: prev = m_setThru; m_setThru = ch; return (prev); case PAT_SET_EXCL: prev = m_setExcl; m_setExcl = ch; return (prev); case PAT_DIR_SEP: prev = m_dirSep; m_dirSep = ch; return (prev); case PAT_NEGATE: prev = m_negate; m_negate = ch; return (prev); default: // Invalid special pattern character type throw new IllegalArgumentException("Invalid special pattern " + "character type: '" + ((char) type) + "'"); } } /*************************************************************************** * Establish whether or not the filename pattern matcher ignores case. * (By default, the case-sensitivity of a pattern matcher is dependent upon * the underlying operating system.) * * @param flag * If true, upper and lower case letters are considered to be identical when * matching filenames (e.g., filenames "abc", "Abc", and * "ABC" will all be considered to be the same filename). * If false, the pattern matcher is case-sensitive, and equivalent upper and * lower case letters are considered to be different. * * @return * The previous setting for this pattern matcher. * * @since 1.1, 2003-02-09 */ public boolean setIgnoreCase(boolean flag) { boolean prev; // Establish the control setting prev = m_ignoreCase; m_ignoreCase = flag; return (prev); } /*************************************************************************** * Determine if a filename matches the filename pattern. * * @param fname * A filename to match against the filename pattern. * Note that empty filenames ("") are not matched by any filename * pattern. * * @return * True if the pattern matches fname, otherwise false. * * @throws NullPointerException (unchecked) * Thrown if fname is null, or if this pattern matcher does not have * a filename pattern established. * * @since 1.1, 2003-02-09 */ public boolean matches(String fname) //throws IllegalArgumentException, NullPointerException { // Check args if (m_pat == null) throw new NullPointerException("Null pattern"); if (fname == null) throw new NullPointerException("Null filename"); if (fname.length() == 0) return (false); // Attempt to match the filename against the pattern return (matchSubpattern(0, fname, 0) >= 0); } /*************************************************************************** * Determine if a filename matches the filename pattern. * *

* If this filename pattern matcher contains directory separator characters, * it attempts to match the full pathname formed by concatenating the * directory name dir with the filename fname. Otherwise * it attempts to match only the filename, ignoring the directory name. * * @param dir * The pathname of the directory containing fname. * (This pattern matcher implementation ignores this argument.) * * @param fname * A filename to match against the pattern. * * @return * True if the filename matches the pattern, otherwise false. * * @since 1.1, 2003-02-09 */ public boolean accept(File dir, String fname) //implements java.io.FilenameFilter { // Attempt to match the filename if (m_hasDirSep) return (matches(dir.getPath() + DIR_SEP1 + fname)); else return (matches(fname)); } /*************************************************************************** * Determine if a filename matches the filename pattern. * *

* If this filename pattern matcher contains directory separator characters, * it attempts to match the entire pathname of the given filename. Otherwise * it attempts to match only the filename portion, ignoring the directory * prefix. * * @param fname * A filename to match against the pattern. * * @return * True if the filename matches the pattern, otherwise false. * * @since 1.1, 2003-02-09 */ public boolean accept(File fname) //implements java.io.FileFilter { // Attempt to match the filename if (m_hasDirSep) return (matches(fname.getPath())); else return (matches(fname.getName())); } /*************************************************************************** * Compare this object to another. * *

* Note *

* Two filename pattern matchers are considered the same (equal) if they have * identical internal patterns and identical control settings. * * @param obj * Another object to compare this one to. * If obj is not a {@link FilenamePattern} or one of its subtypes, * it cannot be equal to this object. * * @return * True if this object is identical to obj, otherwise false. * * @since 1.7, 2004-10-21 */ public boolean equals(Object obj) //overrides java.lang.Object { FilenamePattern that; // Compare this object to 'that' if (!(obj instanceof FilenamePattern)) return (false); that = (FilenamePattern) obj; // Equal pattern matchers have the same internal pattern and settings if (m_pat != null) { int len; len = this.m_pat.length; if (len != that.m_pat.length) return (false); for (int i = 0; i < len; i++) if (this.m_pat[i] != that.m_pat[i]) return (false); } if (this.m_ignoreCase != that.m_ignoreCase) return (false); if (this.m_hasDirSep != that.m_hasDirSep) return (false); if (this.m_escape != that.m_escape) return (false); if (this.m_any != that.m_any) return (false); if (this.m_closure != that.m_closure) return (false); if (this.m_setOpen != that.m_setOpen) return (false); if (this.m_setClose != that.m_setClose) return (false); if (this.m_setThru != that.m_setThru) return (false); if (this.m_setExcl != that.m_setExcl) return (false); if (this.m_dirSep != that.m_dirSep) return (false); if (this.m_negate != that.m_negate) return (false); // The pattern matcher objects are identical return (true); } /*************************************************************************** * Calculate a hash code for this object. * *

* Note *

* Two filename pattern matchers are considered the same (equal) if they have * identical internal patterns and identical control settings. * * @return * A hash code for this object. * * @since 1.7, 2004-10-21 */ public int hashCode() //overrides java.lang.Object { int h = 0x0000; // Compute the hash code if (m_pat != null) { for (int i = m_pat.length-1; i >= 0; i--) h = ((h << 3) | (h >>> 32-3)) ^ m_pat[i]; } h = ((h << 1) | (h >>> 32-1)) ^ (m_ignoreCase ? 0x0001 : 0x0000); h = ((h << 1) | (h >>> 32-1)) ^ (m_hasDirSep ? 0x0001 : 0x0000); h = ((h << 1) | (h >>> 32-1)) ^ m_escape; h = ((h << 1) | (h >>> 32-1)) ^ m_any; h = ((h << 1) | (h >>> 32-1)) ^ m_closure; h = ((h << 1) | (h >>> 32-1)) ^ m_setOpen; h = ((h << 1) | (h >>> 32-1)) ^ m_setClose; h = ((h << 1) | (h >>> 32-1)) ^ m_setThru; h = ((h << 1) | (h >>> 32-1)) ^ m_setExcl; h = ((h << 1) | (h >>> 32-1)) ^ m_dirSep; h = ((h << 1) | (h >>> 32-1)) ^ m_negate; return (h); } /*************************************************************************** * Clone this filename pattern matcher. * *

* Note *

* The internal pattern is shared by both this pattern matcher and the newly * created clone object. If the internal pattern is to be modified by a * method in a subclass of this class, this method must be overridden so that * the clone gets its own copy of the internal pattern which it can then * freely modify. (Calling method {@link #setPattern setPattern()} endows a * pattern matcher with an entirely new internal pattern, of course). * * @return * A filename pattern matcher with settings identical to this one. * * @since 1.7, 2004-10-21 */ public Object clone() //implements java.lang.Cloneable //overrides java.lang.Object { FilenamePattern copy; // Create a clone of this pattern matcher copy = new FilenamePattern(); // Note: 'm_pat[]' is shared among cloned objects copy.m_pat = this.m_pat; copy.m_ignoreCase = this.m_ignoreCase; copy.m_hasDirSep = this.m_hasDirSep; copy.m_escape = this.m_escape; copy.m_any = this.m_any; copy.m_closure = this.m_closure; copy.m_setOpen = this.m_setOpen; copy.m_setClose = this.m_setClose; copy.m_setThru = this.m_setThru; copy.m_setExcl = this.m_setExcl; copy.m_dirSep = this.m_dirSep; copy.m_negate = this.m_negate; return (copy); } /*************************************************************************** * Determine if a filename pattern is well-formed. * The current special character settings of the filename pattern are used. * * @param pat * A filename pattern to verify. * * @return * True if pat is well-formed, otherwise false. * * @see #setSpecialChar setSpecialChar() * * @since 1.1, 2003-02-09 */ protected boolean isValidPattern(String pat) { int len; int setOpen; boolean setRange; // Validate the pattern len = pat.length(); if (len == 0) return (false); setOpen = 0; setRange = false; for (int i = 0; i < len; i++) { char pCh; // Examine a single pattern character pCh = pat.charAt(i); if (pCh == '\0') { // Special case, which must be first if any of the 'm_xxx' // special pattern matching characters are '\0' (disabled) } else if (pCh == m_negate && setOpen == 0) { // Ensure there is a negated subpattern present if (i+1 >= len) return (false); } else if (pCh == m_escape) { // Ensure there is an escaped character present i++; if (i >= len) return (false); setRange = false; } else if (pCh == m_setOpen && setOpen == 0) { // Start of a character set setOpen++; } else if (pCh == m_setExcl && setOpen > 0) { // Character set exclusion } else if (pCh == m_setThru && setOpen > 0) { // Character set range setRange = true; } else if (pCh == m_setClose && setRange) { // Incomplete character set range return (false); } else if (pCh == m_setClose && setOpen > 0) { // End of a character set if (setOpen < 2) return (false); setOpen = 0; } else if (setOpen > 0) { // Normal character, or the end of a character set range setOpen++; setRange = false; } } // Check for a well-formed pattern if (setOpen > 0 || setRange) { // Incomplete character set or character range return (false); } // The filename pattern is well-formed return (true); } /*************************************************************************** * Determine if a filename substring matches a pattern substring. * *

* Note * *

* The pattern string ({@link #m_pat}) should be well-formed, otherwise the * pattern matching algorithm will become confused. * * @param patOff * The index of the first (leftmost) character within the pattern to match. * * @param fname * A filename string to compare against the pattern. * Note that empty filenames ("") are not matched by any filename * pattern. * * @param fnameOff * The index of the first (leftmost) character within string fname * to match. * * @return * The index of one character past the last (rightmost) character within * filename string fname that matches the pattern, or a negative * value if the filename substring does not match the pattern substring. * * @see #matches(String) matches() * @see #compareCharIgnoreCase compareCharIgnoreCase() * * @since 1.1, 2003-02-09 */ protected int matchSubpattern(int patOff, String fname, int fnameOff) { int patLen; int fnameLen; int pi; int fi; // Attempt to match the string, one pattern character at a time patLen = m_pat.length; fnameLen = fname.length(); for (pi = patOff, fi = fnameOff; pi < patLen; pi++) { char pCh; int fCh; // Examine the next character of the filename fCh = -1; if (fi < fnameLen) fCh = fname.charAt(fi); // Examine the next character of the filename pattern pCh = m_pat[pi]; if (pCh == '\0') { // Special case, which must be first if any of the 'm_xxx' // special pattern matching characters are '\0' (disabled) if (fCh != '\0') return (-fi-1); // Submatch succeeded fi++; } else if (pCh == m_closure) { int j; if (m_dirSep != '\0') { // Match only up to the next directory path separator for (j = fi; j < fnameLen; j++) { char ch; ch = fname.charAt(j); if (ch == DIR_SEP1 || ch == DIR_SEP2) break; } } else j = fnameLen; if (j == fnameOff) return (-fi-1); // Match zero or more filename characters for ( ; j >= fi; j--) { // Assume a match of substring 'fname[fi...j-1]', // attempt to match the rest of the pattern if (matchSubpattern(pi+1, fname, j) >= 0) { // Successful submatch return (j); } } // Submatch failed return (-fi-1); } else if (pCh == m_any) { // Match any single character if (fCh < 0) return (-fi-1); // Submatch succeeded fi++; } else if (pCh == m_escape) { // Escape the next pattern character if (pi+1 < patLen) pCh = m_pat[++pi]; if (pCh != fCh) { if (m_ignoreCase) { if (!compareCharIgnoreCase((char) fCh, pCh, pCh)) return (-fi-1); } else return (-fi-1); } // Submatch succeeded fi++; } else if (pCh == m_setOpen) { boolean sense; boolean found; // Match a single character from a character set if (fCh < 0) return (-fi-1); // Check for character set exclusion pi++; if (pi < patLen) pCh = m_pat[pi]; sense = true; if (pCh == m_setExcl) { sense = false; pi++; } // Attempt to match a single character to the character set found = !sense; while (pi < patLen) { char loCh; // Get the next character from the character set pCh = m_pat[pi]; if (pCh == m_setClose) break; loCh = pCh; pi++; if (pCh == m_escape) { // Escape the special meaning of the set character if (pi >= patLen) return (-fi-1); pCh = m_pat[pi++]; loCh = pCh; } else if (pi < patLen && m_pat[pi] == m_setThru) { // Set specifies a range of characters pi++; if (pi >= patLen) return (-fi-1); pCh = m_pat[pi++]; if (pCh == m_escape) { // Escape the special meaning of the set character if (pi >= patLen) return (-fi-1); pCh = m_pat[pi++]; } else if (pCh == m_setClose) return (-fi-1); } // Compare the filename character against the // character range set [loCh,pCh] if (m_ignoreCase) { if (compareCharIgnoreCase((char) fCh, loCh, pCh)) found = sense; } else if (loCh <= fCh && fCh <= pCh) found = sense; } // Check for a match if (!found) return (-fi-1); // Submatch succeeded fi++; } else if (pCh == m_dirSep) { // Match a single directory path separator character if (fCh < 0) return (-fi-1); if (fCh != DIR_SEP1 && fCh != DIR_SEP2) return (-fi-1); // Submatch succeeded fi++; if (pi+1 < patLen && m_pat[pi+1] != m_dirSep || pi+1 == patLen) { // Match multiple consecutive path separators while (fi < fnameLen) { fCh = fname.charAt(fi); if (fCh != DIR_SEP1 && fCh != DIR_SEP2) break; fi++; } } if (pi+1 < patLen) { // Recursively match the rest of the pattern if (matchSubpattern(pi+1, fname, fi) >= fi) return (fi); // Recursive match failed return (-fi-1); } } else if (pCh == m_negate) { int j; // Negate the sense of the rest of the subpattern match j = matchSubpattern(pi+1, fname, fi); if (j >= 0) { // Successful submatch, so this match failed return (-fi-1); } // Submatch failed, so this match succeeded return (-j-1); } else { // Match a single non-special character exactly if (fCh < 0) return (-fi-1); if (pCh != fCh) { if (m_ignoreCase) { if (!compareCharIgnoreCase((char) fCh, pCh, pCh)) return (-fi-1); } else return (-fi-1); } // Submatch succeeded fi++; } } // Match the end of the string if (fi == fnameLen) return (fi); // Pattern match failed return (-fi-1); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Inner classes /*************************************************************************** * Test driver class for class {@link FilenamePattern}. * * *

*
Source code:
*
* http://david.tribble.com/src/java/tribble/util/FilenamePattern.java *
*
Documentation:
*
* http://david.tribble.com/docs/tribble/util/FilenamePattern.html *
*
* * * @version $Revision: 1.9 $ $Date: 2008/10/10 21:53:21 $ * @since {@link FilenamePattern} 1.9, 2008-10-10 * @author David R. Tribble (david@tribble.com). *
* Copyright ©2003-2008 by David R. Tribble, all rights reserved.
* Permission is granted to any person or entity except those designated by * by the United States Department of State as a terrorist, or terrorist * government or agency, to use and distribute this source code provided * that the original copyright notice remains present and unaltered. */ static abstract class Test { /*********************************************************************** * Test driver for this class. * *

* Usage *
* * java tribble.util.FilenamePattern$Test pattern filename result * * * Where pattern is a filename pattern, * filename is a filename, * and result is the expected result, which is one of: *

*
t - True
*
f - False
*
m - Malformed pattern * (java.lang.IllegalArgumentException)
*
* * @param args * Command line arguments. * * @since 1.9, 2008-10-10 (2003-02-09) */ public static void main(String[] args) { String exp = "?"; // Check args if (args.length < 3) { System.out.println("usage: java " + Test.class.getName() + " pattern filename t|f|m"); System.exit(255); } try { FilenamePattern m; String pat; String fname; boolean res; pat = args[0]; fname = args[1]; exp = args[2]; // Test case m = new FilenamePattern(); System.out.println("pattern: \"" + pat + "\""); System.out.println("filename: \"" + fname + "\""); m.setPattern(pat); res = m.matches(fname); System.out.print("-> " + (res ? "true, " : "false, ")); System.out.println((res && exp.equals("t") ? "pass" : !res && exp.equals("f") ? "pass" : "FAIL ***")); } catch (Exception ex) { System.out.println("Exception: " + ex.getClass().getName()); System.out.println(ex.getMessage()); if (ex instanceof IllegalArgumentException) System.out.println("-> " + (exp.equals("m") ? "pass" : "FAIL ***")); else if (!(ex instanceof IllegalArgumentException)) ex.printStackTrace(System.out); } } } } // End FilenamePattern.java