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