001: /*
002: * Janino - An embedded Java[TM] compiler
003: *
004: * Copyright (c) 2006, Arno Unkrig
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * 2. Redistributions in binary form must reproduce the above
014: * copyright notice, this list of conditions and the following
015: * disclaimer in the documentation and/or other materials
016: * provided with the distribution.
017: * 3. The name of the author may not be used to endorse or promote
018: * products derived from this software without specific prior
019: * written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
022: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
024: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
025: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
027: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
028: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
029: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
030: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
031: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
032: */
033:
034: package org.codehaus.janino.util;
035:
036: import java.util.*;
037:
038: /**
039: * Implementation of a UNIX shell-like string pattern algorithm.
040: * <p>
041: * Additionally, the concept of the "combined pattern" is supported (see
042: * {@link #matches(StringPattern[], String)}.
043: */
044: public class StringPattern {
045:
046: /**
047: * @see #matches(StringPattern[], String)
048: */
049: public final static int INCLUDE = 0;
050:
051: /**
052: * @see #matches(StringPattern[], String)
053: */
054: public final static int EXCLUDE = 1;
055:
056: private final int mode;
057: private final String pattern;
058:
059: public StringPattern(int mode, String pattern) {
060: this .mode = mode;
061: this .pattern = pattern;
062: }
063:
064: public StringPattern(String pattern) {
065: this .mode = StringPattern.INCLUDE;
066: this .pattern = pattern;
067: }
068:
069: public int getMode() {
070: return this .mode;
071: }
072:
073: /**
074: * Match the given <code>text</code> against the pattern represented by the current instance,
075: * as follows:
076: * <ul>
077: * <li>
078: * A <code>*</code> in the pattern matches any sequence of zero or more characters in the
079: * <code>text</code>
080: * </li>
081: * <li>
082: * A <code>?</code> in the pattern matches exactly one character in the <code>text</code>
083: * </li>
084: * <li>
085: * Any other character in the pattern must appear exactly as it is in the <code>text</code>
086: * </ul>
087: * Notice: The <code>mode</code> flag of the current instance does not take any effect here.
088: */
089: public boolean matches(String text) {
090: return StringPattern.wildmatch(this .pattern, text);
091: }
092:
093: /**
094: * Parse a "combined pattern" into an array of {@link StringPattern}s. A combined pattern
095: * string is structured as follows:
096: * <pre>
097: * combined-pattern :=
098: * [ '+' | '-' ] pattern
099: * { ( '+' | '-' ) pattern }
100: * </pre>
101: * If a pattern is preceeded with a '-', then the {@link StringPattern} is created with mode
102: * {@link #EXCLUDE}, otherwise with mode {@link #INCLUDE}.
103: */
104: public static StringPattern[] parseCombinedPattern(
105: String combinedPattern) {
106: ArrayList al = new ArrayList();
107: for (int k = 0, l; k < combinedPattern.length(); k = l) {
108: int patternMode;
109: char c = combinedPattern.charAt(k);
110: if (c == '+') {
111: patternMode = StringPattern.INCLUDE;
112: ++k;
113: } else if (c == '-') {
114: patternMode = StringPattern.EXCLUDE;
115: ++k;
116: } else {
117: patternMode = StringPattern.INCLUDE;
118: }
119: for (l = k; l < combinedPattern.length(); ++l) {
120: c = combinedPattern.charAt(l);
121: if (c == '+' || c == '-')
122: break;
123: }
124: al.add(new StringPattern(patternMode, combinedPattern
125: .substring(k, l)));
126: }
127: return (StringPattern[]) al
128: .toArray(new StringPattern[al.size()]);
129: }
130:
131: /**
132: * Match a given <code>text</code> against an array of {@link StringPattern}s (which was
133: * typically created by {@link #parseCombinedPattern(String)}.
134: * <p>
135: * The last matching pattern takes effect; if its mode is {@link #INCLUDE}, then
136: * <code>true</code> is returned, if its mode is {@link #EXCLUDE}, then <code>false</code> is
137: * returned.
138: * <p>
139: * If <code>patterns</code> is {@link #PATTERNS_NONE}, or empty, or none of its patterns
140: * matches, then <code>false</code> is returned.
141: * <p>
142: * If <code>patterns</code> is {@link #PATTERNS_ALL}, then <code>true</code> is
143: * returned.
144: * <p>
145: * For backwards compatibility, <code>null</code> patterns are treated like
146: * {@link #PATTERNS_NONE}.
147: */
148: public static boolean matches(StringPattern[] patterns, String text) {
149: if (patterns == null)
150: return false; // Backwards compatibility -- previously, "null" was officially documented.
151:
152: for (int i = patterns.length - 1; i >= 0; --i) {
153: if (patterns[i].matches(text)) {
154: return patterns[i].getMode() == StringPattern.INCLUDE;
155: }
156: }
157: return false; // No patterns defined or no pattern matches.
158: }
159:
160: public static StringPattern[] PATTERNS_ALL = new StringPattern[] { new StringPattern(
161: "*") };
162: public static StringPattern[] PATTERNS_NONE = new StringPattern[0];
163:
164: public String toString() {
165: return (this .mode == StringPattern.INCLUDE ? '+'
166: : this .mode == StringPattern.EXCLUDE ? '-' : '?')
167: + this .pattern;
168: }
169:
170: private static boolean wildmatch(String pattern, String text) {
171: int i;
172: for (i = 0; i < pattern.length(); ++i) {
173: char c = pattern.charAt(i);
174: switch (c) {
175:
176: case '?':
177: if (i == text.length())
178: return false;
179: break;
180:
181: case '*':
182: if (pattern.length() == i + 1)
183: return true; // Optimization for trailing '*'.
184: pattern = pattern.substring(i + 1);
185: for (; i <= text.length(); ++i) {
186: if (StringPattern.wildmatch(pattern, text
187: .substring(i)))
188: return true;
189: }
190: return false;
191:
192: default:
193: if (i == text.length())
194: return false;
195: if (text.charAt(i) != c)
196: return false;
197: break;
198: }
199: }
200: return text.length() == i;
201: }
202: }
|