xref: /onnv-gate/usr/src/lib/libslp/javalib/com/sun/slp/Parser.java (revision 7298:b69e27387f74)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*7298SMark.J.Nelson@Sun.COM  * Common Development and Distribution License (the "License").
6*7298SMark.J.Nelson@Sun.COM  * You may not use this file except in compliance with the License.
70Sstevel@tonic-gate  *
80Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
90Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
100Sstevel@tonic-gate  * See the License for the specific language governing permissions
110Sstevel@tonic-gate  * and limitations under the License.
120Sstevel@tonic-gate  *
130Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
140Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
150Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
160Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
170Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
180Sstevel@tonic-gate  *
190Sstevel@tonic-gate  * CDDL HEADER END
200Sstevel@tonic-gate  */
210Sstevel@tonic-gate /*
220Sstevel@tonic-gate  * Copyright 2001,2003 Sun Microsystems, Inc.  All rights reserved.
230Sstevel@tonic-gate  * Use is subject to license terms.
240Sstevel@tonic-gate  *
250Sstevel@tonic-gate  */
260Sstevel@tonic-gate 
270Sstevel@tonic-gate //  Parser.java:      LDAP Parser for those service stores that need it.
280Sstevel@tonic-gate //  Author:           James Kempf
290Sstevel@tonic-gate //  Created On:       Mon Apr 27 08:11:08 1998
300Sstevel@tonic-gate //  Last Modified By: James Kempf
310Sstevel@tonic-gate //  Last Modified On: Mon Mar  1 08:29:36 1999
320Sstevel@tonic-gate //  Update Count:     45
330Sstevel@tonic-gate //
340Sstevel@tonic-gate 
350Sstevel@tonic-gate package com.sun.slp;
360Sstevel@tonic-gate 
370Sstevel@tonic-gate import java.util.*;
380Sstevel@tonic-gate import java.io.*;
390Sstevel@tonic-gate 
400Sstevel@tonic-gate /**
410Sstevel@tonic-gate  * The Parser class implements LDAP query parsing for ServiceStoreInMemory.
420Sstevel@tonic-gate  * It is an internal class because it must know about the internal
430Sstevel@tonic-gate  * structure of the hashtables.
440Sstevel@tonic-gate  *
450Sstevel@tonic-gate  * @author James Kempf
460Sstevel@tonic-gate  */
470Sstevel@tonic-gate 
480Sstevel@tonic-gate abstract class Parser extends Object {
490Sstevel@tonic-gate 
500Sstevel@tonic-gate     final private static char NONASCII_LOWER = '\u0080';
510Sstevel@tonic-gate     final private static char NONASCII_UPPER = '\uffff';
520Sstevel@tonic-gate 
530Sstevel@tonic-gate     final static char EQUAL = '=';
540Sstevel@tonic-gate     final static char LESS = '<';
550Sstevel@tonic-gate     final static char GREATER = '>';
560Sstevel@tonic-gate     private final static char STAR = '*';
570Sstevel@tonic-gate     final static char PRESENT = STAR;
580Sstevel@tonic-gate 
590Sstevel@tonic-gate     private final static char OPAREN = '(';
600Sstevel@tonic-gate     private final static char CPAREN = ')';
610Sstevel@tonic-gate     private final static char APPROX = '~';
620Sstevel@tonic-gate     private final static char NOT = '!';
630Sstevel@tonic-gate     private final static char AND = '&';
640Sstevel@tonic-gate     private final static char OR = '|';
650Sstevel@tonic-gate     private final static char SPACE = ' ';
660Sstevel@tonic-gate 
670Sstevel@tonic-gate     /**
680Sstevel@tonic-gate      * Record for returning stuff to the service store.
690Sstevel@tonic-gate      *
700Sstevel@tonic-gate      * @author James Kempf
710Sstevel@tonic-gate      */
720Sstevel@tonic-gate 
730Sstevel@tonic-gate     static final class ParserRecord extends Object {
740Sstevel@tonic-gate 
750Sstevel@tonic-gate 	Hashtable services = new Hashtable();
760Sstevel@tonic-gate 	Hashtable signatures = new Hashtable();
770Sstevel@tonic-gate 
780Sstevel@tonic-gate     }
790Sstevel@tonic-gate 
800Sstevel@tonic-gate 
810Sstevel@tonic-gate     /**
820Sstevel@tonic-gate      * The QueryEvaluator interface evaluates a term in a query, given
830Sstevel@tonic-gate      * the attribute id, the operator, the object, and whether the
840Sstevel@tonic-gate      * term is currently under negation from a not operator. Only those
850Sstevel@tonic-gate      * ServiceStore implemenations that want to use the Parser
860Sstevel@tonic-gate      * class to perform query parsing must provide this.
870Sstevel@tonic-gate      *
880Sstevel@tonic-gate      * @author James Kempf
890Sstevel@tonic-gate      */
900Sstevel@tonic-gate 
910Sstevel@tonic-gate     interface QueryEvaluator {
920Sstevel@tonic-gate 
930Sstevel@tonic-gate 	/**
940Sstevel@tonic-gate 	 * Evaluate the query, storing away the services that match.
950Sstevel@tonic-gate 	 *
960Sstevel@tonic-gate 	 * @param tag The attribute tag for the term.
970Sstevel@tonic-gate 	 * @param op The term operator.
980Sstevel@tonic-gate 	 * @param pattern the operand of the term.
990Sstevel@tonic-gate 	 * @param invert True if the results of the comparison should be
1000Sstevel@tonic-gate 	 *		     inverted due to a not operator.
1010Sstevel@tonic-gate 	 * @param returns Hashtable for the returns. The returns are
1020Sstevel@tonic-gate 	 *		      structured exactly like the hashtable
1030Sstevel@tonic-gate 	 *		      returned from findServices().
1040Sstevel@tonic-gate 	 * @return True if the term matched, false if not.
1050Sstevel@tonic-gate 	 */
1060Sstevel@tonic-gate 
evaluate(AttributeString tag, char op, Object pattern, boolean invert, ParserRecord returns)1070Sstevel@tonic-gate 	boolean evaluate(AttributeString tag,
1080Sstevel@tonic-gate 			 char op,
1090Sstevel@tonic-gate 			 Object pattern,
1100Sstevel@tonic-gate 			 boolean invert,
1110Sstevel@tonic-gate 			 ParserRecord returns)
1120Sstevel@tonic-gate 	    throws ServiceLocationException;
1130Sstevel@tonic-gate 
1140Sstevel@tonic-gate     }
1150Sstevel@tonic-gate 
1160Sstevel@tonic-gate     /**
1170Sstevel@tonic-gate      * Parse a query and incrementally evaluate.
1180Sstevel@tonic-gate      *
1190Sstevel@tonic-gate      * @param urlLevel Hashtable of langlevel hashtables containing
1200Sstevel@tonic-gate      *                 registrations for the service type and scope.
1210Sstevel@tonic-gate      * @param query The query. Escapes have not yet been processed.
1220Sstevel@tonic-gate      * @param ret   Vector for returned records.
1230Sstevel@tonic-gate      * @param locale Locale in which to interpret query strings.
1240Sstevel@tonic-gate      * @param ret ParserRecord in which to return the results.
1250Sstevel@tonic-gate      */
1260Sstevel@tonic-gate 
1270Sstevel@tonic-gate     static void
parseAndEvaluateQuery(String query, Parser.QueryEvaluator ev, Locale locale, ParserRecord ret)1280Sstevel@tonic-gate 	parseAndEvaluateQuery(String query,
1290Sstevel@tonic-gate 			      Parser.QueryEvaluator ev,
1300Sstevel@tonic-gate 			      Locale locale,
1310Sstevel@tonic-gate 			      ParserRecord ret)
1320Sstevel@tonic-gate 	throws ServiceLocationException {
1330Sstevel@tonic-gate 
1340Sstevel@tonic-gate 	// Create and initialize lexical analyzer.
1350Sstevel@tonic-gate 
1360Sstevel@tonic-gate 	StreamTokenizer tk = new StreamTokenizer(new StringReader(query));
1370Sstevel@tonic-gate 
1380Sstevel@tonic-gate 	tk.resetSyntax();  		 // make all chars ordinary...
1390Sstevel@tonic-gate 	tk.wordChars('\177','\177');	 // treat controls as part of tokens
1400Sstevel@tonic-gate 	tk.wordChars('\000', SPACE);
1410Sstevel@tonic-gate 	tk.ordinaryChar(NOT);              // 'NOT' operator
1420Sstevel@tonic-gate 	tk.wordChars('"', '%');
1430Sstevel@tonic-gate 	tk.ordinaryChar(AND);              // 'AND' operator
1440Sstevel@tonic-gate 	tk.wordChars('\'', '\'');
1450Sstevel@tonic-gate 	tk.ordinaryChar(OPAREN);           // filter grouping
1460Sstevel@tonic-gate 	tk.ordinaryChar(CPAREN);
1470Sstevel@tonic-gate 	tk.ordinaryChar(STAR);             // present operator
1480Sstevel@tonic-gate 	tk.wordChars('+', '{');
1490Sstevel@tonic-gate 	tk.ordinaryChar(OR);               // 'OR' operator
1500Sstevel@tonic-gate 	tk.wordChars('}', '~');
1510Sstevel@tonic-gate 	tk.ordinaryChar(EQUAL);            // comparision operator
1520Sstevel@tonic-gate 	tk.ordinaryChar(LESS);             // less operator
1530Sstevel@tonic-gate 	tk.ordinaryChar(GREATER);          // greater operator
1540Sstevel@tonic-gate 	tk.ordinaryChar(APPROX);           // approx operator
1550Sstevel@tonic-gate 
1560Sstevel@tonic-gate 	// Begin parsing.
1570Sstevel@tonic-gate 
1580Sstevel@tonic-gate 	try {
1590Sstevel@tonic-gate 	    ParserRecord rec = parseFilter(tk, ev, locale, false, true);
1600Sstevel@tonic-gate 
1610Sstevel@tonic-gate 	    // Throw exception if anything occurs after the
1620Sstevel@tonic-gate 	    //  parsed expression.
1630Sstevel@tonic-gate 
1640Sstevel@tonic-gate 	    if (tk.nextToken() != StreamTokenizer.TT_EOF) {
1650Sstevel@tonic-gate 		throw
1660Sstevel@tonic-gate 		    new ServiceLocationException(
1670Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
1680Sstevel@tonic-gate 				"par_char_closing",
1690Sstevel@tonic-gate 				new Object[] {query});
1700Sstevel@tonic-gate 
1710Sstevel@tonic-gate 	    }
1720Sstevel@tonic-gate 
1730Sstevel@tonic-gate 	    // Merge in returns. Use OR operator so all returned
1740Sstevel@tonic-gate 	    //  values are merged in.
1750Sstevel@tonic-gate 
1760Sstevel@tonic-gate 	    mergeQueryReturns(ret, rec, OR);
1770Sstevel@tonic-gate 
1780Sstevel@tonic-gate 	} catch (IOException ex) {
1790Sstevel@tonic-gate 	    throw
1800Sstevel@tonic-gate 		new ServiceLocationException(
1810Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
1820Sstevel@tonic-gate 				"par_syn_err",
1830Sstevel@tonic-gate 				new Object[] {query});
1840Sstevel@tonic-gate 
1850Sstevel@tonic-gate 	}
1860Sstevel@tonic-gate     }
1870Sstevel@tonic-gate 
1880Sstevel@tonic-gate     //
1890Sstevel@tonic-gate     // Routines for dealing with parse returns record.
1900Sstevel@tonic-gate     //
1910Sstevel@tonic-gate 
1920Sstevel@tonic-gate     // Merge source to target. The target has already
1930Sstevel@tonic-gate     //  been precharged with ones that must match
1940Sstevel@tonic-gate     //  if the op is AND. If it's OR, then simply
1950Sstevel@tonic-gate     //  stuff them in.
1960Sstevel@tonic-gate 
1970Sstevel@tonic-gate     private static boolean
mergeQueryReturns(ParserRecord target, ParserRecord source, char op)1980Sstevel@tonic-gate 	mergeQueryReturns(ParserRecord target,
1990Sstevel@tonic-gate 			  ParserRecord source,
2000Sstevel@tonic-gate 			  char op) {
2010Sstevel@tonic-gate 	Hashtable targetServices = target.services;
2020Sstevel@tonic-gate 	Hashtable sourceServices = source.services;
2030Sstevel@tonic-gate 	boolean eval;
2040Sstevel@tonic-gate 
2050Sstevel@tonic-gate 	if (op == AND) {
2060Sstevel@tonic-gate 	    eval = mergeTablesWithAnd(targetServices, sourceServices);
2070Sstevel@tonic-gate 
2080Sstevel@tonic-gate 	} else {
2090Sstevel@tonic-gate 	    eval = mergeTablesWithOr(targetServices, sourceServices);
2100Sstevel@tonic-gate 
2110Sstevel@tonic-gate 	}
2120Sstevel@tonic-gate 
2130Sstevel@tonic-gate 	Hashtable targetSigs = target.signatures;
2140Sstevel@tonic-gate 	Hashtable sourceSigs = source.signatures;
2150Sstevel@tonic-gate 
2160Sstevel@tonic-gate 	if (op == AND) {
2170Sstevel@tonic-gate 	    mergeTablesWithAnd(targetSigs, sourceSigs);
2180Sstevel@tonic-gate 
2190Sstevel@tonic-gate 	} else {
2200Sstevel@tonic-gate 	    mergeTablesWithOr(targetSigs, sourceSigs);
2210Sstevel@tonic-gate 
2220Sstevel@tonic-gate 	}
2230Sstevel@tonic-gate 
2240Sstevel@tonic-gate 	return eval;
2250Sstevel@tonic-gate     }
2260Sstevel@tonic-gate 
2270Sstevel@tonic-gate 
2280Sstevel@tonic-gate     // Merge tables by removing anything from target that isn't in source.
2290Sstevel@tonic-gate 
mergeTablesWithAnd(Hashtable target, Hashtable source)2300Sstevel@tonic-gate     private static boolean mergeTablesWithAnd(Hashtable target,
2310Sstevel@tonic-gate 					      Hashtable source) {
2320Sstevel@tonic-gate 
2330Sstevel@tonic-gate 	Enumeration en = target.keys();
2340Sstevel@tonic-gate 
2350Sstevel@tonic-gate 	// Remove any from target that aren't in source.
2360Sstevel@tonic-gate 
2370Sstevel@tonic-gate 	while (en.hasMoreElements()) {
2380Sstevel@tonic-gate 	    Object tkey = en.nextElement();
2390Sstevel@tonic-gate 
2400Sstevel@tonic-gate 	    if (source.get(tkey) == null) {
2410Sstevel@tonic-gate 		target.remove(tkey);
2420Sstevel@tonic-gate 
2430Sstevel@tonic-gate 	    }
2440Sstevel@tonic-gate 	}
2450Sstevel@tonic-gate 
2460Sstevel@tonic-gate 	// If there's nothing left, return false to indicate no further
2470Sstevel@tonic-gate 	//  evaluation needed.
2480Sstevel@tonic-gate 
2490Sstevel@tonic-gate 	if (target.size() <= 0) {
2500Sstevel@tonic-gate 	    return false;
2510Sstevel@tonic-gate 
2520Sstevel@tonic-gate 	}
2530Sstevel@tonic-gate 
2540Sstevel@tonic-gate 	return true;
2550Sstevel@tonic-gate     }
2560Sstevel@tonic-gate 
2570Sstevel@tonic-gate     // Merge tables by adding everything from source into target.
2580Sstevel@tonic-gate 
mergeTablesWithOr(Hashtable target, Hashtable source)2590Sstevel@tonic-gate     private static boolean mergeTablesWithOr(Hashtable target,
2600Sstevel@tonic-gate 					     Hashtable source) {
2610Sstevel@tonic-gate 
2620Sstevel@tonic-gate 	Enumeration en = source.keys();
2630Sstevel@tonic-gate 
2640Sstevel@tonic-gate 	while (en.hasMoreElements()) {
2650Sstevel@tonic-gate 	    Object skey = en.nextElement();
2660Sstevel@tonic-gate 
2670Sstevel@tonic-gate 	    target.put(skey, source.get(skey));
2680Sstevel@tonic-gate 
2690Sstevel@tonic-gate 	}
2700Sstevel@tonic-gate 
2710Sstevel@tonic-gate 	return true;
2720Sstevel@tonic-gate     }
2730Sstevel@tonic-gate 
2740Sstevel@tonic-gate     //
2750Sstevel@tonic-gate     // Parsing for various productions.
2760Sstevel@tonic-gate     //
2770Sstevel@tonic-gate 
2780Sstevel@tonic-gate 
2790Sstevel@tonic-gate     // Parse the filter production.
2800Sstevel@tonic-gate 
2810Sstevel@tonic-gate     private static ParserRecord
parseFilter(StreamTokenizer tk, Parser.QueryEvaluator ev, Locale locale, boolean invert, boolean eval)2820Sstevel@tonic-gate 	parseFilter(StreamTokenizer tk,
2830Sstevel@tonic-gate 		    Parser.QueryEvaluator ev,
2840Sstevel@tonic-gate 		    Locale locale,
2850Sstevel@tonic-gate 		    boolean invert,
2860Sstevel@tonic-gate 		    boolean eval)
2870Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
2880Sstevel@tonic-gate 
2890Sstevel@tonic-gate 	ParserRecord ret = null;
2900Sstevel@tonic-gate 	int tok = tk.nextToken();
2910Sstevel@tonic-gate 
2920Sstevel@tonic-gate 	// Check for opening paren.
2930Sstevel@tonic-gate 
2940Sstevel@tonic-gate 	if (tok != OPAREN) {
2950Sstevel@tonic-gate 	    throw
2960Sstevel@tonic-gate 		new ServiceLocationException(
2970Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
2980Sstevel@tonic-gate 				"par_init_par",
2990Sstevel@tonic-gate 				new Object[0]);
3000Sstevel@tonic-gate 
3010Sstevel@tonic-gate 	}
3020Sstevel@tonic-gate 
3030Sstevel@tonic-gate 	// Parse inside.
3040Sstevel@tonic-gate 
3050Sstevel@tonic-gate 	tok = tk.nextToken();
3060Sstevel@tonic-gate 
3070Sstevel@tonic-gate 	// Check for a logical operator.
3080Sstevel@tonic-gate 
3090Sstevel@tonic-gate 	if (tok == AND || tok == OR) {
3100Sstevel@tonic-gate 	    ret = parseFilterlist(tk, ev, locale, (char)tok, invert, eval);
3110Sstevel@tonic-gate 
3120Sstevel@tonic-gate 	} else if (tok == NOT) {
3130Sstevel@tonic-gate 	    ret =  parseFilter(tk, ev, locale, !invert, eval);
3140Sstevel@tonic-gate 
3150Sstevel@tonic-gate 	} else if (tok == StreamTokenizer.TT_WORD) {
3160Sstevel@tonic-gate 	    tk.pushBack();
3170Sstevel@tonic-gate 	    ret =  parseItem(tk, ev, locale, invert, eval);
3180Sstevel@tonic-gate 
3190Sstevel@tonic-gate 	} else {
3200Sstevel@tonic-gate 
3210Sstevel@tonic-gate 	    // Since we've covered the ASCII character set, the only other
3220Sstevel@tonic-gate 	    //  thing that could be here is a nonASCII character. We push it
3230Sstevel@tonic-gate 	    //  back and deal with it in parseItem().
3240Sstevel@tonic-gate 
3250Sstevel@tonic-gate 	    tk.pushBack();
3260Sstevel@tonic-gate 	    ret = parseItem(tk, ev, locale, invert, eval);
3270Sstevel@tonic-gate 
3280Sstevel@tonic-gate 	}
3290Sstevel@tonic-gate 
3300Sstevel@tonic-gate 	tok = tk.nextToken();
3310Sstevel@tonic-gate 
3320Sstevel@tonic-gate 	// Check for closing paren.
3330Sstevel@tonic-gate 
3340Sstevel@tonic-gate 	if (tok != CPAREN) {
3350Sstevel@tonic-gate 	    throw
3360Sstevel@tonic-gate 		new ServiceLocationException(
3370Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
3380Sstevel@tonic-gate 				"par_final_par",
3390Sstevel@tonic-gate 				new Object[0]);
3400Sstevel@tonic-gate 
3410Sstevel@tonic-gate 	}
3420Sstevel@tonic-gate 
3430Sstevel@tonic-gate 	return ret;
3440Sstevel@tonic-gate     }
3450Sstevel@tonic-gate 
3460Sstevel@tonic-gate     // Parse a filterlist production.
3470Sstevel@tonic-gate 
3480Sstevel@tonic-gate     private static ParserRecord
parseFilterlist(StreamTokenizer tk, Parser.QueryEvaluator ev, Locale locale, char op, boolean invert, boolean eval)3490Sstevel@tonic-gate 	parseFilterlist(StreamTokenizer tk,
3500Sstevel@tonic-gate 			Parser.QueryEvaluator ev,
3510Sstevel@tonic-gate 			Locale locale,
3520Sstevel@tonic-gate 			char op,
3530Sstevel@tonic-gate 			boolean invert,
3540Sstevel@tonic-gate 			boolean eval)
3550Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
3560Sstevel@tonic-gate 	boolean match;
3570Sstevel@tonic-gate 
3580Sstevel@tonic-gate 	ParserRecord mrex = null;
3590Sstevel@tonic-gate 
3600Sstevel@tonic-gate 	// Parse through the list of filters.
3610Sstevel@tonic-gate 
3620Sstevel@tonic-gate 	do {
3630Sstevel@tonic-gate 	    ParserRecord prex = null;
3640Sstevel@tonic-gate 
3650Sstevel@tonic-gate 	    if (op == AND) {
3660Sstevel@tonic-gate 
3670Sstevel@tonic-gate 		prex = parseFilter(tk, ev, locale, invert, eval);
3680Sstevel@tonic-gate 
3690Sstevel@tonic-gate 	    } else {
3700Sstevel@tonic-gate 
3710Sstevel@tonic-gate 		prex = parseFilter(tk, ev, locale, invert, eval);
3720Sstevel@tonic-gate 
3730Sstevel@tonic-gate 	    }
3740Sstevel@tonic-gate 
3750Sstevel@tonic-gate 	    // We need to start off with something.
3760Sstevel@tonic-gate 
3770Sstevel@tonic-gate 	    if (mrex == null) {
3780Sstevel@tonic-gate 		mrex = prex;
3790Sstevel@tonic-gate 
3800Sstevel@tonic-gate 	    } else {
3810Sstevel@tonic-gate 
3820Sstevel@tonic-gate 		// Merge in returns.
3830Sstevel@tonic-gate 
3840Sstevel@tonic-gate 		eval = mergeQueryReturns(mrex, prex, op);
3850Sstevel@tonic-gate 
3860Sstevel@tonic-gate 	    }
3870Sstevel@tonic-gate 
3880Sstevel@tonic-gate 	    // Look for ending paren.
3890Sstevel@tonic-gate 
3900Sstevel@tonic-gate 	    int tok = tk.nextToken();
3910Sstevel@tonic-gate 	    tk.pushBack();
3920Sstevel@tonic-gate 
3930Sstevel@tonic-gate 	    if (tok == CPAREN) {
3940Sstevel@tonic-gate 
3950Sstevel@tonic-gate 		return mrex;
3960Sstevel@tonic-gate 
3970Sstevel@tonic-gate 	    }
3980Sstevel@tonic-gate 
3990Sstevel@tonic-gate 	} while (true);
4000Sstevel@tonic-gate 
4010Sstevel@tonic-gate     }
4020Sstevel@tonic-gate 
4030Sstevel@tonic-gate     // Parse item.
4040Sstevel@tonic-gate 
4050Sstevel@tonic-gate     private static ParserRecord
parseItem(StreamTokenizer tk, Parser.QueryEvaluator ev, Locale locale, boolean invert, boolean eval)4060Sstevel@tonic-gate 	parseItem(StreamTokenizer tk,
4070Sstevel@tonic-gate 		  Parser.QueryEvaluator ev,
4080Sstevel@tonic-gate 		  Locale locale,
4090Sstevel@tonic-gate 		  boolean invert,
4100Sstevel@tonic-gate 		  boolean eval)
4110Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
4120Sstevel@tonic-gate 
4130Sstevel@tonic-gate 	ParserRecord prex = new ParserRecord();
4140Sstevel@tonic-gate 	AttributeString attr = parseAttr(tk, locale);
4150Sstevel@tonic-gate 	char op = parseOp(tk);
4160Sstevel@tonic-gate 	Object value = null;
4170Sstevel@tonic-gate 
4180Sstevel@tonic-gate 	// If operator is PRESENT, then check whether
4190Sstevel@tonic-gate 	//  it's not really a wildcarded value. If the next
4200Sstevel@tonic-gate 	//  token isn't a closing paren, then it's
4210Sstevel@tonic-gate 	//  a wildcarded value.
4220Sstevel@tonic-gate 
4230Sstevel@tonic-gate 	if (op == PRESENT) {
4240Sstevel@tonic-gate 	    int tok = tk.nextToken();
4250Sstevel@tonic-gate 
4260Sstevel@tonic-gate 	    tk.pushBack();  // ...in any event...
4270Sstevel@tonic-gate 
4280Sstevel@tonic-gate 	    if ((char)tok != CPAREN) { // It's a wildcarded pattern...
4290Sstevel@tonic-gate 		op = EQUAL;
4300Sstevel@tonic-gate 		value = parseValue(tk, locale);
4310Sstevel@tonic-gate 
4320Sstevel@tonic-gate 		// Need to convert to a wildcarded pattern. Regardless
4330Sstevel@tonic-gate 		//  of type, since wildcard makes the type be a
4340Sstevel@tonic-gate 		//  string.
4350Sstevel@tonic-gate 
4360Sstevel@tonic-gate 		value =
4370Sstevel@tonic-gate 		    new AttributePattern(PRESENT + value.toString(), locale);
4380Sstevel@tonic-gate 
4390Sstevel@tonic-gate 	    }
4400Sstevel@tonic-gate 	} else {
4410Sstevel@tonic-gate 	    value = parseValue(tk, locale);
4420Sstevel@tonic-gate 
4430Sstevel@tonic-gate 	}
4440Sstevel@tonic-gate 
4450Sstevel@tonic-gate 	// Check for inappropriate pattern.
4460Sstevel@tonic-gate 
4470Sstevel@tonic-gate 	if (value instanceof AttributePattern &&
4480Sstevel@tonic-gate 	    ((AttributePattern)value).isWildcarded() &&
4490Sstevel@tonic-gate 	    op != EQUAL) {
4500Sstevel@tonic-gate 	    throw
4510Sstevel@tonic-gate 		new ServiceLocationException(
4520Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
4530Sstevel@tonic-gate 				"par_wild_op",
4540Sstevel@tonic-gate 				new Object[] {new Character(op)});
4550Sstevel@tonic-gate 
4560Sstevel@tonic-gate 	}
4570Sstevel@tonic-gate 
4580Sstevel@tonic-gate 	// Check for inappropriate boolean.
4590Sstevel@tonic-gate 
4600Sstevel@tonic-gate 	if ((value instanceof Boolean ||
4610Sstevel@tonic-gate 	    value instanceof Opaque) &&
4620Sstevel@tonic-gate 	    (op == GREATER || op == LESS)) {
4630Sstevel@tonic-gate 	    throw
4640Sstevel@tonic-gate 		new ServiceLocationException(
4650Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
4660Sstevel@tonic-gate 				"par_bool_op",
4670Sstevel@tonic-gate 				new Object[] {new Character(op)});
4680Sstevel@tonic-gate 
4690Sstevel@tonic-gate 	}
4700Sstevel@tonic-gate 
4710Sstevel@tonic-gate 	// Check for wrong operator with keyword.
4720Sstevel@tonic-gate 
4730Sstevel@tonic-gate 	if ((value == null || value.toString().length() <= 0) &&
4740Sstevel@tonic-gate 	    op != PRESENT) {
4750Sstevel@tonic-gate 	    throw
4760Sstevel@tonic-gate 		new ServiceLocationException(
4770Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
4780Sstevel@tonic-gate 				"par_key_op",
4790Sstevel@tonic-gate 				new Object[] {new Character(op)});
4800Sstevel@tonic-gate 	}
4810Sstevel@tonic-gate 
4820Sstevel@tonic-gate 	if (eval) {
4830Sstevel@tonic-gate 	    /*
4840Sstevel@tonic-gate 	     * Try and evaluate the query. If the evaluation failed and the
4850Sstevel@tonic-gate 	     * value was an Integer or Boolean try again after converting the
4860Sstevel@tonic-gate 	     * value to a String. This is because the value in the query will
4870Sstevel@tonic-gate 	     * be converted to an Integer or Boolean in preference to a String
4880Sstevel@tonic-gate 	     * even though the query starts out as a String.  Hence when an
4890Sstevel@tonic-gate 	     * attribute is registered with a String value that can equally be
4900Sstevel@tonic-gate 	     * parsed as a valid Integer or Boolean value the String will
4910Sstevel@tonic-gate 	     * almost always be parsed as an Integer or Boolean. This results
4920Sstevel@tonic-gate 	     * in the failing of the initial type check when performing the
4930Sstevel@tonic-gate 	     * query. By converting the value to a String there is another shot
4940Sstevel@tonic-gate 	     * at fulfulling the query.
4950Sstevel@tonic-gate 	     */
4960Sstevel@tonic-gate 	    if (!ev.evaluate(attr, op, value, invert, prex) &&
4970Sstevel@tonic-gate 		    !(value instanceof AttributeString)) {
4980Sstevel@tonic-gate 		ev.evaluate(attr,
4990Sstevel@tonic-gate 			    op,
5000Sstevel@tonic-gate 			    new AttributeString(
5010Sstevel@tonic-gate 				value.toString().trim(),
5020Sstevel@tonic-gate 				locale),
5030Sstevel@tonic-gate 			    invert,
5040Sstevel@tonic-gate 			    prex);
5050Sstevel@tonic-gate 	    }
5060Sstevel@tonic-gate 
5070Sstevel@tonic-gate 	}
5080Sstevel@tonic-gate 
5090Sstevel@tonic-gate 	return prex;
5100Sstevel@tonic-gate     }
5110Sstevel@tonic-gate 
5120Sstevel@tonic-gate     // Parse attribute tag.
5130Sstevel@tonic-gate 
parseAttr(StreamTokenizer tk, Locale locale)5140Sstevel@tonic-gate     private static AttributeString parseAttr(StreamTokenizer tk, Locale locale)
5150Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
5160Sstevel@tonic-gate 
5170Sstevel@tonic-gate 	String str  = parsePotentialNonASCII(tk);
5180Sstevel@tonic-gate 
5190Sstevel@tonic-gate 	str =
5200Sstevel@tonic-gate 	    ServiceLocationAttribute.unescapeAttributeString(str, true);
5210Sstevel@tonic-gate 
5220Sstevel@tonic-gate 	return new AttributeString(str, locale);
5230Sstevel@tonic-gate     }
5240Sstevel@tonic-gate 
5250Sstevel@tonic-gate     // Parse attribute operator.
5260Sstevel@tonic-gate 
parseOp(StreamTokenizer tk)5270Sstevel@tonic-gate     private static char parseOp(StreamTokenizer tk)
5280Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
5290Sstevel@tonic-gate 
5300Sstevel@tonic-gate 	int tok = tk.nextToken();
5310Sstevel@tonic-gate 
5320Sstevel@tonic-gate 	// Identify operator
5330Sstevel@tonic-gate 
5340Sstevel@tonic-gate 	switch (tok) {
5350Sstevel@tonic-gate 
5360Sstevel@tonic-gate 	case EQUAL:
5370Sstevel@tonic-gate 
5380Sstevel@tonic-gate 	    // Is it present?
5390Sstevel@tonic-gate 
5400Sstevel@tonic-gate 	    tok = tk.nextToken();
5410Sstevel@tonic-gate 
5420Sstevel@tonic-gate 	    if (tok == STAR) {
5430Sstevel@tonic-gate 		return PRESENT;
5440Sstevel@tonic-gate 
5450Sstevel@tonic-gate 	    } else {
5460Sstevel@tonic-gate 		tk.pushBack();
5470Sstevel@tonic-gate 		return EQUAL;
5480Sstevel@tonic-gate 
5490Sstevel@tonic-gate 	    }
5500Sstevel@tonic-gate 
5510Sstevel@tonic-gate 	case APPROX: case GREATER: case LESS:
5520Sstevel@tonic-gate 
5530Sstevel@tonic-gate 	    // Need equals.
5540Sstevel@tonic-gate 
5550Sstevel@tonic-gate 	    if (tk.nextToken() != EQUAL) {
5560Sstevel@tonic-gate 		break;
5570Sstevel@tonic-gate 
5580Sstevel@tonic-gate 	    }
5590Sstevel@tonic-gate 
5600Sstevel@tonic-gate 	    if (tok == APPROX) {
5610Sstevel@tonic-gate 		tok = EQUAL;
5620Sstevel@tonic-gate 
5630Sstevel@tonic-gate 	    }
5640Sstevel@tonic-gate 
5650Sstevel@tonic-gate 	    return (char)tok;
5660Sstevel@tonic-gate 
5670Sstevel@tonic-gate 	default:
5680Sstevel@tonic-gate 	    break;
5690Sstevel@tonic-gate 
5700Sstevel@tonic-gate 	}
5710Sstevel@tonic-gate 
5720Sstevel@tonic-gate 	throw
5730Sstevel@tonic-gate 	    new ServiceLocationException(
5740Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
5750Sstevel@tonic-gate 				"par_comp_op",
5760Sstevel@tonic-gate 				new Object[0]);
5770Sstevel@tonic-gate 
5780Sstevel@tonic-gate     }
5790Sstevel@tonic-gate 
5800Sstevel@tonic-gate     // Parse expression value.
5810Sstevel@tonic-gate 
parseValue(StreamTokenizer tk, Locale locale)5820Sstevel@tonic-gate     private static Object parseValue(StreamTokenizer tk, Locale locale)
5830Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
5840Sstevel@tonic-gate 
5850Sstevel@tonic-gate 	StringBuffer buf = new StringBuffer();
5860Sstevel@tonic-gate 
5870Sstevel@tonic-gate 	// Parse until the next closing paren.
5880Sstevel@tonic-gate 
5890Sstevel@tonic-gate 	do {
5900Sstevel@tonic-gate 	    int tok = tk.nextToken();
5910Sstevel@tonic-gate 
5920Sstevel@tonic-gate 	    if (tok == CPAREN) {
5930Sstevel@tonic-gate 		tk.pushBack();
5940Sstevel@tonic-gate 
5950Sstevel@tonic-gate 		Object o =
5960Sstevel@tonic-gate 		    ServiceLocationAttribute.evaluate(buf.toString().trim());
5970Sstevel@tonic-gate 
5980Sstevel@tonic-gate 		if (o instanceof String) {
5990Sstevel@tonic-gate 		    o = new AttributePattern((String)o, locale);
6000Sstevel@tonic-gate 
6010Sstevel@tonic-gate 		} else if (o instanceof byte[]) {
6020Sstevel@tonic-gate 		    o = new Opaque((byte[])o);
6030Sstevel@tonic-gate 
6040Sstevel@tonic-gate 		}
6050Sstevel@tonic-gate 
6060Sstevel@tonic-gate 		return o;
6070Sstevel@tonic-gate 
6080Sstevel@tonic-gate 	    } else if (tok != StreamTokenizer.TT_EOF) {
6090Sstevel@tonic-gate 
6100Sstevel@tonic-gate 		if (tok == StreamTokenizer.TT_WORD) {
6110Sstevel@tonic-gate 		    buf.append(tk.sval);
6120Sstevel@tonic-gate 
6130Sstevel@tonic-gate 		} else if (tok == StreamTokenizer.TT_NUMBER) {
6140Sstevel@tonic-gate 		    Assert.slpassert(false,
6150Sstevel@tonic-gate 				  "par_ntok",
6160Sstevel@tonic-gate 				  new Object[0]);
6170Sstevel@tonic-gate 
6180Sstevel@tonic-gate 		} else {
6190Sstevel@tonic-gate 		    buf.append((char)tok);
6200Sstevel@tonic-gate 
6210Sstevel@tonic-gate 		}
6220Sstevel@tonic-gate 
6230Sstevel@tonic-gate 	    } else {
6240Sstevel@tonic-gate 		throw
6250Sstevel@tonic-gate 		    new ServiceLocationException(
6260Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
6270Sstevel@tonic-gate 				"par_qend",
6280Sstevel@tonic-gate 				new Object[0]);
6290Sstevel@tonic-gate 	    }
6300Sstevel@tonic-gate 	} while (true);
6310Sstevel@tonic-gate 
6320Sstevel@tonic-gate     }
6330Sstevel@tonic-gate 
6340Sstevel@tonic-gate     // NonASCII characters may be in the string. StreamTokenizer
6350Sstevel@tonic-gate     //  can't handle them as part of words, so we need to resort to
6360Sstevel@tonic-gate     //  this loop to handle it.
6370Sstevel@tonic-gate 
parsePotentialNonASCII(StreamTokenizer tk)6380Sstevel@tonic-gate     private static String parsePotentialNonASCII(StreamTokenizer tk)
6390Sstevel@tonic-gate 	throws IOException {
6400Sstevel@tonic-gate 
6410Sstevel@tonic-gate 	StringBuffer buf = new StringBuffer();
6420Sstevel@tonic-gate 
6430Sstevel@tonic-gate 	do {
6440Sstevel@tonic-gate 
6450Sstevel@tonic-gate 	    int tok = tk.nextToken();
6460Sstevel@tonic-gate 
6470Sstevel@tonic-gate 	    if (tok == StreamTokenizer.TT_WORD) {
6480Sstevel@tonic-gate 		buf.append(tk.sval);
6490Sstevel@tonic-gate 
6500Sstevel@tonic-gate 	    } else if (((char)tok >= NONASCII_LOWER) &&
6510Sstevel@tonic-gate 		       ((char)tok <= NONASCII_UPPER)) {
6520Sstevel@tonic-gate 		buf.append((char)tok);
6530Sstevel@tonic-gate 
6540Sstevel@tonic-gate 	    } else {
6550Sstevel@tonic-gate 		tk.pushBack();
6560Sstevel@tonic-gate 		break;
6570Sstevel@tonic-gate 
6580Sstevel@tonic-gate 	    }
6590Sstevel@tonic-gate 
6600Sstevel@tonic-gate 	} while (true);
6610Sstevel@tonic-gate 
6620Sstevel@tonic-gate 	return buf.toString();
6630Sstevel@tonic-gate     }
6640Sstevel@tonic-gate }
665