1*7c478bd9Sstevel@tonic-gate /*
2*7c478bd9Sstevel@tonic-gate  * CDDL HEADER START
3*7c478bd9Sstevel@tonic-gate  *
4*7c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*7c478bd9Sstevel@tonic-gate  * Common Development and Distribution License, Version 1.0 only
6*7c478bd9Sstevel@tonic-gate  * (the "License").  You may not use this file except in compliance
7*7c478bd9Sstevel@tonic-gate  * with the License.
8*7c478bd9Sstevel@tonic-gate  *
9*7c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*7c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
11*7c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
12*7c478bd9Sstevel@tonic-gate  * and limitations under the License.
13*7c478bd9Sstevel@tonic-gate  *
14*7c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
15*7c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*7c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
17*7c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
18*7c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
19*7c478bd9Sstevel@tonic-gate  *
20*7c478bd9Sstevel@tonic-gate  * CDDL HEADER END
21*7c478bd9Sstevel@tonic-gate  */
22*7c478bd9Sstevel@tonic-gate /*
23*7c478bd9Sstevel@tonic-gate  * ident	"%Z%%M%	%I%	%E% SMI"
24*7c478bd9Sstevel@tonic-gate  *
25*7c478bd9Sstevel@tonic-gate  * Copyright 2001,2003 Sun Microsystems, Inc.  All rights reserved.
26*7c478bd9Sstevel@tonic-gate  * Use is subject to license terms.
27*7c478bd9Sstevel@tonic-gate  *
28*7c478bd9Sstevel@tonic-gate  */
29*7c478bd9Sstevel@tonic-gate 
30*7c478bd9Sstevel@tonic-gate //  SCCS Status:      %W%	%G%
31*7c478bd9Sstevel@tonic-gate //  Parser.java:      LDAP Parser for those service stores that need it.
32*7c478bd9Sstevel@tonic-gate //  Author:           James Kempf
33*7c478bd9Sstevel@tonic-gate //  Created On:       Mon Apr 27 08:11:08 1998
34*7c478bd9Sstevel@tonic-gate //  Last Modified By: James Kempf
35*7c478bd9Sstevel@tonic-gate //  Last Modified On: Mon Mar  1 08:29:36 1999
36*7c478bd9Sstevel@tonic-gate //  Update Count:     45
37*7c478bd9Sstevel@tonic-gate //
38*7c478bd9Sstevel@tonic-gate 
39*7c478bd9Sstevel@tonic-gate package com.sun.slp;
40*7c478bd9Sstevel@tonic-gate 
41*7c478bd9Sstevel@tonic-gate import java.util.*;
42*7c478bd9Sstevel@tonic-gate import java.io.*;
43*7c478bd9Sstevel@tonic-gate 
44*7c478bd9Sstevel@tonic-gate /**
45*7c478bd9Sstevel@tonic-gate  * The Parser class implements LDAP query parsing for ServiceStoreInMemory.
46*7c478bd9Sstevel@tonic-gate  * It is an internal class because it must know about the internal
47*7c478bd9Sstevel@tonic-gate  * structure of the hashtables.
48*7c478bd9Sstevel@tonic-gate  *
49*7c478bd9Sstevel@tonic-gate  * @version %R%.%L% %D%
50*7c478bd9Sstevel@tonic-gate  * @author James Kempf
51*7c478bd9Sstevel@tonic-gate  */
52*7c478bd9Sstevel@tonic-gate 
53*7c478bd9Sstevel@tonic-gate abstract class Parser extends Object {
54*7c478bd9Sstevel@tonic-gate 
55*7c478bd9Sstevel@tonic-gate     final private static char NONASCII_LOWER = '\u0080';
56*7c478bd9Sstevel@tonic-gate     final private static char NONASCII_UPPER = '\uffff';
57*7c478bd9Sstevel@tonic-gate 
58*7c478bd9Sstevel@tonic-gate     final static char EQUAL = '=';
59*7c478bd9Sstevel@tonic-gate     final static char LESS = '<';
60*7c478bd9Sstevel@tonic-gate     final static char GREATER = '>';
61*7c478bd9Sstevel@tonic-gate     private final static char STAR = '*';
62*7c478bd9Sstevel@tonic-gate     final static char PRESENT = STAR;
63*7c478bd9Sstevel@tonic-gate 
64*7c478bd9Sstevel@tonic-gate     private final static char OPAREN = '(';
65*7c478bd9Sstevel@tonic-gate     private final static char CPAREN = ')';
66*7c478bd9Sstevel@tonic-gate     private final static char APPROX = '~';
67*7c478bd9Sstevel@tonic-gate     private final static char NOT = '!';
68*7c478bd9Sstevel@tonic-gate     private final static char AND = '&';
69*7c478bd9Sstevel@tonic-gate     private final static char OR = '|';
70*7c478bd9Sstevel@tonic-gate     private final static char SPACE = ' ';
71*7c478bd9Sstevel@tonic-gate 
72*7c478bd9Sstevel@tonic-gate     /**
73*7c478bd9Sstevel@tonic-gate      * Record for returning stuff to the service store.
74*7c478bd9Sstevel@tonic-gate      *
75*7c478bd9Sstevel@tonic-gate      * @version %R%.%L% %D%
76*7c478bd9Sstevel@tonic-gate      * @author James Kempf
77*7c478bd9Sstevel@tonic-gate      */
78*7c478bd9Sstevel@tonic-gate 
79*7c478bd9Sstevel@tonic-gate     static final class ParserRecord extends Object {
80*7c478bd9Sstevel@tonic-gate 
81*7c478bd9Sstevel@tonic-gate 	Hashtable services = new Hashtable();
82*7c478bd9Sstevel@tonic-gate 	Hashtable signatures = new Hashtable();
83*7c478bd9Sstevel@tonic-gate 
84*7c478bd9Sstevel@tonic-gate     }
85*7c478bd9Sstevel@tonic-gate 
86*7c478bd9Sstevel@tonic-gate 
87*7c478bd9Sstevel@tonic-gate     /**
88*7c478bd9Sstevel@tonic-gate      * The QueryEvaluator interface evaluates a term in a query, given
89*7c478bd9Sstevel@tonic-gate      * the attribute id, the operator, the object, and whether the
90*7c478bd9Sstevel@tonic-gate      * term is currently under negation from a not operator. Only those
91*7c478bd9Sstevel@tonic-gate      * ServiceStore implemenations that want to use the Parser
92*7c478bd9Sstevel@tonic-gate      * class to perform query parsing must provide this.
93*7c478bd9Sstevel@tonic-gate      *
94*7c478bd9Sstevel@tonic-gate      * @version %R%.%L% %D%
95*7c478bd9Sstevel@tonic-gate      * @author James Kempf
96*7c478bd9Sstevel@tonic-gate      */
97*7c478bd9Sstevel@tonic-gate 
98*7c478bd9Sstevel@tonic-gate     interface QueryEvaluator {
99*7c478bd9Sstevel@tonic-gate 
100*7c478bd9Sstevel@tonic-gate 	/**
101*7c478bd9Sstevel@tonic-gate 	 * Evaluate the query, storing away the services that match.
102*7c478bd9Sstevel@tonic-gate 	 *
103*7c478bd9Sstevel@tonic-gate 	 * @param tag The attribute tag for the term.
104*7c478bd9Sstevel@tonic-gate 	 * @param op The term operator.
105*7c478bd9Sstevel@tonic-gate 	 * @param pattern the operand of the term.
106*7c478bd9Sstevel@tonic-gate 	 * @param invert True if the results of the comparison should be
107*7c478bd9Sstevel@tonic-gate 	 *		     inverted due to a not operator.
108*7c478bd9Sstevel@tonic-gate 	 * @param returns Hashtable for the returns. The returns are
109*7c478bd9Sstevel@tonic-gate 	 *		      structured exactly like the hashtable
110*7c478bd9Sstevel@tonic-gate 	 *		      returned from findServices().
111*7c478bd9Sstevel@tonic-gate 	 * @return True if the term matched, false if not.
112*7c478bd9Sstevel@tonic-gate 	 */
113*7c478bd9Sstevel@tonic-gate 
114*7c478bd9Sstevel@tonic-gate 	boolean evaluate(AttributeString tag,
115*7c478bd9Sstevel@tonic-gate 			 char op,
116*7c478bd9Sstevel@tonic-gate 			 Object pattern,
117*7c478bd9Sstevel@tonic-gate 			 boolean invert,
118*7c478bd9Sstevel@tonic-gate 			 ParserRecord returns)
119*7c478bd9Sstevel@tonic-gate 	    throws ServiceLocationException;
120*7c478bd9Sstevel@tonic-gate 
121*7c478bd9Sstevel@tonic-gate     }
122*7c478bd9Sstevel@tonic-gate 
123*7c478bd9Sstevel@tonic-gate     /**
124*7c478bd9Sstevel@tonic-gate      * Parse a query and incrementally evaluate.
125*7c478bd9Sstevel@tonic-gate      *
126*7c478bd9Sstevel@tonic-gate      * @param urlLevel Hashtable of langlevel hashtables containing
127*7c478bd9Sstevel@tonic-gate      *                 registrations for the service type and scope.
128*7c478bd9Sstevel@tonic-gate      * @param query The query. Escapes have not yet been processed.
129*7c478bd9Sstevel@tonic-gate      * @param ret   Vector for returned records.
130*7c478bd9Sstevel@tonic-gate      * @param locale Locale in which to interpret query strings.
131*7c478bd9Sstevel@tonic-gate      * @param ret ParserRecord in which to return the results.
132*7c478bd9Sstevel@tonic-gate      */
133*7c478bd9Sstevel@tonic-gate 
134*7c478bd9Sstevel@tonic-gate     static void
135*7c478bd9Sstevel@tonic-gate 	parseAndEvaluateQuery(String query,
136*7c478bd9Sstevel@tonic-gate 			      Parser.QueryEvaluator ev,
137*7c478bd9Sstevel@tonic-gate 			      Locale locale,
138*7c478bd9Sstevel@tonic-gate 			      ParserRecord ret)
139*7c478bd9Sstevel@tonic-gate 	throws ServiceLocationException {
140*7c478bd9Sstevel@tonic-gate 
141*7c478bd9Sstevel@tonic-gate 	// Create and initialize lexical analyzer.
142*7c478bd9Sstevel@tonic-gate 
143*7c478bd9Sstevel@tonic-gate 	StreamTokenizer tk = new StreamTokenizer(new StringReader(query));
144*7c478bd9Sstevel@tonic-gate 
145*7c478bd9Sstevel@tonic-gate 	tk.resetSyntax();  		 // make all chars ordinary...
146*7c478bd9Sstevel@tonic-gate 	tk.wordChars('\177','\177');	 // treat controls as part of tokens
147*7c478bd9Sstevel@tonic-gate 	tk.wordChars('\000', SPACE);
148*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(NOT);              // 'NOT' operator
149*7c478bd9Sstevel@tonic-gate 	tk.wordChars('"', '%');
150*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(AND);              // 'AND' operator
151*7c478bd9Sstevel@tonic-gate 	tk.wordChars('\'', '\'');
152*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(OPAREN);           // filter grouping
153*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(CPAREN);
154*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(STAR);             // present operator
155*7c478bd9Sstevel@tonic-gate 	tk.wordChars('+', '{');
156*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(OR);               // 'OR' operator
157*7c478bd9Sstevel@tonic-gate 	tk.wordChars('}', '~');
158*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(EQUAL);            // comparision operator
159*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(LESS);             // less operator
160*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(GREATER);          // greater operator
161*7c478bd9Sstevel@tonic-gate 	tk.ordinaryChar(APPROX);           // approx operator
162*7c478bd9Sstevel@tonic-gate 
163*7c478bd9Sstevel@tonic-gate 	// Begin parsing.
164*7c478bd9Sstevel@tonic-gate 
165*7c478bd9Sstevel@tonic-gate 	try {
166*7c478bd9Sstevel@tonic-gate 	    ParserRecord rec = parseFilter(tk, ev, locale, false, true);
167*7c478bd9Sstevel@tonic-gate 
168*7c478bd9Sstevel@tonic-gate 	    // Throw exception if anything occurs after the
169*7c478bd9Sstevel@tonic-gate 	    //  parsed expression.
170*7c478bd9Sstevel@tonic-gate 
171*7c478bd9Sstevel@tonic-gate 	    if (tk.nextToken() != StreamTokenizer.TT_EOF) {
172*7c478bd9Sstevel@tonic-gate 		throw
173*7c478bd9Sstevel@tonic-gate 		    new ServiceLocationException(
174*7c478bd9Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
175*7c478bd9Sstevel@tonic-gate 				"par_char_closing",
176*7c478bd9Sstevel@tonic-gate 				new Object[] {query});
177*7c478bd9Sstevel@tonic-gate 
178*7c478bd9Sstevel@tonic-gate 	    }
179*7c478bd9Sstevel@tonic-gate 
180*7c478bd9Sstevel@tonic-gate 	    // Merge in returns. Use OR operator so all returned
181*7c478bd9Sstevel@tonic-gate 	    //  values are merged in.
182*7c478bd9Sstevel@tonic-gate 
183*7c478bd9Sstevel@tonic-gate 	    mergeQueryReturns(ret, rec, OR);
184*7c478bd9Sstevel@tonic-gate 
185*7c478bd9Sstevel@tonic-gate 	} catch (IOException ex) {
186*7c478bd9Sstevel@tonic-gate 	    throw
187*7c478bd9Sstevel@tonic-gate 		new ServiceLocationException(
188*7c478bd9Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
189*7c478bd9Sstevel@tonic-gate 				"par_syn_err",
190*7c478bd9Sstevel@tonic-gate 				new Object[] {query});
191*7c478bd9Sstevel@tonic-gate 
192*7c478bd9Sstevel@tonic-gate 	}
193*7c478bd9Sstevel@tonic-gate     }
194*7c478bd9Sstevel@tonic-gate 
195*7c478bd9Sstevel@tonic-gate     //
196*7c478bd9Sstevel@tonic-gate     // Routines for dealing with parse returns record.
197*7c478bd9Sstevel@tonic-gate     //
198*7c478bd9Sstevel@tonic-gate 
199*7c478bd9Sstevel@tonic-gate     // Merge source to target. The target has already
200*7c478bd9Sstevel@tonic-gate     //  been precharged with ones that must match
201*7c478bd9Sstevel@tonic-gate     //  if the op is AND. If it's OR, then simply
202*7c478bd9Sstevel@tonic-gate     //  stuff them in.
203*7c478bd9Sstevel@tonic-gate 
204*7c478bd9Sstevel@tonic-gate     private static boolean
205*7c478bd9Sstevel@tonic-gate 	mergeQueryReturns(ParserRecord target,
206*7c478bd9Sstevel@tonic-gate 			  ParserRecord source,
207*7c478bd9Sstevel@tonic-gate 			  char op) {
208*7c478bd9Sstevel@tonic-gate 	Hashtable targetServices = target.services;
209*7c478bd9Sstevel@tonic-gate 	Hashtable sourceServices = source.services;
210*7c478bd9Sstevel@tonic-gate 	boolean eval;
211*7c478bd9Sstevel@tonic-gate 
212*7c478bd9Sstevel@tonic-gate 	if (op == AND) {
213*7c478bd9Sstevel@tonic-gate 	    eval = mergeTablesWithAnd(targetServices, sourceServices);
214*7c478bd9Sstevel@tonic-gate 
215*7c478bd9Sstevel@tonic-gate 	} else {
216*7c478bd9Sstevel@tonic-gate 	    eval = mergeTablesWithOr(targetServices, sourceServices);
217*7c478bd9Sstevel@tonic-gate 
218*7c478bd9Sstevel@tonic-gate 	}
219*7c478bd9Sstevel@tonic-gate 
220*7c478bd9Sstevel@tonic-gate 	Hashtable targetSigs = target.signatures;
221*7c478bd9Sstevel@tonic-gate 	Hashtable sourceSigs = source.signatures;
222*7c478bd9Sstevel@tonic-gate 
223*7c478bd9Sstevel@tonic-gate 	if (op == AND) {
224*7c478bd9Sstevel@tonic-gate 	    mergeTablesWithAnd(targetSigs, sourceSigs);
225*7c478bd9Sstevel@tonic-gate 
226*7c478bd9Sstevel@tonic-gate 	} else {
227*7c478bd9Sstevel@tonic-gate 	    mergeTablesWithOr(targetSigs, sourceSigs);
228*7c478bd9Sstevel@tonic-gate 
229*7c478bd9Sstevel@tonic-gate 	}
230*7c478bd9Sstevel@tonic-gate 
231*7c478bd9Sstevel@tonic-gate 	return eval;
232*7c478bd9Sstevel@tonic-gate     }
233*7c478bd9Sstevel@tonic-gate 
234*7c478bd9Sstevel@tonic-gate 
235*7c478bd9Sstevel@tonic-gate     // Merge tables by removing anything from target that isn't in source.
236*7c478bd9Sstevel@tonic-gate 
237*7c478bd9Sstevel@tonic-gate     private static boolean mergeTablesWithAnd(Hashtable target,
238*7c478bd9Sstevel@tonic-gate 					      Hashtable source) {
239*7c478bd9Sstevel@tonic-gate 
240*7c478bd9Sstevel@tonic-gate 	Enumeration en = target.keys();
241*7c478bd9Sstevel@tonic-gate 
242*7c478bd9Sstevel@tonic-gate 	// Remove any from target that aren't in source.
243*7c478bd9Sstevel@tonic-gate 
244*7c478bd9Sstevel@tonic-gate 	while (en.hasMoreElements()) {
245*7c478bd9Sstevel@tonic-gate 	    Object tkey = en.nextElement();
246*7c478bd9Sstevel@tonic-gate 
247*7c478bd9Sstevel@tonic-gate 	    if (source.get(tkey) == null) {
248*7c478bd9Sstevel@tonic-gate 		target.remove(tkey);
249*7c478bd9Sstevel@tonic-gate 
250*7c478bd9Sstevel@tonic-gate 	    }
251*7c478bd9Sstevel@tonic-gate 	}
252*7c478bd9Sstevel@tonic-gate 
253*7c478bd9Sstevel@tonic-gate 	// If there's nothing left, return false to indicate no further
254*7c478bd9Sstevel@tonic-gate 	//  evaluation needed.
255*7c478bd9Sstevel@tonic-gate 
256*7c478bd9Sstevel@tonic-gate 	if (target.size() <= 0) {
257*7c478bd9Sstevel@tonic-gate 	    return false;
258*7c478bd9Sstevel@tonic-gate 
259*7c478bd9Sstevel@tonic-gate 	}
260*7c478bd9Sstevel@tonic-gate 
261*7c478bd9Sstevel@tonic-gate 	return true;
262*7c478bd9Sstevel@tonic-gate     }
263*7c478bd9Sstevel@tonic-gate 
264*7c478bd9Sstevel@tonic-gate     // Merge tables by adding everything from source into target.
265*7c478bd9Sstevel@tonic-gate 
266*7c478bd9Sstevel@tonic-gate     private static boolean mergeTablesWithOr(Hashtable target,
267*7c478bd9Sstevel@tonic-gate 					     Hashtable source) {
268*7c478bd9Sstevel@tonic-gate 
269*7c478bd9Sstevel@tonic-gate 	Enumeration en = source.keys();
270*7c478bd9Sstevel@tonic-gate 
271*7c478bd9Sstevel@tonic-gate 	while (en.hasMoreElements()) {
272*7c478bd9Sstevel@tonic-gate 	    Object skey = en.nextElement();
273*7c478bd9Sstevel@tonic-gate 
274*7c478bd9Sstevel@tonic-gate 	    target.put(skey, source.get(skey));
275*7c478bd9Sstevel@tonic-gate 
276*7c478bd9Sstevel@tonic-gate 	}
277*7c478bd9Sstevel@tonic-gate 
278*7c478bd9Sstevel@tonic-gate 	return true;
279*7c478bd9Sstevel@tonic-gate     }
280*7c478bd9Sstevel@tonic-gate 
281*7c478bd9Sstevel@tonic-gate     //
282*7c478bd9Sstevel@tonic-gate     // Parsing for various productions.
283*7c478bd9Sstevel@tonic-gate     //
284*7c478bd9Sstevel@tonic-gate 
285*7c478bd9Sstevel@tonic-gate 
286*7c478bd9Sstevel@tonic-gate     // Parse the filter production.
287*7c478bd9Sstevel@tonic-gate 
288*7c478bd9Sstevel@tonic-gate     private static ParserRecord
289*7c478bd9Sstevel@tonic-gate 	parseFilter(StreamTokenizer tk,
290*7c478bd9Sstevel@tonic-gate 		    Parser.QueryEvaluator ev,
291*7c478bd9Sstevel@tonic-gate 		    Locale locale,
292*7c478bd9Sstevel@tonic-gate 		    boolean invert,
293*7c478bd9Sstevel@tonic-gate 		    boolean eval)
294*7c478bd9Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
295*7c478bd9Sstevel@tonic-gate 
296*7c478bd9Sstevel@tonic-gate 	ParserRecord ret = null;
297*7c478bd9Sstevel@tonic-gate 	int tok = tk.nextToken();
298*7c478bd9Sstevel@tonic-gate 
299*7c478bd9Sstevel@tonic-gate 	// Check for opening paren.
300*7c478bd9Sstevel@tonic-gate 
301*7c478bd9Sstevel@tonic-gate 	if (tok != OPAREN) {
302*7c478bd9Sstevel@tonic-gate 	    throw
303*7c478bd9Sstevel@tonic-gate 		new ServiceLocationException(
304*7c478bd9Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
305*7c478bd9Sstevel@tonic-gate 				"par_init_par",
306*7c478bd9Sstevel@tonic-gate 				new Object[0]);
307*7c478bd9Sstevel@tonic-gate 
308*7c478bd9Sstevel@tonic-gate 	}
309*7c478bd9Sstevel@tonic-gate 
310*7c478bd9Sstevel@tonic-gate 	// Parse inside.
311*7c478bd9Sstevel@tonic-gate 
312*7c478bd9Sstevel@tonic-gate 	tok = tk.nextToken();
313*7c478bd9Sstevel@tonic-gate 
314*7c478bd9Sstevel@tonic-gate 	// Check for a logical operator.
315*7c478bd9Sstevel@tonic-gate 
316*7c478bd9Sstevel@tonic-gate 	if (tok == AND || tok == OR) {
317*7c478bd9Sstevel@tonic-gate 	    ret = parseFilterlist(tk, ev, locale, (char)tok, invert, eval);
318*7c478bd9Sstevel@tonic-gate 
319*7c478bd9Sstevel@tonic-gate 	} else if (tok == NOT) {
320*7c478bd9Sstevel@tonic-gate 	    ret =  parseFilter(tk, ev, locale, !invert, eval);
321*7c478bd9Sstevel@tonic-gate 
322*7c478bd9Sstevel@tonic-gate 	} else if (tok == StreamTokenizer.TT_WORD) {
323*7c478bd9Sstevel@tonic-gate 	    tk.pushBack();
324*7c478bd9Sstevel@tonic-gate 	    ret =  parseItem(tk, ev, locale, invert, eval);
325*7c478bd9Sstevel@tonic-gate 
326*7c478bd9Sstevel@tonic-gate 	} else {
327*7c478bd9Sstevel@tonic-gate 
328*7c478bd9Sstevel@tonic-gate 	    // Since we've covered the ASCII character set, the only other
329*7c478bd9Sstevel@tonic-gate 	    //  thing that could be here is a nonASCII character. We push it
330*7c478bd9Sstevel@tonic-gate 	    //  back and deal with it in parseItem().
331*7c478bd9Sstevel@tonic-gate 
332*7c478bd9Sstevel@tonic-gate 	    tk.pushBack();
333*7c478bd9Sstevel@tonic-gate 	    ret = parseItem(tk, ev, locale, invert, eval);
334*7c478bd9Sstevel@tonic-gate 
335*7c478bd9Sstevel@tonic-gate 	}
336*7c478bd9Sstevel@tonic-gate 
337*7c478bd9Sstevel@tonic-gate 	tok = tk.nextToken();
338*7c478bd9Sstevel@tonic-gate 
339*7c478bd9Sstevel@tonic-gate 	// Check for closing paren.
340*7c478bd9Sstevel@tonic-gate 
341*7c478bd9Sstevel@tonic-gate 	if (tok != CPAREN) {
342*7c478bd9Sstevel@tonic-gate 	    throw
343*7c478bd9Sstevel@tonic-gate 		new ServiceLocationException(
344*7c478bd9Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
345*7c478bd9Sstevel@tonic-gate 				"par_final_par",
346*7c478bd9Sstevel@tonic-gate 				new Object[0]);
347*7c478bd9Sstevel@tonic-gate 
348*7c478bd9Sstevel@tonic-gate 	}
349*7c478bd9Sstevel@tonic-gate 
350*7c478bd9Sstevel@tonic-gate 	return ret;
351*7c478bd9Sstevel@tonic-gate     }
352*7c478bd9Sstevel@tonic-gate 
353*7c478bd9Sstevel@tonic-gate     // Parse a filterlist production.
354*7c478bd9Sstevel@tonic-gate 
355*7c478bd9Sstevel@tonic-gate     private static ParserRecord
356*7c478bd9Sstevel@tonic-gate 	parseFilterlist(StreamTokenizer tk,
357*7c478bd9Sstevel@tonic-gate 			Parser.QueryEvaluator ev,
358*7c478bd9Sstevel@tonic-gate 			Locale locale,
359*7c478bd9Sstevel@tonic-gate 			char op,
360*7c478bd9Sstevel@tonic-gate 			boolean invert,
361*7c478bd9Sstevel@tonic-gate 			boolean eval)
362*7c478bd9Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
363*7c478bd9Sstevel@tonic-gate 	boolean match;
364*7c478bd9Sstevel@tonic-gate 
365*7c478bd9Sstevel@tonic-gate 	ParserRecord mrex = null;
366*7c478bd9Sstevel@tonic-gate 
367*7c478bd9Sstevel@tonic-gate 	// Parse through the list of filters.
368*7c478bd9Sstevel@tonic-gate 
369*7c478bd9Sstevel@tonic-gate 	do {
370*7c478bd9Sstevel@tonic-gate 	    ParserRecord prex = null;
371*7c478bd9Sstevel@tonic-gate 
372*7c478bd9Sstevel@tonic-gate 	    if (op == AND) {
373*7c478bd9Sstevel@tonic-gate 
374*7c478bd9Sstevel@tonic-gate 		prex = parseFilter(tk, ev, locale, invert, eval);
375*7c478bd9Sstevel@tonic-gate 
376*7c478bd9Sstevel@tonic-gate 	    } else {
377*7c478bd9Sstevel@tonic-gate 
378*7c478bd9Sstevel@tonic-gate 		prex = parseFilter(tk, ev, locale, invert, eval);
379*7c478bd9Sstevel@tonic-gate 
380*7c478bd9Sstevel@tonic-gate 	    }
381*7c478bd9Sstevel@tonic-gate 
382*7c478bd9Sstevel@tonic-gate 	    // We need to start off with something.
383*7c478bd9Sstevel@tonic-gate 
384*7c478bd9Sstevel@tonic-gate 	    if (mrex == null) {
385*7c478bd9Sstevel@tonic-gate 		mrex = prex;
386*7c478bd9Sstevel@tonic-gate 
387*7c478bd9Sstevel@tonic-gate 	    } else {
388*7c478bd9Sstevel@tonic-gate 
389*7c478bd9Sstevel@tonic-gate 		// Merge in returns.
390*7c478bd9Sstevel@tonic-gate 
391*7c478bd9Sstevel@tonic-gate 		eval = mergeQueryReturns(mrex, prex, op);
392*7c478bd9Sstevel@tonic-gate 
393*7c478bd9Sstevel@tonic-gate 	    }
394*7c478bd9Sstevel@tonic-gate 
395*7c478bd9Sstevel@tonic-gate 	    // Look for ending paren.
396*7c478bd9Sstevel@tonic-gate 
397*7c478bd9Sstevel@tonic-gate 	    int tok = tk.nextToken();
398*7c478bd9Sstevel@tonic-gate 	    tk.pushBack();
399*7c478bd9Sstevel@tonic-gate 
400*7c478bd9Sstevel@tonic-gate 	    if (tok == CPAREN) {
401*7c478bd9Sstevel@tonic-gate 
402*7c478bd9Sstevel@tonic-gate 		return mrex;
403*7c478bd9Sstevel@tonic-gate 
404*7c478bd9Sstevel@tonic-gate 	    }
405*7c478bd9Sstevel@tonic-gate 
406*7c478bd9Sstevel@tonic-gate 	} while (true);
407*7c478bd9Sstevel@tonic-gate 
408*7c478bd9Sstevel@tonic-gate     }
409*7c478bd9Sstevel@tonic-gate 
410*7c478bd9Sstevel@tonic-gate     // Parse item.
411*7c478bd9Sstevel@tonic-gate 
412*7c478bd9Sstevel@tonic-gate     private static ParserRecord
413*7c478bd9Sstevel@tonic-gate 	parseItem(StreamTokenizer tk,
414*7c478bd9Sstevel@tonic-gate 		  Parser.QueryEvaluator ev,
415*7c478bd9Sstevel@tonic-gate 		  Locale locale,
416*7c478bd9Sstevel@tonic-gate 		  boolean invert,
417*7c478bd9Sstevel@tonic-gate 		  boolean eval)
418*7c478bd9Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
419*7c478bd9Sstevel@tonic-gate 
420*7c478bd9Sstevel@tonic-gate 	ParserRecord prex = new ParserRecord();
421*7c478bd9Sstevel@tonic-gate 	AttributeString attr = parseAttr(tk, locale);
422*7c478bd9Sstevel@tonic-gate 	char op = parseOp(tk);
423*7c478bd9Sstevel@tonic-gate 	Object value = null;
424*7c478bd9Sstevel@tonic-gate 
425*7c478bd9Sstevel@tonic-gate 	// If operator is PRESENT, then check whether
426*7c478bd9Sstevel@tonic-gate 	//  it's not really a wildcarded value. If the next
427*7c478bd9Sstevel@tonic-gate 	//  token isn't a closing paren, then it's
428*7c478bd9Sstevel@tonic-gate 	//  a wildcarded value.
429*7c478bd9Sstevel@tonic-gate 
430*7c478bd9Sstevel@tonic-gate 	if (op == PRESENT) {
431*7c478bd9Sstevel@tonic-gate 	    int tok = tk.nextToken();
432*7c478bd9Sstevel@tonic-gate 
433*7c478bd9Sstevel@tonic-gate 	    tk.pushBack();  // ...in any event...
434*7c478bd9Sstevel@tonic-gate 
435*7c478bd9Sstevel@tonic-gate 	    if ((char)tok != CPAREN) { // It's a wildcarded pattern...
436*7c478bd9Sstevel@tonic-gate 		op = EQUAL;
437*7c478bd9Sstevel@tonic-gate 		value = parseValue(tk, locale);
438*7c478bd9Sstevel@tonic-gate 
439*7c478bd9Sstevel@tonic-gate 		// Need to convert to a wildcarded pattern. Regardless
440*7c478bd9Sstevel@tonic-gate 		//  of type, since wildcard makes the type be a
441*7c478bd9Sstevel@tonic-gate 		//  string.
442*7c478bd9Sstevel@tonic-gate 
443*7c478bd9Sstevel@tonic-gate 		value =
444*7c478bd9Sstevel@tonic-gate 		    new AttributePattern(PRESENT + value.toString(), locale);
445*7c478bd9Sstevel@tonic-gate 
446*7c478bd9Sstevel@tonic-gate 	    }
447*7c478bd9Sstevel@tonic-gate 	} else {
448*7c478bd9Sstevel@tonic-gate 	    value = parseValue(tk, locale);
449*7c478bd9Sstevel@tonic-gate 
450*7c478bd9Sstevel@tonic-gate 	}
451*7c478bd9Sstevel@tonic-gate 
452*7c478bd9Sstevel@tonic-gate 	// Check for inappropriate pattern.
453*7c478bd9Sstevel@tonic-gate 
454*7c478bd9Sstevel@tonic-gate 	if (value instanceof AttributePattern &&
455*7c478bd9Sstevel@tonic-gate 	    ((AttributePattern)value).isWildcarded() &&
456*7c478bd9Sstevel@tonic-gate 	    op != EQUAL) {
457*7c478bd9Sstevel@tonic-gate 	    throw
458*7c478bd9Sstevel@tonic-gate 		new ServiceLocationException(
459*7c478bd9Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
460*7c478bd9Sstevel@tonic-gate 				"par_wild_op",
461*7c478bd9Sstevel@tonic-gate 				new Object[] {new Character(op)});
462*7c478bd9Sstevel@tonic-gate 
463*7c478bd9Sstevel@tonic-gate 	}
464*7c478bd9Sstevel@tonic-gate 
465*7c478bd9Sstevel@tonic-gate 	// Check for inappropriate boolean.
466*7c478bd9Sstevel@tonic-gate 
467*7c478bd9Sstevel@tonic-gate 	if ((value instanceof Boolean ||
468*7c478bd9Sstevel@tonic-gate 	    value instanceof Opaque) &&
469*7c478bd9Sstevel@tonic-gate 	    (op == GREATER || op == LESS)) {
470*7c478bd9Sstevel@tonic-gate 	    throw
471*7c478bd9Sstevel@tonic-gate 		new ServiceLocationException(
472*7c478bd9Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
473*7c478bd9Sstevel@tonic-gate 				"par_bool_op",
474*7c478bd9Sstevel@tonic-gate 				new Object[] {new Character(op)});
475*7c478bd9Sstevel@tonic-gate 
476*7c478bd9Sstevel@tonic-gate 	}
477*7c478bd9Sstevel@tonic-gate 
478*7c478bd9Sstevel@tonic-gate 	// Check for wrong operator with keyword.
479*7c478bd9Sstevel@tonic-gate 
480*7c478bd9Sstevel@tonic-gate 	if ((value == null || value.toString().length() <= 0) &&
481*7c478bd9Sstevel@tonic-gate 	    op != PRESENT) {
482*7c478bd9Sstevel@tonic-gate 	    throw
483*7c478bd9Sstevel@tonic-gate 		new ServiceLocationException(
484*7c478bd9Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
485*7c478bd9Sstevel@tonic-gate 				"par_key_op",
486*7c478bd9Sstevel@tonic-gate 				new Object[] {new Character(op)});
487*7c478bd9Sstevel@tonic-gate 	}
488*7c478bd9Sstevel@tonic-gate 
489*7c478bd9Sstevel@tonic-gate 	if (eval) {
490*7c478bd9Sstevel@tonic-gate 	    /*
491*7c478bd9Sstevel@tonic-gate 	     * Try and evaluate the query. If the evaluation failed and the
492*7c478bd9Sstevel@tonic-gate 	     * value was an Integer or Boolean try again after converting the
493*7c478bd9Sstevel@tonic-gate 	     * value to a String. This is because the value in the query will
494*7c478bd9Sstevel@tonic-gate 	     * be converted to an Integer or Boolean in preference to a String
495*7c478bd9Sstevel@tonic-gate 	     * even though the query starts out as a String.  Hence when an
496*7c478bd9Sstevel@tonic-gate 	     * attribute is registered with a String value that can equally be
497*7c478bd9Sstevel@tonic-gate 	     * parsed as a valid Integer or Boolean value the String will
498*7c478bd9Sstevel@tonic-gate 	     * almost always be parsed as an Integer or Boolean. This results
499*7c478bd9Sstevel@tonic-gate 	     * in the failing of the initial type check when performing the
500*7c478bd9Sstevel@tonic-gate 	     * query. By converting the value to a String there is another shot
501*7c478bd9Sstevel@tonic-gate 	     * at fulfulling the query.
502*7c478bd9Sstevel@tonic-gate 	     */
503*7c478bd9Sstevel@tonic-gate 	    if (!ev.evaluate(attr, op, value, invert, prex) &&
504*7c478bd9Sstevel@tonic-gate 		    !(value instanceof AttributeString)) {
505*7c478bd9Sstevel@tonic-gate 		ev.evaluate(attr,
506*7c478bd9Sstevel@tonic-gate 			    op,
507*7c478bd9Sstevel@tonic-gate 			    new AttributeString(
508*7c478bd9Sstevel@tonic-gate 				value.toString().trim(),
509*7c478bd9Sstevel@tonic-gate 				locale),
510*7c478bd9Sstevel@tonic-gate 			    invert,
511*7c478bd9Sstevel@tonic-gate 			    prex);
512*7c478bd9Sstevel@tonic-gate 	    }
513*7c478bd9Sstevel@tonic-gate 
514*7c478bd9Sstevel@tonic-gate 	}
515*7c478bd9Sstevel@tonic-gate 
516*7c478bd9Sstevel@tonic-gate 	return prex;
517*7c478bd9Sstevel@tonic-gate     }
518*7c478bd9Sstevel@tonic-gate 
519*7c478bd9Sstevel@tonic-gate     // Parse attribute tag.
520*7c478bd9Sstevel@tonic-gate 
521*7c478bd9Sstevel@tonic-gate     private static AttributeString parseAttr(StreamTokenizer tk, Locale locale)
522*7c478bd9Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
523*7c478bd9Sstevel@tonic-gate 
524*7c478bd9Sstevel@tonic-gate 	String str  = parsePotentialNonASCII(tk);
525*7c478bd9Sstevel@tonic-gate 
526*7c478bd9Sstevel@tonic-gate 	str =
527*7c478bd9Sstevel@tonic-gate 	    ServiceLocationAttribute.unescapeAttributeString(str, true);
528*7c478bd9Sstevel@tonic-gate 
529*7c478bd9Sstevel@tonic-gate 	return new AttributeString(str, locale);
530*7c478bd9Sstevel@tonic-gate     }
531*7c478bd9Sstevel@tonic-gate 
532*7c478bd9Sstevel@tonic-gate     // Parse attribute operator.
533*7c478bd9Sstevel@tonic-gate 
534*7c478bd9Sstevel@tonic-gate     private static char parseOp(StreamTokenizer tk)
535*7c478bd9Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
536*7c478bd9Sstevel@tonic-gate 
537*7c478bd9Sstevel@tonic-gate 	int tok = tk.nextToken();
538*7c478bd9Sstevel@tonic-gate 
539*7c478bd9Sstevel@tonic-gate 	// Identify operator
540*7c478bd9Sstevel@tonic-gate 
541*7c478bd9Sstevel@tonic-gate 	switch (tok) {
542*7c478bd9Sstevel@tonic-gate 
543*7c478bd9Sstevel@tonic-gate 	case EQUAL:
544*7c478bd9Sstevel@tonic-gate 
545*7c478bd9Sstevel@tonic-gate 	    // Is it present?
546*7c478bd9Sstevel@tonic-gate 
547*7c478bd9Sstevel@tonic-gate 	    tok = tk.nextToken();
548*7c478bd9Sstevel@tonic-gate 
549*7c478bd9Sstevel@tonic-gate 	    if (tok == STAR) {
550*7c478bd9Sstevel@tonic-gate 		return PRESENT;
551*7c478bd9Sstevel@tonic-gate 
552*7c478bd9Sstevel@tonic-gate 	    } else {
553*7c478bd9Sstevel@tonic-gate 		tk.pushBack();
554*7c478bd9Sstevel@tonic-gate 		return EQUAL;
555*7c478bd9Sstevel@tonic-gate 
556*7c478bd9Sstevel@tonic-gate 	    }
557*7c478bd9Sstevel@tonic-gate 
558*7c478bd9Sstevel@tonic-gate 	case APPROX: case GREATER: case LESS:
559*7c478bd9Sstevel@tonic-gate 
560*7c478bd9Sstevel@tonic-gate 	    // Need equals.
561*7c478bd9Sstevel@tonic-gate 
562*7c478bd9Sstevel@tonic-gate 	    if (tk.nextToken() != EQUAL) {
563*7c478bd9Sstevel@tonic-gate 		break;
564*7c478bd9Sstevel@tonic-gate 
565*7c478bd9Sstevel@tonic-gate 	    }
566*7c478bd9Sstevel@tonic-gate 
567*7c478bd9Sstevel@tonic-gate 	    if (tok == APPROX) {
568*7c478bd9Sstevel@tonic-gate 		tok = EQUAL;
569*7c478bd9Sstevel@tonic-gate 
570*7c478bd9Sstevel@tonic-gate 	    }
571*7c478bd9Sstevel@tonic-gate 
572*7c478bd9Sstevel@tonic-gate 	    return (char)tok;
573*7c478bd9Sstevel@tonic-gate 
574*7c478bd9Sstevel@tonic-gate 	default:
575*7c478bd9Sstevel@tonic-gate 	    break;
576*7c478bd9Sstevel@tonic-gate 
577*7c478bd9Sstevel@tonic-gate 	}
578*7c478bd9Sstevel@tonic-gate 
579*7c478bd9Sstevel@tonic-gate 	throw
580*7c478bd9Sstevel@tonic-gate 	    new ServiceLocationException(
581*7c478bd9Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
582*7c478bd9Sstevel@tonic-gate 				"par_comp_op",
583*7c478bd9Sstevel@tonic-gate 				new Object[0]);
584*7c478bd9Sstevel@tonic-gate 
585*7c478bd9Sstevel@tonic-gate     }
586*7c478bd9Sstevel@tonic-gate 
587*7c478bd9Sstevel@tonic-gate     // Parse expression value.
588*7c478bd9Sstevel@tonic-gate 
589*7c478bd9Sstevel@tonic-gate     private static Object parseValue(StreamTokenizer tk, Locale locale)
590*7c478bd9Sstevel@tonic-gate 	throws ServiceLocationException, IOException {
591*7c478bd9Sstevel@tonic-gate 
592*7c478bd9Sstevel@tonic-gate 	StringBuffer buf = new StringBuffer();
593*7c478bd9Sstevel@tonic-gate 
594*7c478bd9Sstevel@tonic-gate 	// Parse until the next closing paren.
595*7c478bd9Sstevel@tonic-gate 
596*7c478bd9Sstevel@tonic-gate 	do {
597*7c478bd9Sstevel@tonic-gate 	    int tok = tk.nextToken();
598*7c478bd9Sstevel@tonic-gate 
599*7c478bd9Sstevel@tonic-gate 	    if (tok == CPAREN) {
600*7c478bd9Sstevel@tonic-gate 		tk.pushBack();
601*7c478bd9Sstevel@tonic-gate 
602*7c478bd9Sstevel@tonic-gate 		Object o =
603*7c478bd9Sstevel@tonic-gate 		    ServiceLocationAttribute.evaluate(buf.toString().trim());
604*7c478bd9Sstevel@tonic-gate 
605*7c478bd9Sstevel@tonic-gate 		if (o instanceof String) {
606*7c478bd9Sstevel@tonic-gate 		    o = new AttributePattern((String)o, locale);
607*7c478bd9Sstevel@tonic-gate 
608*7c478bd9Sstevel@tonic-gate 		} else if (o instanceof byte[]) {
609*7c478bd9Sstevel@tonic-gate 		    o = new Opaque((byte[])o);
610*7c478bd9Sstevel@tonic-gate 
611*7c478bd9Sstevel@tonic-gate 		}
612*7c478bd9Sstevel@tonic-gate 
613*7c478bd9Sstevel@tonic-gate 		return o;
614*7c478bd9Sstevel@tonic-gate 
615*7c478bd9Sstevel@tonic-gate 	    } else if (tok != StreamTokenizer.TT_EOF) {
616*7c478bd9Sstevel@tonic-gate 
617*7c478bd9Sstevel@tonic-gate 		if (tok == StreamTokenizer.TT_WORD) {
618*7c478bd9Sstevel@tonic-gate 		    buf.append(tk.sval);
619*7c478bd9Sstevel@tonic-gate 
620*7c478bd9Sstevel@tonic-gate 		} else if (tok == StreamTokenizer.TT_NUMBER) {
621*7c478bd9Sstevel@tonic-gate 		    Assert.slpassert(false,
622*7c478bd9Sstevel@tonic-gate 				  "par_ntok",
623*7c478bd9Sstevel@tonic-gate 				  new Object[0]);
624*7c478bd9Sstevel@tonic-gate 
625*7c478bd9Sstevel@tonic-gate 		} else {
626*7c478bd9Sstevel@tonic-gate 		    buf.append((char)tok);
627*7c478bd9Sstevel@tonic-gate 
628*7c478bd9Sstevel@tonic-gate 		}
629*7c478bd9Sstevel@tonic-gate 
630*7c478bd9Sstevel@tonic-gate 	    } else {
631*7c478bd9Sstevel@tonic-gate 		throw
632*7c478bd9Sstevel@tonic-gate 		    new ServiceLocationException(
633*7c478bd9Sstevel@tonic-gate 				ServiceLocationException.PARSE_ERROR,
634*7c478bd9Sstevel@tonic-gate 				"par_qend",
635*7c478bd9Sstevel@tonic-gate 				new Object[0]);
636*7c478bd9Sstevel@tonic-gate 	    }
637*7c478bd9Sstevel@tonic-gate 	} while (true);
638*7c478bd9Sstevel@tonic-gate 
639*7c478bd9Sstevel@tonic-gate     }
640*7c478bd9Sstevel@tonic-gate 
641*7c478bd9Sstevel@tonic-gate     // NonASCII characters may be in the string. StreamTokenizer
642*7c478bd9Sstevel@tonic-gate     //  can't handle them as part of words, so we need to resort to
643*7c478bd9Sstevel@tonic-gate     //  this loop to handle it.
644*7c478bd9Sstevel@tonic-gate 
645*7c478bd9Sstevel@tonic-gate     private static String parsePotentialNonASCII(StreamTokenizer tk)
646*7c478bd9Sstevel@tonic-gate 	throws IOException {
647*7c478bd9Sstevel@tonic-gate 
648*7c478bd9Sstevel@tonic-gate 	StringBuffer buf = new StringBuffer();
649*7c478bd9Sstevel@tonic-gate 
650*7c478bd9Sstevel@tonic-gate 	do {
651*7c478bd9Sstevel@tonic-gate 
652*7c478bd9Sstevel@tonic-gate 	    int tok = tk.nextToken();
653*7c478bd9Sstevel@tonic-gate 
654*7c478bd9Sstevel@tonic-gate 	    if (tok == StreamTokenizer.TT_WORD) {
655*7c478bd9Sstevel@tonic-gate 		buf.append(tk.sval);
656*7c478bd9Sstevel@tonic-gate 
657*7c478bd9Sstevel@tonic-gate 	    } else if (((char)tok >= NONASCII_LOWER) &&
658*7c478bd9Sstevel@tonic-gate 		       ((char)tok <= NONASCII_UPPER)) {
659*7c478bd9Sstevel@tonic-gate 		buf.append((char)tok);
660*7c478bd9Sstevel@tonic-gate 
661*7c478bd9Sstevel@tonic-gate 	    } else {
662*7c478bd9Sstevel@tonic-gate 		tk.pushBack();
663*7c478bd9Sstevel@tonic-gate 		break;
664*7c478bd9Sstevel@tonic-gate 
665*7c478bd9Sstevel@tonic-gate 	    }
666*7c478bd9Sstevel@tonic-gate 
667*7c478bd9Sstevel@tonic-gate 	} while (true);
668*7c478bd9Sstevel@tonic-gate 
669*7c478bd9Sstevel@tonic-gate 	return buf.toString();
670*7c478bd9Sstevel@tonic-gate     }
671*7c478bd9Sstevel@tonic-gate }
672