001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.community;
028:
029: import java.util.ArrayList;
030: import java.util.List;
031: import java.util.StringTokenizer;
032:
033: import javax.naming.NamingException;
034: import javax.naming.directory.Attribute;
035: import javax.naming.directory.Attributes;
036:
037: /**
038: * Parse a RFC 2554 search string. A simple recursive descent parser.
039: **/
040: public class SearchStringParser {
041: private static final String LP = "(";
042: private static final String RP = ")";
043: private static final String AND = "&";
044: private static final String OR = "|";
045: private static final String NOT = "!";
046: private static final String SPACE = " ";
047: private static final String TAB = "\t";
048: private static final String SEPS = LP + RP + AND + OR + NOT;
049:
050: private StringTokenizer tokens;
051: private String token;
052: private String peek; // Lookahead token
053:
054: /**
055: * An exception to throw when a parsing error occurs
056: **/
057: public static class ParseException extends NamingException {
058: public ParseException(String msg) {
059: super (msg);
060: }
061: }
062:
063: /**
064: * Parse a string into a Filter. Creates a StringTokenizer for the
065: * parser methods to use and parses the top level "filter"
066: * expression.
067: * @param s the string to parse
068: * @return a Filter that can be used to test Attributes for a
069: * match
070: * @exception ParseException
071: **/
072: public synchronized Filter parse(String s) throws ParseException {
073: tokens = new StringTokenizer(s, SEPS, true);
074: Filter result = filter();
075: return result;
076: }
077:
078: /**
079: * Get the next token. Has a one token pushback in the peek
080: * variable. Skips whitespace.
081: * @return the next token string
082: * @exception ParseException if there are no more tokens left
083: **/
084: private String getToken() throws ParseException {
085: if (peek != null) {
086: token = peek;
087: peek = null;
088: } else {
089: do {
090: if (!tokens.hasMoreTokens())
091: throw new ParseException("premature end");
092: token = tokens.nextToken();
093: } while (token != null && token.trim().equals(""));
094: }
095: return token;
096: }
097:
098: /**
099: * Get the next token but leave it in the token stream
100: * @return the next token string
101: * @exception ParseException if there are no more tokens left
102: **/
103: private String peekToken() throws ParseException {
104: peek = getToken();
105: return peek;
106: }
107:
108: /**
109: * Verify that the next token is as expected.
110: * @param expected the token that should come next
111: * @exception ParseException if the next token is incorrect
112: **/
113: private void checkToken(String expected) throws ParseException {
114: if (!expected.equals(getToken()))
115: throw new ParseException(expected + " missing");
116: }
117:
118: /**
119: * Parse a "filter", a filtercomp surrounded by parens.
120: * @return the resulting Filter
121: * @exception ParseException
122: **/
123: private Filter filter() throws ParseException {
124: checkToken(LP);
125: Filter result = filtercomp();
126: checkToken(RP);
127: return result;
128: }
129:
130: /**
131: * Parse a "filtercomp" which is either an AND, OR, or NOT
132: * expression or some kind of match expression. All match
133: * expressions have no parens, so the appearance of a paren
134: * constitutes a syntax error
135: * @return the resulting Filter
136: * @exception ParseException
137: **/
138: private Filter filtercomp() throws ParseException {
139: getToken();
140: if (token.equals(AND))
141: return new FilterAnd(filterlist());
142: if (token.equals(OR))
143: return new FilterOr(filterlist());
144: if (token.equals(NOT))
145: return new FilterNot(filter());
146: if (token.equals(LP))
147: throw new ParseException(LP + " unexpected");
148: if (token.equals(RP))
149: throw new ParseException(RP + " unexpected");
150: return item(token);
151: }
152:
153: /**
154: * Parse a list of one or more "filters". Each "filter" begins
155: * with a left paren, so the loop continues as long as that is
156: * true.
157: * @return an array of Filter objects.
158: * @exception ParseException
159: **/
160: private Filter[] filterlist() throws ParseException {
161: List result = new ArrayList();
162: do {
163: result.add(filter());
164: } while (LP.equals(peekToken()));
165: return (Filter[]) result.toArray(new Filter[result.size()]);
166: }
167:
168: /**
169: * An item is an attribute description, filtertype, and pattern or
170: * value. All filtertypes have an equal sign, so we look for that.
171: * Then we check the character preceding the equal sign to see if
172: * it is also part of the filter type. The string before the
173: * filtertype is the attribute description. The part after is some
174: * kind of value or pattern depending on the filtertype. We don't
175: * support matching rule items and we don't parse the attribute
176: * description any further.
177: * @param s String
178: **/
179: private Filter item(String s) throws ParseException {
180: int eqPos = s.indexOf('=');
181: if (eqPos < 1)
182: throw new ParseException("filtertype missing");
183: String pattern = s.substring(eqPos + 1);
184: switch (s.charAt(eqPos - 1)) {
185: case '<':
186: return new FilterLessThan(s.substring(0, eqPos - 1),
187: pattern);
188: case '>':
189: return new FilterGreaterThan(s.substring(0, eqPos - 1),
190: pattern);
191: case '~':
192: return new FilterApproximateMatch(
193: s.substring(0, eqPos - 1), pattern);
194: case ':':
195: throw new ParseException("matching rules not supported");
196: default:
197: String attr = s.substring(0, eqPos);
198: if (pattern.indexOf('*') < 0) {
199: return new FilterEquality(attr, pattern);
200: } else if (pattern.length() == 1) {
201: return new FilterPresence(attr);
202: } else {
203: return new FilterSubstring(attr, pattern);
204: }
205: }
206: }
207:
208: /**
209: * A base class for the following Filter implementations.
210: * Implements the usual public toString method in terms of a
211: * version of toString accepting a StringBuffer arg. This makes
212: * the construction of the string value more efficient.
213: **/
214: private static abstract class FilterBase {
215: public String toString() {
216: StringBuffer b = new StringBuffer();
217: toString(b);
218: return b.toString();
219: }
220:
221: public abstract void toString(StringBuffer b);
222: }
223:
224: /**
225: * A Filter representing an AND operation.
226: **/
227: private static class FilterAnd extends FilterBase implements Filter {
228: private Filter[] list;
229:
230: public FilterAnd(Filter[] list) {
231: this .list = list;
232: }
233:
234: /**
235: * Check if all the filters in the filter list match the given
236: * Attributes.
237: * @return false if any item fails, else return true.
238: **/
239: public boolean match(Attributes attrs) throws NamingException {
240: for (int i = 0; i < list.length; i++) {
241: if (!list[i].match(attrs))
242: return false;
243: }
244: return true;
245: }
246:
247: /**
248: * Append our contribution to the overall string.
249: **/
250: public void toString(StringBuffer b) {
251: b.append("(&");
252: for (int i = 0; i < list.length; i++) {
253: list[i].toString(b);
254: }
255: b.append(")");
256: }
257: }
258:
259: /**
260: * A Filter representing an OR operation.
261: **/
262: private static class FilterOr extends FilterBase implements Filter {
263: private Filter[] list;
264:
265: public FilterOr(Filter[] list) {
266: this .list = list;
267: }
268:
269: /**
270: * Check if any of the filters in the filter list match the given
271: * Attributes.
272: * @return true if any item matches, else return false.
273: **/
274: public boolean match(Attributes attrs) throws NamingException {
275: for (int i = 0; i < list.length; i++) {
276: if (list[i].match(attrs))
277: return true;
278: }
279: return false;
280: }
281:
282: /**
283: * Append our contribution to the overall string.
284: **/
285: public void toString(StringBuffer b) {
286: b.append("(|");
287: for (int i = 0; i < list.length; i++) {
288: list[i].toString(b);
289: }
290: b.append(")");
291: }
292: }
293:
294: /**
295: * A filter that negates the value of another Filter
296: **/
297: private static class FilterNot extends FilterBase implements Filter {
298: private Filter filter;
299:
300: public FilterNot(Filter filter) {
301: this .filter = filter;
302: }
303:
304: /**
305: * Return the negation of applying filter.
306: **/
307: public boolean match(Attributes attrs) throws NamingException {
308: return !filter.match(attrs);
309: }
310:
311: /**
312: * Append our contribution to the overall string.
313: **/
314: public void toString(StringBuffer b) {
315: b.append("(!");
316: filter.toString(b);
317: b.append(")");
318: }
319: }
320:
321: /**
322: * A Filter that checks if the value of an attribute is less than
323: * a specified value. This version simply compares strings.
324: **/
325: private static class FilterLessThan extends FilterBase implements
326: Filter {
327: private String attrdesc, value;
328:
329: public FilterLessThan(String attrdesc, String value) {
330: this .attrdesc = attrdesc;
331: this .value = value;
332: }
333:
334: public boolean match(Attributes attrs) throws NamingException {
335: try {
336: Attribute attr = attrs.get(attrdesc);
337: for (int i = 0, n = attr.size(); i < n; i++) {
338: String attrValue = attr.get(i).toString();
339: if (attrValue.compareTo((String) attrs
340: .get(attrdesc).get(i)) <= 0) {
341: return true;
342: }
343: }
344: return false;
345: } catch (Exception e) {
346: return false;
347: }
348: }
349:
350: /**
351: * Append our contribution to the overall string.
352: **/
353: public void toString(StringBuffer b) {
354: b.append("(");
355: b.append(attrdesc);
356: b.append("<=");
357: b.append(value);
358: b.append(")");
359: }
360: }
361:
362: /**
363: * A Filter that checks if the value of an attribute is greater than
364: * a specified value. This version simply compares strings.
365: **/
366: private static class FilterGreaterThan extends FilterBase implements
367: Filter {
368: private String attrdesc, value;
369:
370: public FilterGreaterThan(String attrdesc, String value) {
371: this .attrdesc = attrdesc;
372: this .value = value;
373: }
374:
375: public boolean match(Attributes attrs) throws NamingException {
376: try {
377: Attribute attr = attrs.get(attrdesc);
378: for (int i = 0, n = attr.size(); i < n; i++) {
379: String attrValue = attr.get(i).toString();
380: if (attrValue.compareTo((String) attrs
381: .get(attrdesc).get(i)) >= 0) {
382: return true;
383: }
384: }
385: return false;
386: } catch (Exception e) {
387: return false;
388: }
389: }
390:
391: /**
392: * Append our contribution to the overall string.
393: **/
394: public void toString(StringBuffer b) {
395: b.append("(");
396: b.append(attrdesc);
397: b.append(">=");
398: b.append(value);
399: b.append(")");
400: }
401: }
402:
403: /**
404: * A Filter that checks if an attribute is present
405: **/
406: private static class FilterPresence extends FilterBase implements
407: Filter {
408: private String attrdesc;
409:
410: public FilterPresence(String attrdesc) {
411: this .attrdesc = attrdesc;
412: }
413:
414: public boolean match(Attributes attrs) throws NamingException {
415: try {
416: return attrs.get(attrdesc) != null;
417: } catch (Exception e) {
418: return false;
419: }
420: }
421:
422: /**
423: * Append our contribution to the overall string.
424: **/
425: public void toString(StringBuffer b) {
426: b.append("(");
427: b.append(attrdesc);
428: b.append("=*)");
429: }
430: }
431:
432: /**
433: * A Filter that checks if the value of an attribute is equal to
434: * a specified value. This version simply compares strings.
435: **/
436: private static class FilterEquality extends FilterBase implements
437: Filter {
438: protected String attrdesc, value;
439:
440: public FilterEquality(String attrdesc, String value) {
441: this .attrdesc = attrdesc;
442: this .value = value;
443: }
444:
445: public boolean match(Attributes attrs) throws NamingException {
446: try {
447: Attribute attr = attrs.get(attrdesc);
448: for (int i = 0, n = attr.size(); i < n; i++) {
449: String attrValue = attr.get(i).toString();
450: if (value.equals(attrValue))
451: return true;
452: }
453: return false;
454: } catch (Exception e) {
455: return false;
456: }
457: }
458:
459: /**
460: * Append our contribution to the overall string.
461: **/
462: public void toString(StringBuffer b) {
463: b.append("(");
464: b.append(attrdesc);
465: b.append("=");
466: b.append(value);
467: b.append(")");
468: }
469: }
470:
471: /**
472: * A Filter that checks if the value of an attribute is
473: * approximately equal to a specified value. This version is the
474: * same as FilterEquality.
475: **/
476: private static class FilterApproximateMatch extends FilterEquality {
477: public FilterApproximateMatch(String attrdesc, String value) {
478: super (attrdesc, value);
479: }
480:
481: /**
482: * Append our contribution to the overall string.
483: **/
484: public void toString(StringBuffer b) {
485: b.append("(");
486: b.append(attrdesc);
487: b.append("~=");
488: b.append(value);
489: b.append(")");
490: }
491: }
492:
493: /**
494: * A Filter that checks if the value of an attribute matches
495: * a specified pattern.
496: **/
497: private static class FilterSubstring extends FilterBase implements
498: Filter {
499: private String attrdesc;
500: private Glob glob;
501:
502: public FilterSubstring(String attrdesc, String value) {
503: this .attrdesc = attrdesc;
504: this .glob = Glob.parse(value);
505: }
506:
507: public boolean match(Attributes attrs) throws NamingException {
508: try {
509: Attribute attr = attrs.get(attrdesc);
510: for (int i = 0; i < attr.size(); i++) {
511: if (glob.match(attrs.get(attrdesc).get(i)
512: .toString())) {
513: return true;
514: }
515: }
516: return false;
517: } catch (Exception e) {
518: return false;
519: }
520: }
521:
522: /**
523: * Append our contribution to the overall string.
524: **/
525: public void toString(StringBuffer b) {
526: b.append("(");
527: b.append(attrdesc);
528: b.append("=");
529: glob.appendString(b);
530: b.append(")");
531: }
532: }
533: }
|