1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * ident	"%Z%%M%	%I%	%E% SMI"
24  *
25  * Copyright (c) 1999 by Sun Microsystems, Inc.
26  * All rights reserved.
27  *
28  */
29 
30 //  SCCS Status:      %W%	%G%
31 //  SLPV1SSrvMsg.java: SLPv1 server side service rqst/reply.
32 //  Author:           James Kempf
33 //  Created On:       Thu Sep 10 15:33:58 1998
34 //  Last Modified By: James Kempf
35 //  Last Modified On: Fri Nov  6 14:03:00 1998
36 //  Update Count:     41
37 //
38 
39 
40 package com.sun.slp;
41 
42 import java.util.*;
43 import java.io.*;
44 
45 
46 /**
47  * The SLPV1SSrvMsg class models the SLP server side service request message.
48  *
49  * @version %R%.%L% %D%
50  * @author James Kempf
51  */
52 
53 class SLPV1SSrvMsg extends SSrvMsg {
54 
55     // For eating whitespace.
56 
57     final static char SPACE = ' ';
58 
59     // Comma for list parsing.
60 
61     final static char COMMA = ',';
62 
63     // Logical operators.
64 
65     final static char OR_OP = '|';
66     final static char AND_OP = '&';
67 
68     // Logical operator corner case needs this.
69 
70     final static char HASH = '#';
71 
72     // Comparison/Assignment operators.
73 
74     final static char EQUAL_OP = '=';
75     final static char NOT_OP = '!';
76     final static char LESS_OP = '<';
77     final static char GREATER_OP = '>';
78     final static char GEQUAL_OP = 'g';
79     final static char LEQUAL_OP = 'l';
80 
81     // Parens.
82 
83     final static char OPEN_PAREN = '(';
84     final static char CLOSE_PAREN = ')';
85 
86     // LDAP present operator
87 
88     final static char PRESENT = '*';
89 
90     // Wildcard operator.
91 
92     final static String WILDCARD = "*";
93 
94     // Character code for parsing.
95 
96     String charCode = IANACharCode.UTF8;
97 
98     // For creating a null reply.
99 
100     protected SLPV1SSrvMsg() {}
101 
102     // Construct a SLPV1SSrvMsg from the input stream.
103 
104     SLPV1SSrvMsg(SrvLocHeader hdr, DataInputStream dis)
105 	throws ServiceLocationException, IOException {
106 	super(hdr, dis);
107 
108     }
109 
110     // Construct an empty SLPV1SSrvMsg, for monolingual off.
111 
112     static SrvLocMsg makeEmptyReply(SLPHeaderV1 hdr)
113 	throws ServiceLocationException {
114 
115 	SLPV1SSrvMsg msg = new SLPV1SSrvMsg();
116 	msg.hdr = hdr;
117 
118 	msg.makeReply(new Hashtable(), null);
119 
120 	return msg;
121 
122     }
123 
124     // Initialize the message from the input stream.
125 
126     void initialize(DataInputStream dis)
127 	throws ServiceLocationException, IOException {
128 
129 	SLPHeaderV1 hdr = (SLPHeaderV1)getHeader();
130 	StringBuffer buf = new StringBuffer();
131 
132 	// First get the previous responder.
133 
134 	hdr.parsePreviousRespondersIn(dis);
135 
136 	// Now get the raw query.
137 
138 	hdr.getString(buf, dis);
139 
140 	String rq = buf.toString();
141 
142 	// Parse the raw query to pull out the service type, scope,
143 	//  and query.
144 
145 	StringTokenizer st = new StringTokenizer(rq, "/", true);
146 
147 	try {
148 
149 	    String type =
150 		Defaults.SERVICE_PREFIX + ":" +
151 		st.nextToken().trim().toLowerCase() + ":";
152 
153 	    serviceType =
154 		hdr.checkServiceType(type);
155 
156 	    st.nextToken();  // get rid of slash.
157 
158 	    // Get the scope.
159 
160 	    String scope = st.nextToken().trim().toLowerCase();
161 
162 	    // Special case if scope is empty (meaning the next
163 	    //  token will be a slash).
164 
165 	    if (scope.equals("/")) {
166 		scope = "";
167 
168 	    } else {
169 
170 		st.nextToken();  // get rid of slash.
171 
172 		if (scope.length() > 0) {
173 
174 		    // Validate the scope name.
175 
176 		    hdr.validateScope(scope);
177 		}
178 	    }
179 
180 	    // Set up scopes vector.
181 
182 	    hdr.scopes = new Vector();
183 
184 	    // Substitute default scope here.
185 
186 	    if (scope.length() <= 0) {
187 		scope = Defaults.DEFAULT_SCOPE;
188 
189 	    }
190 
191 	    hdr.scopes.addElement(scope.toLowerCase().trim());
192 
193 	    // Parsing the query is complicated by opaques having slashes.
194 
195 	    String q = "";
196 
197 	    while (st.hasMoreTokens()) {
198 		q = q + st.nextToken();
199 
200 	    }
201 
202 	    // Drop off the final backslash, error if none.
203 
204 	    if (!q.endsWith("/")) {
205 		throw
206 		    new ServiceLocationException(
207 				ServiceLocationException.PARSE_ERROR,
208 				"v1_query_error",
209 				new Object[] {rq});
210 	    }
211 
212 	    query = q.substring(0, q.length()-1);
213 
214 	    // Save header char code for parsing.
215 
216 	    charCode = hdr.charCode;
217 
218 	    // Convert the query into a V2 query.
219 
220 	    convertQuery();
221 
222 	    // If the query is for "service:directory-agent", then we
223 	    //  mark it as having been multicast, because that is the
224 	    //  only kind of multicast that we accept for SLPv1. Anybody
225 	    //  who unicasts this to us will time out.
226 
227 	    if (serviceType.equals(Defaults.DA_SERVICE_TYPE.toString())) {
228 		hdr.mcast = true;
229 
230 	    }
231 
232 	    // Construct description.
233 
234 	    hdr.constructDescription("SrvRqst",
235 				     "        service type=``" +
236 				     serviceType + "''\n" +
237 				     "        query=``" +
238 				     query + "''");
239 
240 	}  catch (NoSuchElementException ex) {
241 	    throw
242 		new ServiceLocationException(
243 				ServiceLocationException.PARSE_ERROR,
244 				"v1_query_error",
245 				new Object[] {rq});
246 	}
247     }
248 
249     // Make a reply message.
250 
251     SrvLocMsg makeReply(Hashtable urltable,
252 			Hashtable URLSignatures)
253 	throws ServiceLocationException {
254 
255 	SLPHeaderV1 hdr =
256 	    ((SLPHeaderV1)getHeader()).makeReplyHeader();
257 
258 	ByteArrayOutputStream baos = new ByteArrayOutputStream();
259 
260 	// Edit out abstract types and nonService: URLs.
261 
262 	Enumeration en = urltable.keys();
263 	Vector urls = new Vector();
264 
265 	while (en.hasMoreElements()) {
266 	    ServiceURL surl = (ServiceURL)en.nextElement();
267 
268 	    // Reject if abstract type or nonservice: URL.
269 
270 	    ServiceType type = surl.getServiceType();
271 
272 	    if (!type.isAbstractType() && type.isServiceURL()) {
273 		urls.addElement(surl);
274 
275 	    }
276 	}
277 
278 	hdr.iNumReplies = urls.size();
279 	// keep this info so SAs can drop 0 replies
280 
281 	int n = urls.size();
282 
283 	// Write out the size of the list.
284 
285 	hdr.putInt(n, baos);
286 
287 	en = urls.elements();
288 
289 	// Write out the size of the list.
290 
291 	while (en.hasMoreElements()) {
292 	    ServiceURL surl = (ServiceURL)en.nextElement();
293 
294 	    hdr.parseServiceURLOut(surl, true, baos);
295 
296 	}
297 
298 	// We ignore the signatures because we only do V1 compatibility
299 	//  for nonprotected scopes.
300 
301 	hdr.payload = baos.toByteArray();
302 
303 	hdr.constructDescription("SrvRply",
304 				 "        service URLs=``" + urls + "''\n");
305 
306 	return hdr;
307 
308     }
309 
310     // Convert the query to a V2 query.
311 
312     void convertQuery()
313 	throws ServiceLocationException {
314 
315 	// Check for empty query.
316 
317 	query = query.trim();
318 
319 	if (query.length() <= 0) {
320 	    return;
321 
322 	}
323 
324 	// Check for query join.
325 
326 	if (!(query.startsWith("(") && query.endsWith(")"))) {
327 
328 	    // Rewrite to a standard query.
329 
330 	    query = rewriteQueryJoin(query);
331 
332 	}
333 
334 	// Now rewrite the query into v2 format.
335 
336 	query = rewriteQuery(query);
337     }
338 
339 
340     // Rewrite a query join as a conjunction.
341 
342     private String rewriteQueryJoin(String query)
343 	throws ServiceLocationException {
344 
345 	// Turn infix expression into prefix.
346 
347 	StringBuffer sbuf = new StringBuffer();
348 	StringTokenizer tk = new StringTokenizer(query, ",", true);
349 	boolean lastTokComma = true;
350 	int numEx = 0;
351 
352 	while (tk.hasMoreElements()) {
353 	    String exp = tk.nextToken().trim();
354 
355 	    if (exp.equals(",")) {
356 		if (lastTokComma) {
357 		    throw
358 			new ServiceLocationException(
359 				ServiceLocationException.PARSE_ERROR,
360 				"v1_query_error",
361 				new Object[] {query});
362 
363 		} else {
364 		    lastTokComma = true;
365 		}
366 
367 	    } else {
368 		lastTokComma = false;
369 
370 		if (exp.length() <= 0) {
371 		    throw
372 			new ServiceLocationException(
373 				ServiceLocationException.PARSE_ERROR,
374 				"v1_query_error",
375 				new Object[] {query});
376 
377 		}
378 
379 		// Put in parens
380 
381 		sbuf.append("(");
382 		sbuf.append(exp);
383 		sbuf.append(")");
384 
385 		numEx++;
386 	    }
387 	}
388 
389 	if (lastTokComma || numEx == 0) {
390 	    throw
391 		new ServiceLocationException(
392 				ServiceLocationException.PARSE_ERROR,
393 				"v1_query_error",
394 				new Object[] {query});
395 
396 	}
397 
398 	if (numEx > 1) {
399 	    sbuf.insert(0, "(&");
400 	    sbuf.append(")");
401 
402 	}
403 
404 	return sbuf.toString();
405     }
406 
407     // Rewrite a v1 query into v2 format. This includes character escaping.
408 
409     private String rewriteQuery(String whereList)
410 	throws ServiceLocationException {
411 
412 	// Parse a logical expression.
413 
414 	StreamTokenizer tk =
415 	    new StreamTokenizer(new StringReader(whereList));
416 
417 	tk.resetSyntax();  		// make all chars ordinary...
418 	tk.whitespaceChars('\000','\037');
419 	tk.ordinaryChar(SPACE);		// but beware of embedded whites...
420 	tk.wordChars('!', '%');
421 	tk.ordinaryChar(AND_OP);
422 	tk.wordChars('\'', '\'');
423 	tk.ordinaryChar(OPEN_PAREN);
424 	tk.ordinaryChar(CLOSE_PAREN);
425 	tk.wordChars('*', '{');
426 	tk.ordinaryChar(OR_OP);
427 	tk.wordChars('}', '~');
428 
429 	// Initialize parse tables in terminal.
430 
431 	tk.ordinaryChar(EQUAL_OP);
432 	tk.ordinaryChar(NOT_OP);
433 	tk.ordinaryChar(LESS_OP);
434 	tk.ordinaryChar(GREATER_OP);
435 
436 	StringBuffer buf = new StringBuffer();
437 
438 
439 	// Parse through the expression.
440 
441 	try {
442 	    parseInternal(tk, buf, true);
443 
444 	} catch (IOException ex) {
445 	    throw
446 		new ServiceLocationException(
447 				ServiceLocationException.PARSE_ERROR,
448 				"v1_query_error",
449 				new Object[] {query});
450 
451 	}
452 
453 	return buf.toString();
454     }
455 
456     // Do the actual parsing, using the passed-in stream tokenizer.
457 
458     private void
459 	parseInternal(StreamTokenizer tk, StringBuffer buf, boolean start)
460 	throws ServiceLocationException, IOException {
461 
462 	int tok = 0;
463 	boolean ret = true;
464 
465 	do {
466 	    tok = eatWhite(tk);
467 
468 	    // We should be at the beginning a parenthesized
469 	    //  where list.
470 
471 	    if (tok == OPEN_PAREN) {
472 
473 		// Get the next token. Eat whitespace in the process.
474 
475 		tok = eatWhite(tk);
476 
477 		// If it's a logOp, then process as a logical expression.
478 		//  This handles the following nasty case:
479 		//
480 		//  	(&#44;&#45==the rest of it)
481 
482 		int logOp = tok;
483 
484 		if (logOp == AND_OP) {
485 
486 		    // Need to check for escape as first thing.
487 
488 		    tok = tk.nextToken();
489 		    String str = tk.sval; // not used if token not a string...
490 		    tk.pushBack();
491 
492 		    if (tok == StreamTokenizer.TT_WORD) {
493 
494 			if (str.charAt(0) != HASH) {
495 			    parseLogicalExpression(logOp, tk, buf);
496 
497 			} else {
498 			    parse(tk, buf, true);
499 					// cause we can't push back twice
500 
501 			}
502 
503 		    } else {
504 			parseLogicalExpression(logOp, tk, buf);
505 
506 		    }
507 
508 		    break;
509 
510 		} else if (logOp == OR_OP) {
511 
512 		    parseLogicalExpression(logOp, tk, buf);
513 
514 		    break;
515 
516 		} else {
517 
518 		    // It's a terminal expression. Push back the last token
519 		    //  and parse the terminal.
520 
521 		    tk.pushBack();
522 
523 		    parse(tk, buf, false);
524 
525 		    break;
526 
527 		}
528 
529 	    } else {
530 		throw
531 		    new ServiceLocationException(
532 				ServiceLocationException.PARSE_ERROR,
533 				"v1_query_error",
534 				new Object[] {query});
535 	    }
536 
537 	} while (true);
538 
539 	// Since terminals are allowed alone at the top level,
540 	//  we need to check here whether anything else is
541 	//  in the query.
542 
543 	if (start) {
544 
545 	    tok = eatWhite(tk);
546 
547 	    if (tok != StreamTokenizer.TT_EOF) {
548 
549 		// The line should have ended by now.
550 
551 		throw
552 		    new ServiceLocationException(
553 				ServiceLocationException.PARSE_ERROR,
554 				"v1_query_error",
555 				new Object[] {query});
556 	    }
557 	}
558 
559     }
560 
561     // Rewrite a logical expression.
562 
563     private void
564 	parseLogicalExpression(int logOp, StreamTokenizer tk, StringBuffer buf)
565 	throws ServiceLocationException, IOException {
566 
567 	// Append paren and operator to buffer.
568 
569 	buf.append((char)OPEN_PAREN);
570 	buf.append((char)logOp);
571 
572 	int tok = 0;
573 
574 	do {
575 
576 	    tok = eatWhite(tk);
577 
578 	    if (tok == OPEN_PAREN) {
579 
580 		// So parseInternal() sees a parenthesized list.
581 
582 		tk.pushBack();
583 
584 		// Go back to parseInternal.
585 
586 		parseInternal(tk, buf, false);
587 
588 	    } else if (tok == CLOSE_PAREN) {
589 
590 		// Append the character to the buffer and return.
591 
592 		buf.append((char)tok);
593 
594 		return;
595 
596 	    } else {
597 		throw
598 		    new ServiceLocationException(
599 				ServiceLocationException.PARSE_ERROR,
600 				"v1_query_error",
601 				new Object[] {query});
602 	    }
603 
604 	} while (tok != StreamTokenizer.TT_EOF);
605 
606 	// Error if we've not caught ourselves before this.
607 
608 	throw
609 	    new ServiceLocationException(
610 				ServiceLocationException.PARSE_ERROR,
611 				"v1_query_error",
612 				new Object[] {query});
613     }
614 
615     // Parse a terminal. Opening paren has been got.
616 
617     private void parse(StreamTokenizer tk,
618 		       StringBuffer buf,
619 		       boolean firstEscaped)
620 	throws ServiceLocationException, IOException {
621 
622 	String tag = "";
623 	int tok = 0;
624 
625 	tok = eatWhite(tk);
626 
627 	// Gather the tag and value.
628 
629 	if (tok != StreamTokenizer.TT_WORD) {
630 	    throw
631 		new ServiceLocationException(
632 				ServiceLocationException.PARSE_ERROR,
633 				"v1_query_error",
634 				new Object[] {query});
635 	}
636 
637 	// Parse the tag.
638 
639 	tag = parseTag(tk, firstEscaped);
640 
641 	if (tag.length() <= 0) {
642 	    throw
643 		new ServiceLocationException(
644 				ServiceLocationException.PARSE_ERROR,
645 				"v1_query_error",
646 				new Object[] {query});
647 	}
648 
649 	// Unescape tag.
650 
651 	tag = ServiceLocationAttributeV1.unescapeAttributeString(tag,
652 								 charCode);
653 
654 	// Now escape in v2 format,
655 
656 	tag = ServiceLocationAttribute.escapeAttributeString(tag, true);
657 
658 	// Parse the operator.
659 
660 	char compOp = parseOperator(tk);
661 
662 	// If this was a keyword operator, then add present
663 	// operator and closing paren and return.
664 
665 	if (compOp == PRESENT) {
666 	    buf.append(OPEN_PAREN);
667 	    buf.append(tag);
668 	    buf.append(EQUAL_OP);
669 	    buf.append(PRESENT);
670 	    buf.append(CLOSE_PAREN);
671 	    return;
672 
673 	}
674 
675 	// Parse value by reading up to the next close paren.
676 	//  Returned value will be in v2 format.
677 
678 	String valTok = parseValue(tk);
679 
680 	// Construct the comparision depending on the operator.
681 
682 	if (compOp == NOT_OP) {
683 
684 	    // If the value is an integer, we can construct a query
685 	    //  that will exclude the number.
686 
687 	    try {
688 
689 		int n = Integer.parseInt(valTok);
690 
691 		// Bump the integer up and down to catch numbers on both
692 		//  sides of the required number. Be careful not to
693 		//  overstep bounds.
694 
695 		if (n < Integer.MAX_VALUE) {
696 		    buf.append(OPEN_PAREN);
697 		    buf.append(tag);
698 		    buf.append(GREATER_OP);
699 		    buf.append(EQUAL_OP);
700 		    buf.append(n + 1);
701 		    buf.append(CLOSE_PAREN);
702 
703 		}
704 
705 		if (n > Integer.MIN_VALUE) {
706 		    buf.append(OPEN_PAREN);
707 		    buf.append(tag);
708 		    buf.append(LESS_OP);
709 		    buf.append(EQUAL_OP);
710 		    buf.append(n - 1);
711 		    buf.append(CLOSE_PAREN);
712 
713 		}
714 
715 		if ((n < Integer.MAX_VALUE) && (n > Integer.MIN_VALUE)) {
716 		    buf.insert(0, OR_OP);
717 		    buf.insert(0, OPEN_PAREN);
718 		    buf.append(CLOSE_PAREN);
719 
720 		}
721 
722 	    } catch (NumberFormatException ex) {
723 
724 		// It's not an integer. We can construct a query expression
725 		// that will not always work. The query rules out advertisments
726 		// where the attribute value doesn't match and there are
727 		// no other attributes or values, and advertisements
728 		// that don't contain the attribute, but it doesn't rule out
729 		// a multivalued attribute with other values or if there
730 		// are other attributes. The format of the query is:
731 		// "(&(<tag>=*)(!(<tag>=<value>))).
732 
733 		buf.append(OPEN_PAREN);
734 		buf.append(AND_OP);
735 		buf.append(OPEN_PAREN);
736 		buf.append(tag);
737 		buf.append(EQUAL_OP);
738 		buf.append(PRESENT);
739 		buf.append(CLOSE_PAREN);
740 		buf.append(OPEN_PAREN);
741 		buf.append(NOT_OP);
742 		buf.append(OPEN_PAREN);
743 		buf.append(tag);
744 		buf.append(EQUAL_OP);
745 		buf.append(valTok);
746 		buf.append(CLOSE_PAREN);
747 		buf.append(CLOSE_PAREN);
748 		buf.append(CLOSE_PAREN);
749 
750 	    }
751 
752 	} else if ((compOp == LESS_OP) || (compOp == GREATER_OP)) {
753 
754 	    int n = 0;
755 
756 	    try {
757 
758 		n = Integer.parseInt(valTok);
759 
760 	    } catch (NumberFormatException ex) {
761 
762 		// It's a parse error here.
763 
764 		throw
765 		    new ServiceLocationException(
766 				ServiceLocationException.PARSE_ERROR,
767 				"v1_query_error",
768 				new Object[] {query});
769 
770 	    }
771 
772 	    // We don't attempt to handle something that would cause
773 	    // arithmetic overflow.
774 
775 	    if ((n == Integer.MAX_VALUE) || (n == Integer.MIN_VALUE)) {
776 		throw
777 		    new ServiceLocationException(
778 				ServiceLocationException.PARSE_ERROR,
779 				"v1_query_error",
780 				new Object[] {query});
781 
782 	    }
783 
784 	    // Construct a query that includes everything
785 	    //  to the correct side.
786 
787 	    buf.append(OPEN_PAREN);
788 	    buf.append(tag);
789 
790 	    if (compOp == LESS_OP) {
791 		buf.append(LESS_OP);
792 		buf.append(EQUAL_OP);
793 		buf.append(n - 1);
794 
795 	    } else {
796 		buf.append(GREATER_OP);
797 		buf.append(EQUAL_OP);
798 		buf.append(n + 1);
799 
800 	    }
801 
802 	    buf.append(CLOSE_PAREN);
803 
804 	} else {
805 
806 	    // Simple, single operator. Just add it with the
807 	    //  value.
808 
809 	    buf.append(OPEN_PAREN);
810 	    buf.append(tag);
811 
812 	    // Need to distinguish less and greater equal.
813 
814 	    if (compOp == LEQUAL_OP) {
815 		buf.append(LESS_OP);
816 		buf.append(EQUAL_OP);
817 
818 	    } else if (compOp == GEQUAL_OP) {
819 		buf.append(GREATER_OP);
820 		buf.append(EQUAL_OP);
821 
822 	    } else {
823 		buf.append(compOp);
824 
825 	    }
826 
827 	    buf.append(valTok);
828 	    buf.append(CLOSE_PAREN);
829 
830 	}
831 
832     }
833 
834     // Gather tokens with embedded whitespace and return.
835 
836     private String parseTag(StreamTokenizer tk, boolean ampStart)
837 	throws ServiceLocationException, IOException {
838 
839 	String value = "";
840 
841 	// Take care of corner case here.
842 
843 	if (ampStart) {
844 	    value = value +"&";
845 	    ampStart = false;
846 	}
847 
848 	do {
849 
850 	    if (tk.ttype == StreamTokenizer.TT_WORD) {
851 		value += tk.sval;
852 
853 	    } else if ((char)tk.ttype == SPACE) {
854 		value = value + " ";
855 
856 	    } else if ((char)tk.ttype == AND_OP) {
857 		value = value + "&";
858 
859 	    } else {
860 		break;
861 
862 	    }
863 	    tk.nextToken();
864 
865 	} while (true);
866 
867 	return value.trim();  // removes trailing whitespace...
868     }
869 
870     private char parseOperator(StreamTokenizer tk)
871 	throws ServiceLocationException, IOException {
872 
873 	int tok = tk.ttype;
874 
875 	// If the token is a close paren, then this was a keyword
876 	// (e.g. "(foo)". Return the present operator.
877 
878 	if ((char)tok == CLOSE_PAREN) {
879 	    return PRESENT;
880 
881 	}
882 
883 	if (tok != EQUAL_OP && tok != NOT_OP &&
884 	    tok != LESS_OP && tok != GREATER_OP) {
885 
886 	    throw
887 		new ServiceLocationException(
888 				ServiceLocationException.PARSE_ERROR,
889 				"v1_query_error",
890 				new Object[] {query});
891 
892 	}
893 
894 	char compOp = (char)tok;
895 
896 	// Get the next token.
897 
898 	tok = tk.nextToken();
899 
900 	// Look for dual character operators.
901 
902 	if ((char)tok == EQUAL_OP) {
903 
904 	    // Here, we can have either "!=", "<=", ">=", or "==".
905 	    //  Anything else is wrong.
906 
907 	    if (compOp != LESS_OP && compOp != GREATER_OP &&
908 		compOp != EQUAL_OP && compOp != NOT_OP) {
909 		throw
910 		    new ServiceLocationException(
911 				ServiceLocationException.PARSE_ERROR,
912 				"v1_query_error",
913 				new Object[] {query});
914 	    }
915 
916 	    // Assign the right dual operator.
917 
918 	    if (compOp == LESS_OP) {
919 		compOp = LEQUAL_OP;
920 
921 	    } else if (compOp == GREATER_OP) {
922 		compOp = GEQUAL_OP;
923 
924 	    }
925 
926 	} else if (compOp != LESS_OP && compOp != GREATER_OP) {
927 
928 	    // Error if the comparison operator was something other
929 	    //  than ``<'' or ``>'' and there is no equal. This
930 	    //  rules out ``!'' or ``='' alone.
931 
932 	    throw
933 		new ServiceLocationException(
934 				ServiceLocationException.PARSE_ERROR,
935 				"v1_query_error",
936 				new Object[] {query});
937 
938 	} else {
939 
940 	    // Push back the last token if it wasn't a two character operator.
941 
942 	    tk.pushBack();
943 
944 	}
945 
946 	return compOp;
947     }
948 
949 
950     private String parseValue(StreamTokenizer tk)
951 	throws ServiceLocationException, IOException {
952 
953 	int tok = 0;
954 	StringBuffer valTok = new StringBuffer();
955 
956 	// Eat leading whitespace.
957 
958 	tok = eatWhite(tk);
959 
960 	// If the first value is a paren, then we've got an
961 	//  opaque.
962 
963 	if ((char)tok == OPEN_PAREN) {
964 
965 	    valTok.append("(");
966 
967 	    // Collect all tokens up to the closing paren.
968 
969 	    do {
970 
971 		tok = tk.nextToken();
972 
973 		// It's a closing paren. break out of the loop.
974 
975 		if ((char)tok == CLOSE_PAREN) {
976 		    valTok.append(")");
977 		    break;
978 
979 		} else if ((char)tok == EQUAL_OP) {
980 		    valTok.append("=");
981 
982 		} else if (tok == StreamTokenizer.TT_WORD) {
983 		    valTok.append(tk.sval);
984 
985 		} else {
986 		    throw
987 			new ServiceLocationException(
988 				ServiceLocationException.PARSE_ERROR,
989 				"v1_query_error",
990 				new Object[] {query});
991 		}
992 
993 	    } while (true);
994 
995 
996 	    // Eat whitespace until closing paren.
997 
998 	    tok = eatWhite(tk);
999 
1000 	    if ((char)tok != CLOSE_PAREN) {
1001 		throw
1002 		    new ServiceLocationException(
1003 				ServiceLocationException.PARSE_ERROR,
1004 				"v1_query_error",
1005 				new Object[] {query});
1006 
1007 	    }
1008 
1009 	} else {
1010 
1011 	    // Error if just a closed paren.
1012 
1013 	    if (tok == CLOSE_PAREN) {
1014 		throw
1015 		    new ServiceLocationException(
1016 				ServiceLocationException.PARSE_ERROR,
1017 				"v1_query_error",
1018 				new Object[] {query});
1019 
1020 	    }
1021 
1022 	    do {
1023 
1024 		// Append the token if a WORD
1025 
1026 		if (tok == StreamTokenizer.TT_WORD) {
1027 		    valTok.append(tk.sval);
1028 
1029 		} else if ((tok != StreamTokenizer.TT_EOF) &&
1030 			   (tok != StreamTokenizer.TT_EOL) &&
1031 			   (tok != CLOSE_PAREN)) {
1032 
1033 		    // Otherwise, it's a token char, so append.
1034 
1035 		    valTok.append((char)tok);
1036 
1037 		}
1038 
1039 		tok = tk.nextToken();
1040 
1041 	    } while (tok != CLOSE_PAREN);
1042 	}
1043 
1044 	// If a wildcard, remove wildcard stars here for later re-insertion.
1045 
1046 	String strval = valTok.toString().trim();
1047 	boolean wildstart = false;
1048 	boolean wildend = false;
1049 
1050 	if (strval.startsWith(WILDCARD)) {
1051 	    wildstart = true;
1052 	    strval = strval.substring(1, strval.length());
1053 
1054 	}
1055 
1056 	if (strval.endsWith(WILDCARD)) {
1057 	    wildend = true;
1058 	    strval = strval.substring(0, strval.length()-1);
1059 
1060 	}
1061 
1062 	// Evaluate the value.
1063 
1064 	Object val =
1065 	    ServiceLocationAttributeV1.evaluate(strval, charCode);
1066 
1067 	// Now convert to v2 format, and return.
1068 
1069 	if (val instanceof String) {
1070 	    strval =
1071 		ServiceLocationAttribute.escapeAttributeString(val.toString(),
1072 							       false);
1073 
1074 	    // Add wildcards back in.
1075 
1076 	    if (wildstart) {
1077 		strval = WILDCARD + strval;
1078 
1079 	    }
1080 
1081 	    if (wildend) {
1082 		strval = strval + WILDCARD;
1083 
1084 	    }
1085 
1086 	} else {
1087 	    strval = val.toString();
1088 
1089 	}
1090 
1091 	return strval;
1092 
1093     }
1094 
1095     // Eat whitespace.
1096 
1097     private int eatWhite(StreamTokenizer tk)
1098 	throws IOException {
1099 
1100 	int tok = tk.nextToken();
1101 
1102 	while (tok == SPACE) {
1103 	    tok = tk.nextToken();
1104 
1105 	}
1106 
1107 	return tok;
1108     }
1109 }
1110