001: /*
002: ** Copyright (c) 1998 by Timothy Gerard Endres
003: **
004: ** This program is free software.
005: **
006: ** You may redistribute it and/or modify it under the terms of the GNU
007: ** General Public License as published by the Free Software Foundation.
008: ** Version 2 of the license should be included with this distribution in
009: ** the file LICENSE, as well as License.html. If the license is not
010: ** included with this distribution, you may find a copy at the FSF web
011: ** site at 'www.gnu.org' or 'www.fsf.org', or you may write to the
012: ** Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139 USA.
013: **
014: ** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
015: ** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
016: ** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
017: ** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
018: ** REDISTRIBUTION OF THIS SOFTWARE.
019: **
020: */
021:
022: package com.ice.util;
023:
024: /**
025: * The Globber class provides filename globbing.
026: * Globber can be used for matching files against a
027: * <em>globbing expression</em>. It can also be used
028: * for very simply string matching.
029: *
030: * For instance, "*.class" would match all class files
031: * and "*.java" would match all Java source files. You
032: * could get text files numbered between 10 and 99 with
033: * "??.txt" or more specifically with "[0-9][0-9].txt".
034: *
035: * Recognized wildcards:
036: * <ul>
037: * <li><strong>*</strong> - Matches any number of any characters
038: * <li><strong>?</strong> - Matches one of any characters
039: * <li><strong>[]</strong> - Matches any of enclosed characters,
040: * ranges (e.g., [a-z]) are supported.
041: * </ul>
042: *
043: * @version $Revision: 1.2 $
044: * @author Timothy Gerard Endres,
045: * <a href="mailto:time@ice.com">time@org.gjt</a>.
046: *
047: */
048:
049: import java.lang.*;
050: import java.io.*;
051: import java.util.*;
052:
053: public class Globber extends Object {
054: static public final String RCS_ID = "$Id: Globber.java,v 1.2 1999/03/13 01:23:32 time Exp $";
055: static public final String RCS_REV = "$Revision: 1.2 $";
056:
057: private Vector specs;
058:
059: /**
060: * Constructs a new Glob object, with no match list.
061: *
062: * @param default_spec The default match specs.
063: */
064: public Globber() {
065: super ();
066: this .specs = null;
067: }
068:
069: /**
070: * Constructs a new Glob object, setting the
071: * match list to that specified by the parameter.
072: *
073: * @param default_spec The default match specs.
074: */
075: public Globber(String default_spec) {
076: super ();
077: this .specs = null;
078: this .setMatchSpec(default_spec);
079: }
080:
081: public int size() {
082: return this .specs.size();
083: }
084:
085: /**
086: * Adds the glob matching specifications to the current list
087: * of matching specifications.
088: *
089: * @param spec The string listing the specs to add.
090: */
091: public void addMatchSpec(String spec) {
092: String toke;
093: int i, count;
094:
095: if (this .specs == null) {
096: this .specs = new Vector();
097: }
098:
099: StringTokenizer toker = new StringTokenizer(spec);
100:
101: count = toker.countTokens();
102:
103: for (i = 0; i < count; ++i) {
104: try {
105: toke = toker.nextToken();
106: } catch (NoSuchElementException ex) {
107: break;
108: }
109:
110: if (toke.equals("!")) {
111: this .specs = new Vector();
112: } else {
113: this .specs.addElement(toke);
114: }
115: }
116: }
117:
118: /**
119: * Adds the glob matching specifications from the file
120: * to the current list of matching specifications.
121: *
122: * @param ignoreFile The file containing the ignore specs.
123: */
124: public void addMatchFile(File matchFile) {
125: String line;
126: boolean ok = true;
127: BufferedReader in = null;
128:
129: if (this .specs == null) {
130: this .specs = new Vector();
131: }
132:
133: try {
134: in = new BufferedReader(new FileReader(matchFile));
135: } catch (IOException ex) {
136: in = null;
137: ok = false;
138: }
139:
140: for (; ok;) {
141: try {
142: line = in.readLine();
143: } catch (IOException ex) {
144: line = null;
145: }
146:
147: if (line == null)
148: break;
149:
150: this .addMatchSpec(line);
151: }
152:
153: if (in != null) {
154: try {
155: in.close();
156: } catch (IOException ex) {
157: }
158: }
159: }
160:
161: /**
162: * Replaces all current match specs with those passed in.
163: *
164: * @param spec The string listing the specs to replace with.
165: */
166: public void setMatchSpec(String spec) {
167: if (this .specs != null) {
168: this .specs.removeAllElements();
169: }
170:
171: this .addMatchSpec(spec);
172: }
173:
174: /**
175: * Determines if a file is to be matched.
176: *
177: * @param name The name of the file to check.
178: * @return If the file is to be ignored, true, else false.
179: */
180: public boolean isFileMatched(String name) {
181: if (this .specs == null || this .specs.size() == 0)
182: return false;
183:
184: for (int i = 0; i < this .specs.size(); ++i) {
185: String spec = (String) this .specs.elementAt(i);
186: if (fileMatchesExpr(name, spec)) {
187: return true;
188: }
189: }
190:
191: return false;
192: }
193:
194: /**
195: * Determines if a filename matches an expression.
196: *
197: * @param fileName The name of the file to check.
198: * @param matchExpr The expression to check against.
199: * @return If the file name matches the expression, true, else false.
200: */
201: private boolean fileMatchesExpr(String fileName, String matchExpr) {
202: return this .matchExprRecursor(fileName, matchExpr, 0, 0);
203: }
204:
205: /**
206: * An internal routine to implement expression matching.
207: * This routine is based on a self-recursive algorithm.
208: *
209: * @param string The string to be compared.
210: * @param pattern The expression to compare <em>string</em> to.
211: * @param sIdx The index of where we are in <em>string</em>.
212: * @param pIdx The index of where we are in <em>pattern</em>.
213: * @return True if <em>string</em> matched pattern, else false.
214: */
215: private boolean matchExprRecursor(String string, String pattern,
216: int sIdx, int pIdx) {
217: int pLen = pattern.length();
218: int sLen = string.length();
219:
220: for (;;) {
221:
222: if (pIdx >= pLen) {
223: if (sIdx >= sLen)
224: return true;
225: else
226: return false;
227: }
228:
229: if (sIdx >= sLen && pattern.charAt(pIdx) != '*') {
230: return false;
231: }
232:
233: // Check for a '*' as the next pattern char.
234: // This is handled by a recursive call for
235: // each postfix of the name.
236: if (pattern.charAt(pIdx) == '*') {
237: if (++pIdx >= pLen)
238: return true;
239:
240: for (;;) {
241: if (this .matchExprRecursor(string, pattern, sIdx,
242: pIdx))
243: return true;
244:
245: if (sIdx >= sLen)
246: return false;
247:
248: ++sIdx;
249: }
250: }
251:
252: // Check for '?' as the next pattern char.
253: // This matches the current character.
254: if (pattern.charAt(pIdx) == '?') {
255: ++pIdx;
256: ++sIdx;
257: continue;
258: }
259:
260: // Check for '[' as the next pattern char.
261: // This is a list of acceptable characters,
262: // which can include character ranges.
263: if (pattern.charAt(pIdx) == '[') {
264: for (++pIdx;; ++pIdx) {
265: if (pIdx >= pLen || pattern.charAt(pIdx) == ']')
266: return false;
267:
268: if (pattern.charAt(pIdx) == string.charAt(sIdx))
269: break;
270:
271: if (pIdx < (pLen - 1)
272: && pattern.charAt(pIdx + 1) == '-') {
273: if (pIdx >= (pLen - 2))
274: return false;
275:
276: char chStr = string.charAt(sIdx);
277: char chPtn = pattern.charAt(pIdx);
278: char chPtn2 = pattern.charAt(pIdx + 2);
279:
280: if ((chPtn <= chStr) && (chPtn2 >= chStr))
281: break;
282:
283: if ((chPtn >= chStr) && (chPtn2 <= chStr))
284: break;
285:
286: pIdx += 2;
287: }
288: }
289:
290: for (; pattern.charAt(pIdx) != ']'; ++pIdx) {
291: if (pIdx >= pLen) {
292: --pIdx;
293: break;
294: }
295: }
296:
297: ++pIdx;
298: ++sIdx;
299: continue;
300: }
301:
302: // Check for backslash escapes
303: // We just skip over them to match the next char.
304: if (pattern.charAt(pIdx) == '\\') {
305: if (++pIdx >= pLen)
306: return false;
307: }
308:
309: if (pIdx < pLen && sIdx < sLen)
310: if (pattern.charAt(pIdx) != string.charAt(sIdx))
311: return false;
312:
313: ++pIdx;
314: ++sIdx;
315: }
316: }
317:
318: public void dumpMatchSpecs(String message) {
319: this .dumpMatchSpecs(new PrintWriter(new OutputStreamWriter(
320: System.err)), message);
321: }
322:
323: public void dumpMatchSpecs(PrintWriter out, String message) {
324: if (message != null)
325: out.println(message);
326:
327: if (this .specs == null) {
328: out.println(" No match specifications.");
329: return;
330: }
331:
332: for (int i = 0; i < this .specs.size(); ++i) {
333: String spec = (String) this .specs.elementAt(i);
334:
335: out.println(" [" + i + "] '" + spec + "'");
336: }
337: }
338: }
|