xref: /illumos-gate/usr/src/lib/libslp/javalib/com/sun/slp/AttributeVerifier.java (revision 608eb926e14f4ba4736b2d59e891335f1cba9e1e)
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-2001 by Sun Microsystems, Inc.
23  * All rights reserved.
24  *
25  */
26 
27 //  AttributeVerifier.java: An attribute verifier for SLP attributes.
28 //  Author:           James Kempf
29 //  Created On:       Thu Jun 19 10:51:32 1997
30 //  Last Modified By: James Kempf
31 //  Last Modified On: Mon Nov  9 10:21:02 1998
32 //  Update Count:     200
33 //
34 
35 package com.sun.slp;
36 
37 import java.util.*;
38 import java.io.*;
39 
40 /**
41  * The AttributeVerifier class implements the ServiceLocationAttributeVerifier
42  * interface, but without committment to a particular mechanism for
43  * obtaining the template defintion. Subclasses provide the mechanism,
44  * and pass in the template to the parent as a Reader during object
45  * creation. The AttributeVerifier class parses tokens from the Reader and
46  * constructs the attribute descriptor objects describing the attribute. These
47  * are used during verification of the attribute. The AttributeVerifier
48  * and implementations of the attribute descriptors are free to optimize
49  * space utilization by lazily evaluating portions of the attribute
50  * template.
51  *
52  * @author James Kempf
53  *
54  */
55 
56 class AttributeVerifier
57     extends Object
58     implements ServiceLocationAttributeVerifier {
59 
60     // Template specific escape.
61 
62     private static final String ESC_HASH = "\\23";
63     private static final String HASH = "#";
64 
65     // Number of template attributes.
66 
67     private static final int TEMPLATE_ATTR_NO = 5;
68 
69     // Bitfields for found template attributes.
70 
71     private static final int SERVICE_MASK = 0x01;
72     private static final int VERSION_MASK = 0x02;
73     private static final int DESCRIPTION_MASK = 0x08;
74     private static final int URL_PATH_RULES_MASK = 0x10;
75 
76     // When all template attribute assignments are found.
77 
78     private static final int TEMPLATE_FOUND = (SERVICE_MASK |
79 					       VERSION_MASK |
80 					       DESCRIPTION_MASK |
81 					       URL_PATH_RULES_MASK);
82 
83     // These are the valid SLP types.
84 
85     private static final String INTEGER_TYPE = "integer";
86     private static final String STRING_TYPE = "string";
87     private static final String BOOLEAN_TYPE = "boolean";
88     private static final String OPAQUE_TYPE = "opaque";
89     private static final String KEYWORD_TYPE = "keyword";
90 
91     // These are the corresponding Java types. Package public so
92     //  others (SLPConfig for example) can get at them.
93 
94     static final String JAVA_STRING_TYPE =
95 	"java.lang.String";
96     static final String JAVA_INTEGER_TYPE =
97 	"java.lang.Integer";
98     static final String JAVA_BOOLEAN_TYPE =
99 	"java.lang.Boolean";
100     static final String JAVA_OPAQUE_TYPE =
101 	"[B";
102 
103     // Tokens for boolean values.
104 
105     private static final String TRUE_TOKEN = "true";
106     private static final String FALSE_TOKEN = "false";
107 
108     // This is the number of flags.
109 
110     private static final int FLAG_NO = 4;
111 
112     // These are the flags.
113 
114     private static final String MULTIPLE_FLAG = "m";
115     private static final String LITERAL_FLAG = "l";
116     private static final String EXPLICIT_FLAG = "x";
117     private static final String OPTIONAL_FLAG = "o";
118 
119     // These masks help determine whether the flags have been duplicated.
120 
121     private static final byte MULTIPLE_MASK = 0x01;
122     private static final byte LITERAL_MASK = 0x02;
123     private static final byte EXPLICIT_MASK = 0x04;
124     private static final byte OPTIONAL_MASK = 0x08;
125 
126     // These are tokens for separator characters.
127 
128     private static final char TT_COMMA = ',';
129     private static final char TT_EQUALS = '=';
130     private static final char TT_FIELD = '#';
131     private static final char TT_ESCAPE = '\\';
132 
133     // This token is for checking version number
134     // attribute assignment.
135 
136     private static final char TT_PERIOD = '.';
137 
138     // Radix64 code characters.
139 
140     private static final char UPPER_START_CODE = 'A';
141     private static final char UPPER_END_CODE = 'Z';
142     private static final char LOWER_START_CODE = 'a';
143     private static final char LOWER_END_CODE = 'z';
144     private static final char NUMBER_START_CODE = '0';
145     private static final char NUMBER_END_CODE = '9';
146     private static final char EXTRA_CODE1 = '+';
147     private static final char EXTRA_CODE2 = '/';
148     private static final char PAD_CODE = '=';
149     private static final char LENGTH_SEPERATOR = ':';
150 
151     // The SLP service type of this template.
152 
153     private ServiceType serviceType;
154 
155     // The template's language locale.
156 
157     private Locale locale;
158 
159     // The template's version.
160 
161     private String version;
162 
163     // The template's URL syntax.
164 
165     private String URLSyntax;
166 
167     // The template's description.
168 
169     private String description;
170 
171     // The attribute descriptors.
172 
173     private Hashtable attributeDescriptors = new Hashtable();
174 
175     //
176     // Constructors.
177 
178     AttributeVerifier() {
179 
180     }
181 
182     // Initialize the attribute verifier with a reader. Subclasses or clients
183     // pass in a Reader on the template that is used for parsing. This
184     // method is used when the template includes the template attributes
185     // and URL rules.
186 
187     void initialize(Reader r) throws ServiceLocationException {
188 
189 	// Use a StreamTokenizer to parse.
190 
191 	StreamTokenizer tk = new StreamTokenizer(r);
192 
193 	// Initialize tokenizer for parsing main.
194 
195 	initFieldChar(tk);
196 
197 	// Now parse the attribute template, including template attributes.
198 
199 	parseTemplate(tk);
200     }
201 
202     // Initialize with this method when no template attributes are involved.
203 
204     void initializeAttributesOnly(Reader r)
205 	throws ServiceLocationException {
206 
207 	// Use a StreamTokenizer to parse.
208 
209 	StreamTokenizer tk = new StreamTokenizer(r);
210 
211 	// Initialize tokenizer for parsing main.
212 
213 	initFieldChar(tk);
214 
215 	// Now parse the attribute templates, but no template attributes.
216 
217 	parseAttributes(tk);
218     }
219 
220     //
221     // ServiceLocationAttributeVerifier interface implementation.
222     //
223 
224     /**
225      * Returns the SLP service type for which this is the verifier.
226      *
227      * @return The SLP service type name.
228      */
229 
230     public ServiceType getServiceType() {
231 
232 	return serviceType;
233     }
234 
235     /**
236      * Returns the SLP language locale of this is the verifier.
237      *
238      * @return The SLP language locale.
239      */
240 
241     public Locale getLocale() {
242 
243 	return locale;
244     }
245 
246     /**
247      * Returns the SLP version of this is the verifier.
248      *
249      * @return The SLP version.
250      */
251 
252     public String getVersion() {
253 
254 	return version;
255     }
256 
257     /**
258      * Returns the SLP URL syntax of this is the verifier.
259      *
260      * @return The SLP URL syntax.
261      */
262 
263     public String getURLSyntax() {
264 
265 	return URLSyntax;
266     }
267 
268     /**
269      * Returns the SLP description of this is the verifier.
270      *
271      * @return The SLP description.
272      */
273 
274     public String getDescription() {
275 
276 	return description;
277     }
278 
279     /**
280      * Returns the ServiceLocationAttributeDescriptor object for the
281      * attribute having the named id. IF no such attribute exists in the
282      * template, returns null. This method is primarily for GUI tools to
283      * display attribute information. Programmatic verification of attributes
284      * should use the verifyAttribute() method.
285      *
286      * @param attrId Id of attribute to return.
287      * @return The ServiceLocationAttributeDescriptor object corresponding
288      * 	     to the parameter, or null if none.
289      */
290 
291     public ServiceLocationAttributeDescriptor
292 	getAttributeDescriptor(String attrId) {
293 
294 	return
295 	    (ServiceLocationAttributeDescriptor)
296 	    attributeDescriptors.get(attrId.toLowerCase());
297 
298     }
299 
300     /**
301      * Returns an Enumeration of
302      * ServiceLocationAttributeDescriptors for the template. This method
303      * is primarily for GUI tools to display attribute information.
304      * Programmatic verification of attributes should use the
305      * verifyAttribute() method. Note that small memory implementations
306      * may want to implement the Enumeration so that attributes are
307      * parsed on demand rather than at creation time.
308      *
309      * @return A Dictionary with attribute id's as the keys and
310      *	      ServiceLocationAttributeDescriptor objects for the
311      *	      attributes as the values.
312      */
313 
314     public Enumeration getAttributeDescriptors() {
315 
316 	return ((Hashtable)attributeDescriptors.clone()).elements();
317 
318     }
319 
320     /**
321      * Verify that the attribute parameter is a valid SLP attribute.
322      *
323      * @param attribute The ServiceLocationAttribute to be verified.
324      */
325 
326     public void verifyAttribute(ServiceLocationAttribute attribute)
327 	throws ServiceLocationException {
328 
329 	String id = attribute.getId().toLowerCase();
330 	ServiceLocationAttributeDescriptor des =
331 	    (ServiceLocationAttributeDescriptor)attributeDescriptors.get(id);
332 
333 	if (des == null) {
334 
335 	    throw
336 		new ServiceLocationException(
337 				ServiceLocationException.PARSE_ERROR,
338 				"template_no_attribute",
339 				new Object[] { id });
340 	}
341 
342 
343 	String type = des.getValueType();
344 	Vector vals = attribute.getValues();
345 
346 	// If keyword, check that no values were specified.
347 
348 	if (des.getIsKeyword()) {
349 
350 	    if (vals != null) {
351 		throw
352 		    new ServiceLocationException(
353 					 ServiceLocationException.PARSE_ERROR,
354 					 "template_not_null",
355 					 new Object[] { id });
356 	    }
357 	} else {
358 
359 	    int i, n;
360 
361 	    // Check that a values vector exists, and, if the attribute is
362 	    //  not multivalued, only one element is in it.
363 
364 	    if (vals == null) {
365 		throw
366 		    new ServiceLocationException(
367 				ServiceLocationException.PARSE_ERROR,
368 				"template_null",
369 				new Object[] { id });
370 
371 	    }
372 
373 	    n = vals.size();
374 
375 	    if (n > 1 && !des.getIsMultivalued()) {
376 		throw
377 		    new ServiceLocationException(
378 				ServiceLocationException.PARSE_ERROR,
379 				"template_not_multi",
380 				new Object[] { id });
381 	    }
382 
383 	    // Get allowed values.
384 
385 	    Vector av = null;
386 	    Enumeration en = des.getAllowedValues();
387 
388 	    if (en.hasMoreElements()) {
389 		av = new Vector();
390 
391 		while (en.hasMoreElements()) {
392 		    Object v = en.nextElement();
393 
394 		    // Lower case if string, convert to Opaque if byte array.
395 
396 		    if (type.equals(JAVA_STRING_TYPE)) {
397 			v = ((String)v).toLowerCase();
398 
399 		    } else if (type.equals(JAVA_OPAQUE_TYPE)) {
400 			v = new Opaque((byte[])v);
401 
402 		    }
403 		    av.addElement(v);
404 
405 		}
406 	    }
407 
408 	    // Check that the types of the values vector match the attribute
409 	    //  type. Also, if any allowed values, that attribute values
410 	    //  match.
411 
412 	    String attTypeName = des.getValueType();
413 
414 	    for (i = 0; i < n; i++) {
415 		Object val = vals.elementAt(i);
416 
417 		String typeName = val.getClass().getName();
418 
419 		if (!typeName.equals(attTypeName)) {
420 		    throw
421 			new ServiceLocationException(
422 				ServiceLocationException.PARSE_ERROR,
423 				"template_type_mismatch",
424 				new Object[] { id, typeName, attTypeName });
425 
426 		}
427 
428 		// Convert value for comparison, if necessary.
429 
430 		if (type.equals(JAVA_STRING_TYPE)) {
431 		    val = ((String)val).toLowerCase();
432 
433 		} else if (type.equals(JAVA_OPAQUE_TYPE)) {
434 		    val = new Opaque((byte[])val);
435 
436 		}
437 
438 		if (av != null && !av.contains(val)) {
439 		    throw
440 			new ServiceLocationException(
441 				ServiceLocationException.PARSE_ERROR,
442 				"template_not_allowed_value",
443 				new Object[] {id, val});
444 
445 		}
446 	    }
447 
448 	}
449 
450 	// No way to verify `X' because that's a search property. We
451 	//  must verify `O' in the context of an attribute set.
452     }
453 
454     /**
455      * Verify that the set of registration attributes matches the
456      * required attributes for the service.
457      *
458      * @param attributeVector A Vector of ServiceLocationAttribute objects
459      *			     for the registration.
460      * @exception ServiceLocationException Thrown if the
461      *		 attribute set is not valid. The message contains information
462      *		 on the attribute name and problem.
463      */
464 
465     public void verifyRegistration(Vector attributeVector)
466 	throws ServiceLocationException {
467 
468 	Assert.nonNullParameter(attributeVector, "attributeVector");
469 
470 
471 	if (attributeVector.size() <= 0) {
472 
473 	    // Check whether any attributes are required. If so, then
474 	    // there's an error.
475 
476 	    Enumeration en = attributeDescriptors.elements();
477 
478 	    while (en.hasMoreElements()) {
479 		ServiceLocationAttributeDescriptor attDesc =
480 		    (ServiceLocationAttributeDescriptor)en.nextElement();
481 
482 		if (!attDesc.getIsOptional()) {
483 
484 		    throw
485 			new ServiceLocationException(
486 				ServiceLocationException.PARSE_ERROR,
487 				"template_missing_required",
488 				new Object[] { attDesc.getId() });
489 		}
490 	    }
491 	} else {
492 
493 	    // Construct a hashtable of incoming objects, verifying them
494 	    // while doing so.
495 
496 	    int i, n = attributeVector.size();
497 	    Hashtable incoming = new Hashtable();
498 
499 	    for (i = 0; i < n; i++) {
500 		ServiceLocationAttribute attribute =
501 		    (ServiceLocationAttribute)attributeVector.elementAt(i);
502 		String id = attribute.getId().toLowerCase();
503 
504 		// If we already have it, signal a duplicate.
505 
506 		if (incoming.get(id) != null) {
507 		    throw
508 			new ServiceLocationException(
509 				ServiceLocationException.PARSE_ERROR,
510 				"template_dup",
511 				new Object[] { attribute.getId() });
512 
513 		}
514 
515 		verifyAttribute(attribute);
516 
517 		incoming.put(id, attribute);
518 	    }
519 
520 	    // Now check that all required attributes are present.
521 
522 	    Enumeration en = attributeDescriptors.elements();
523 
524 	    while (en.hasMoreElements()) {
525 		ServiceLocationAttributeDescriptor attDesc =
526 		    (ServiceLocationAttributeDescriptor)en.nextElement();
527 		String attrId = attDesc.getId();
528 
529 		if (!attDesc.getIsOptional() &&
530 		    incoming.get(attrId.toLowerCase()) == null) {
531 
532 		    throw
533 			new ServiceLocationException(
534 				ServiceLocationException.PARSE_ERROR,
535 				"template_missing_required",
536 				new Object[] { attrId });
537 		}
538 	    }
539 	}
540 
541     }
542 
543     //
544     // Private implementation. This is the template attribute parser.
545     //
546 
547     //
548     // Tokenizer initializers.
549 
550     // Base initialization. Resets syntax tables, sets up EOL parsing,
551     //  and makes word case significant.
552 
553     private void initForBase(StreamTokenizer tk) {
554 
555 	// Each part of an attribute production must specify which
556 	//  characters belong to words.
557 
558 	tk.resetSyntax();
559 
560 	// Note that we have to make EOL be whitespace even if significant
561 	//  because otherwise the line number won't be correctly incremented.
562 
563 	tk.whitespaceChars((int)'\n', (int)'\n');
564 
565 	// Don't lower case tokens.
566 
567 	tk.lowerCaseMode(false);
568     }
569 
570     // Initialize those token characters that appear in all
571     //  productions.
572 
573     private void initCommonToken(StreamTokenizer tk) {
574 
575 	// These characters are recognized as parts of tokens.
576 
577 	tk.wordChars((int)'A', (int)'Z');
578 	tk.wordChars((int)'a', (int)'z');
579 	tk.wordChars((int)'0', (int)'9');
580 	tk.wordChars((int)'&', (int)'&');
581 	tk.wordChars((int)'*', (int)'*');
582 	tk.wordChars((int)':', (int)':');
583 	tk.wordChars((int)'-', (int)'-');
584 	tk.wordChars((int)'_', (int)'_');
585 	tk.wordChars((int)'$', (int)'$');
586 	tk.wordChars((int)'+', (int)'+');
587 	tk.wordChars((int)'@', (int)'@');
588 	tk.wordChars((int)'.', (int)'.');
589 	tk.wordChars((int)'|', (int)'|');
590 	tk.wordChars((int)'<', (int)'<');
591 	tk.wordChars((int)'>', (int)'>');
592 	tk.wordChars((int)'~', (int)'~');
593 
594     }
595 
596     // Initialize tokenizer for parsing attribute name,
597     // attribute type and flags,
598     // and for boolean initializer lists.
599 
600     private void initIdChar(StreamTokenizer tk) {
601 
602 	initForBase(tk);
603 	initCommonToken(tk);
604 
605 	// Need backslash for escaping.
606 
607 	tk.wordChars((int)'\\', (int)'\\');
608 
609 	// Attribute id, Type, flags, and boolean initialzers
610 	//  all ignore white space.
611 
612 	tk.whitespaceChars((int)' ', (int)' ');
613 	tk.whitespaceChars((int)'\t', (int)'\t');
614 
615 	// Attribute part won't view newline as being significant.
616 
617 	tk.eolIsSignificant(false);
618 
619 	// Case is not folded.
620 
621 	tk.lowerCaseMode(false);
622     }
623 
624     // Initialize tokenizer for parsing service type name.
625     //  need to restrict characters.
626 
627     private void initSchemeIdChar(StreamTokenizer tk) {
628 
629 	initForBase(tk);
630 
631 	tk.wordChars((int)'A', (int)'Z');
632 	tk.wordChars((int)'a', (int)'z');
633 	tk.wordChars((int)'0', (int)'9');
634 	tk.wordChars((int)'-', (int)'-');
635 	tk.wordChars((int)'+', (int)'+');
636 	tk.wordChars((int)'.', (int)'.');  // allows naming authority.
637 	tk.wordChars((int)':', (int)':');  // for abstract and concrete type.
638 
639 	// Scheme name, type, flags, and boolean initialzers
640 	//  all ignore white space.
641 
642 	tk.whitespaceChars((int)' ', (int)' ');
643 	tk.whitespaceChars((int)'\t', (int)'\t');
644 
645 	// Scheme part won't view newline as being significant.
646 
647 	tk.eolIsSignificant(false);
648 
649 	// Case is not folded.
650 
651 	tk.lowerCaseMode(false);
652 
653     }
654 
655     // Initialize tokenizer for string list parsing.
656     //  Everything except '#' and ',' is recognized.
657     //  Note that whitespace is significant, but
658     //  EOL is ignored.
659 
660     private void initStringItemChar(StreamTokenizer tk) {
661 
662 	initForBase(tk);
663 
664 	tk.wordChars((int)'\t', (int)'\t');
665 	tk.wordChars((int)' ', (int)'"');
666 	// '#' goes here
667 	tk.wordChars((int)'$', (int)'+');
668 	// ',' goes here
669 	tk.wordChars((int)'-', (int)'/');
670 	tk.wordChars((int)'0', (int)'9');
671 	tk.wordChars((int)':', (int)':');
672 	// ';' goes here
673 	tk.wordChars((int)'<', (int)'@');
674 	tk.wordChars((int)'A', (int)'Z');
675 	tk.wordChars((int)'[', (int)'`');
676 	tk.wordChars((int)'a', (int)'z');
677 	tk.wordChars((int)'{', (int)'~');
678 
679 	// '%' is also reserved, but it is postprocessed
680 	// after the string is collected.
681 
682 	// Parse by lines to check when we've reached the end of the list.
683 
684 	tk.whitespaceChars((int)'\r', (int)'\r');
685 	tk.whitespaceChars((int)'\n', (int)'\n');
686 	tk.eolIsSignificant(true);
687 
688     }
689 
690     // Initialize tokenizer for integer list parsing.
691 
692     private void initIntItemChar(StreamTokenizer tk) {
693 
694 	initForBase(tk);
695 
696 	tk.wordChars((int)'0', (int)'9');
697 	tk.wordChars((int)'-', (int)'-');
698 	tk.wordChars((int)'+', (int)'+');
699 
700 	// Integer value list parsing ignores white space.
701 
702 	tk.whitespaceChars((int)' ', (int)' ');
703 	tk.whitespaceChars((int)'\t', (int)'\t');
704 
705 	// Parse by lines so we can find the end.
706 
707 	tk.whitespaceChars((int)'\r', (int)'\r');
708 	tk.whitespaceChars((int)'\n', (int)'\n');
709 	tk.eolIsSignificant(true);
710 
711     }
712 
713     // Boolean lists have same item syntax as scheme char.
714 
715     // Initialize main production parsing. The only
716     //  significant token character is <NL> because
717     //  parsing is done on a line-oriented basis.
718 
719     private void initFieldChar(StreamTokenizer tk) {
720 
721 	initForBase(tk);
722 
723 	tk.wordChars((int)'\t', (int)'\t');
724 	tk.wordChars((int)' ', (int)'/');
725 	tk.wordChars((int)'0', (int)'9');
726 	tk.wordChars((int)':', (int)'@');
727 	tk.wordChars((int)'A', (int)'Z');
728 	tk.wordChars((int)'[', (int)'`');
729 	tk.wordChars((int)'a', (int)'z');
730 	tk.wordChars((int)'{', (int)'~');
731 
732 	tk.whitespaceChars((int)'\r', (int)'\r');
733 	tk.whitespaceChars((int)'\n', (int)'\n');
734 	tk.eolIsSignificant(true);
735     }
736 
737     //
738     // Parsing methods.
739     //
740 
741     // Parse a template from the tokenizer.
742 
743     private void parseTemplate(StreamTokenizer tk)
744 	throws ServiceLocationException {
745 
746 	// First parse past the template attributes.
747 
748 	parseTemplateAttributes(tk);
749 
750 	// Finally, parse the attributes.
751 
752 	parseAttributes(tk);
753 
754     }
755 
756     // Parse the template attributes from the tokenizer.
757 
758     private void parseTemplateAttributes(StreamTokenizer tk)
759 	throws ServiceLocationException {
760 
761 	int found = 0;
762 
763 	// Parse each of the template attributes. Note that we are parsing
764 	//  the attribute value assignments, not definitions.
765 
766 	try {
767 
768 	    do {
769 
770 		found = found | parseTemplateAttribute(tk, found);
771 
772 	    } while (found != TEMPLATE_FOUND);
773 
774 	} catch (IOException ex) {
775 
776 	    throw
777 		new ServiceLocationException(
778 				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
779 				"template_io_error",
780 				new Object[] {Integer.toString(tk.lineno())});
781 
782 	}
783     }
784 
785     // Parse a template attribute.
786 
787     private int parseTemplateAttribute(StreamTokenizer tk, int found)
788 	throws ServiceLocationException, IOException {
789 
790 	// Get line including id and equals.
791 
792 	int tt = tk.nextToken();
793 
794 	if (tt != StreamTokenizer.TT_WORD) {
795 
796 	    throw
797 		new ServiceLocationException(
798 				ServiceLocationException.PARSE_ERROR,
799 				"template_assign_error",
800 				new Object[] {Integer.toString(tk.lineno())});
801 	}
802 
803 	// Get tokenizer for id and potential value line.
804 
805 	StringReader rdr = new StringReader(tk.sval);
806 	StreamTokenizer stk = new StreamTokenizer(rdr);
807 
808 	initIdChar(stk);
809 
810 	// Make sure newline is there.
811 
812 	if ((tt = tk.nextToken()) == StreamTokenizer.TT_EOF) {
813 
814 	    throw
815 		new ServiceLocationException(
816 				ServiceLocationException.PARSE_ERROR,
817 				"template_end_error",
818 				new Object[] {Integer.toString(tk.lineno())});
819 
820 	}
821 
822 	if (tt != StreamTokenizer.TT_EOL) {
823 
824 	    throw
825 		new ServiceLocationException(
826 				ServiceLocationException.PARSE_ERROR,
827 				"template_unk_token",
828 				new Object[] {Integer.toString(tk.lineno())});
829 
830 	}
831 
832 
833 	// Parse off id.
834 
835 	if ((tt = stk.nextToken()) != StreamTokenizer.TT_WORD) {
836 
837 	    throw
838 		new ServiceLocationException(
839 				ServiceLocationException.PARSE_ERROR,
840 				"template_missing_id",
841 				new Object[] {Integer.toString(tk.lineno())});
842 	}
843 
844 	String id = stk.sval;
845 	boolean duplicate = false;
846 	int mask = 0;
847 
848 	// Check for the equals.
849 
850 	if ((tt = stk.nextToken()) != TT_EQUALS) {
851 
852 	    throw
853 		new ServiceLocationException(
854 				ServiceLocationException.PARSE_ERROR,
855 				"template_missing_eq ",
856 				new Object[] {Integer.toString(tk.lineno())});
857 
858 	}
859 
860 	// Depending on the id, parse the rest.
861 
862 	if (id.equalsIgnoreCase(SLPTemplateRegistry.SERVICE_ATTR_ID)) {
863 
864 	    if ((found & SERVICE_MASK) == 0) {
865 
866 		// Just need to parse off the service type.
867 
868 		if ((tt = stk.nextToken()) != StreamTokenizer.TT_WORD) {
869 		    throw
870 			new ServiceLocationException(
871 				ServiceLocationException.PARSE_ERROR,
872 				"template_srv_type_err",
873 				new Object[] {Integer.toString(tk.lineno())});
874 		}
875 
876 		// Check for characters which are not alphanumerics, + and -.
877 		//  Service type names are more heavily restricted.
878 
879 		StreamTokenizer sttk =
880 		    new StreamTokenizer(new StringReader(stk.sval));
881 
882 		initSchemeIdChar(sttk);
883 
884 		if (sttk.nextToken() != StreamTokenizer.TT_WORD ||
885 		    !stk.sval.equals(sttk.sval)) {
886 		    throw
887 			new ServiceLocationException(
888 				ServiceLocationException.PARSE_ERROR,
889 				"template_srv_type_err",
890 				new Object[] {Integer.toString(tk.lineno())});
891 
892 		}
893 
894 		// Need to prefix with "serivce:".
895 
896 		String typeName = sttk.sval;
897 
898 		if (!typeName.startsWith(Defaults.SERVICE_PREFIX+":")) {
899 		    typeName = Defaults.SERVICE_PREFIX+":"+typeName;
900 
901 		}
902 
903 		// Set service type instance variable.
904 
905 		serviceType = new ServiceType(typeName);
906 
907 		// Check for extra stuff.
908 
909 		if ((tt = stk.nextToken()) != StreamTokenizer.TT_EOF) {
910 		    throw
911 			new ServiceLocationException(
912 				ServiceLocationException.PARSE_ERROR,
913 				"template_srv_type_err",
914 				new Object[] {Integer.toString(tk.lineno())});
915 		}
916 
917 		mask = SERVICE_MASK;
918 	    } else {
919 
920 		duplicate = true;
921 	    }
922 	} else if (id.equalsIgnoreCase(SLPTemplateRegistry.VERSION_ATTR_ID)) {
923 
924 	    if ((found & VERSION_MASK) == 0) {
925 
926 		// Just need to parse off the version number.
927 
928 		if ((tt = stk.nextToken()) != StreamTokenizer.TT_WORD) {
929 		    throw
930 			new ServiceLocationException(
931 				ServiceLocationException.PARSE_ERROR,
932 				"template_vers_err",
933 				new Object[] {Integer.toString(tk.lineno())});
934 		}
935 
936 		// Make sure it's a valid version number.
937 
938 		String version = stk.sval;
939 
940 		if (version.indexOf(TT_PERIOD) == -1) {
941 
942 		    throw
943 			new ServiceLocationException(
944 				ServiceLocationException.PARSE_ERROR,
945 				"template_vers_mssing",
946 				new Object[] {Integer.toString(tk.lineno())});
947 
948 		}
949 
950 		try {
951 		    Float.valueOf(version);
952 		} catch (NumberFormatException ex) {
953 
954 		    throw
955 			new ServiceLocationException(
956 				ServiceLocationException.PARSE_ERROR,
957 				"template_vers_err",
958 				new Object[] {Integer.toString(tk.lineno())});
959 
960 		}
961 
962 		this.version = version;
963 
964 		// Check for extra stuff.
965 
966 		if ((tt = stk.nextToken()) != StreamTokenizer.TT_EOF) {
967 		    throw
968 			new ServiceLocationException(
969 				ServiceLocationException.PARSE_ERROR,
970 				"template_vers_err",
971 				new Object[] {Integer.toString(tk.lineno())});
972 		}
973 
974 		mask = VERSION_MASK;
975 	    } else {
976 
977 		duplicate = true;
978 	    }
979 	} else if (id.equalsIgnoreCase(
980 				SLPTemplateRegistry.DESCRIPTION_ATTR_ID)) {
981 
982 	    // Make sure there is nothing else on that line.
983 
984 	    if (stk.nextToken() != StreamTokenizer.TT_EOF) {
985 
986 		throw
987 		    new ServiceLocationException(
988 				ServiceLocationException.PARSE_ERROR,
989 				"template_attr_syntax",
990 				new Object[] {Integer.toString(tk.lineno())});
991 	    }
992 
993 	    if ((found & DESCRIPTION_MASK) == 0) {
994 
995 		// Need to continue parsing help text until we reach a blank
996 		// line.
997 
998 		String helpText = "";
999 
1000 		do {
1001 		    int ptt = tt;
1002 		    tt = tk.nextToken();
1003 
1004 		    if (tt == StreamTokenizer.TT_WORD) {
1005 
1006 			helpText = helpText + tk.sval + "\n";
1007 
1008 		    } else if (tt == StreamTokenizer.TT_EOL) {
1009 
1010 			// If previous token was end of line, quit.
1011 
1012 			if (ptt == StreamTokenizer.TT_EOL) {
1013 
1014 			    // Store any text first.
1015 
1016 			    if (helpText.length() > 0) {
1017 				description = helpText;
1018 
1019 			    }
1020 
1021 			    tk.pushBack();  // so same as above
1022 
1023 			    break;
1024 			}
1025 		    } else if (tt == StreamTokenizer.TT_EOF) {
1026 			throw
1027 			    new ServiceLocationException(
1028 				ServiceLocationException.PARSE_ERROR,
1029 				"template_end_error",
1030 				new Object[] {Integer.toString(tk.lineno())});
1031 
1032 		    } else {
1033 
1034 			throw
1035 			    new ServiceLocationException(
1036 				ServiceLocationException.PARSE_ERROR,
1037 				"template_unk_token",
1038 				new Object[] {Integer.toString(tk.lineno())});
1039 
1040 		    }
1041 
1042 		} while (true);
1043 
1044 		mask = DESCRIPTION_MASK;
1045 	    } else {
1046 
1047 		duplicate = true;
1048 	    }
1049 	} else if (id.equalsIgnoreCase(
1050 				SLPTemplateRegistry.SERVICE_URL_ATTR_ID)) {
1051 
1052 	    if ((found & URL_PATH_RULES_MASK) == 0) {
1053 
1054 		String serviceURLGrammer = "";
1055 
1056 		// Pull everything out of the rdr StringReader until empty.
1057 
1058 		int ic;
1059 
1060 		while ((ic = rdr.read()) != -1) {
1061 		    serviceURLGrammer += (char)ic;
1062 
1063 		}
1064 
1065 		serviceURLGrammer += "\n";
1066 
1067 		// Need to continue parsing service URL syntax until we
1068 		// reach a blank line.
1069 
1070 		tt = StreamTokenizer.TT_EOL;
1071 
1072 		do {
1073 		    int ptt = tt;
1074 		    tt = tk.nextToken();
1075 
1076 		    if (tt == StreamTokenizer.TT_WORD) {
1077 
1078 			serviceURLGrammer = serviceURLGrammer + tk.sval + "\n";
1079 
1080 		    } else if (tt == StreamTokenizer.TT_EOL) {
1081 
1082 			// If previous token was end of line, quit.
1083 
1084 			if (ptt == StreamTokenizer.TT_EOL) {
1085 
1086 			    // Store any text first.
1087 
1088 			    if (serviceURLGrammer.length() > 0) {
1089 				URLSyntax = serviceURLGrammer;
1090 
1091 			    }
1092 
1093 			    tk.pushBack();  // so same as above.
1094 
1095 			    break;
1096 			}
1097 		    } else if (tt == StreamTokenizer.TT_EOF) {
1098 			throw
1099 			    new ServiceLocationException(
1100 				ServiceLocationException.PARSE_ERROR,
1101 				"template_end_error",
1102 				new Object[] {Integer.toString(tk.lineno())});
1103 
1104 		    } else {
1105 
1106 			throw
1107 			    new ServiceLocationException(
1108 				ServiceLocationException.PARSE_ERROR,
1109 				"template_unk_token",
1110 				new Object[] {Integer.toString(tk.lineno())});
1111 
1112 		    }
1113 
1114 		} while (true);
1115 
1116 		mask = URL_PATH_RULES_MASK;
1117 	    } else {
1118 
1119 		duplicate = true;
1120 	    }
1121 	} else {
1122 
1123 	    throw
1124 		new ServiceLocationException(
1125 				ServiceLocationException.PARSE_ERROR,
1126 				"template_nontattribute_err",
1127 				new Object[] {Integer.toString(tk.lineno())});
1128 
1129 	}
1130 
1131 	// Throw exception if a duplicate definition was detected.
1132 
1133 	if (duplicate) {
1134 
1135 	    throw
1136 		new ServiceLocationException(
1137 				ServiceLocationException.PARSE_ERROR,
1138 				"template_dup_def",
1139 				new Object[] {Integer.toString(tk.lineno())});
1140 
1141 	}
1142 
1143 
1144 	// Make sure the assignment ends with a blank line.
1145 
1146 	if ((tt = tk.nextToken()) != StreamTokenizer.TT_EOL) {
1147 
1148 	    throw
1149 		new ServiceLocationException(
1150 				ServiceLocationException.PARSE_ERROR,
1151 				"template_attr_syntax",
1152 				new Object[] {Integer.toString(tk.lineno())});
1153 
1154 	}
1155 
1156 	return mask;
1157 
1158     }
1159 
1160 
1161     // Parse the attributes from the tokenizer.
1162 
1163     private void parseAttributes(StreamTokenizer tk)
1164 	throws ServiceLocationException {
1165 
1166 	try {
1167 
1168 	    do {
1169 
1170 		// Check if at end of file yet.
1171 
1172 		int tt = tk.nextToken();
1173 
1174 		if (tt == StreamTokenizer.TT_EOF) {
1175 		    break;
1176 		}
1177 
1178 		// If not, push token back so we can get it next time.
1179 
1180 		tk.pushBack();
1181 
1182 		// Parse off the attribute descriptor.
1183 
1184 		AttributeDescriptor attDesc = parseAttribute(tk);
1185 
1186 		// Check whether default values, if any, are correct.
1187 
1188 		checkDefaultValues(attDesc);
1189 
1190 		// If the attribute already exists, then throw exception.
1191 		//  We could arguably replace existing, but it might
1192 		//  suprise the user.
1193 
1194 		String attrId = attDesc.getId().toLowerCase();
1195 
1196 		if (attributeDescriptors.get(attrId) != null) {
1197 
1198 		    throw
1199 			new ServiceLocationException(
1200 				ServiceLocationException.PARSE_ERROR,
1201 				"template_dup_def",
1202 				new Object[] {Integer.toString(tk.lineno())});
1203 
1204 		}
1205 
1206 		// Add the attribute to the descriptor table.
1207 
1208 		attributeDescriptors.put(attrId, attDesc);
1209 
1210 	    } while (true);
1211 
1212 	} catch (IOException ex) {
1213 
1214 	    throw
1215 		new ServiceLocationException(
1216 				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
1217 				"template_io_error",
1218 				new Object[] {Integer.toString(tk.lineno())});
1219 	}
1220 
1221     }
1222 
1223     // Parse a single attribute description from the tokenizer.
1224 
1225     private AttributeDescriptor
1226 	parseAttribute(StreamTokenizer tk) throws ServiceLocationException {
1227 
1228 	AttributeDescriptor attDesc = new AttributeDescriptor();
1229 	int lineno = 0;
1230 
1231 	try {
1232 
1233 	    // Parse the string for attribute id, type, and flags.
1234 
1235 	    lineno = tk.lineno();
1236 
1237 	    int tt = tk.nextToken();
1238 
1239 	    if (tt != StreamTokenizer.TT_WORD) {
1240 		throw
1241 		    new ServiceLocationException(
1242 				ServiceLocationException.PARSE_ERROR,
1243 				"template_attr_syntax",
1244 				new Object[] {Integer.toString(tk.lineno())});
1245 	    }
1246 
1247 	    StreamTokenizer stk =
1248 		new StreamTokenizer(new StringReader(tk.sval));
1249 
1250 	    initIdChar(stk);
1251 
1252 	    // Parse the attribute id.
1253 
1254 	    parseId(stk, attDesc, lineno);
1255 
1256 	    // Parse the type and flags.
1257 
1258 	    parseTypeAndFlags(stk, attDesc, lineno);
1259 
1260 	    tt = tk.nextToken();
1261 
1262 	    if (tt == StreamTokenizer.TT_EOF) {
1263 
1264 		throw
1265 		    new ServiceLocationException(
1266 				ServiceLocationException.PARSE_ERROR,
1267 				"template_end_error",
1268 				new Object[] {Integer.toString(tk.lineno())});
1269 
1270 	    }
1271 
1272 	    if (tt != StreamTokenizer.TT_EOL) {
1273 
1274 		throw
1275 		    new ServiceLocationException(
1276 				ServiceLocationException.PARSE_ERROR,
1277 				"template_unk_token",
1278 				new Object[] {Integer.toString(tk.lineno())});
1279 
1280 	    }
1281 
1282 	    // Parse initial values.
1283 
1284 	    if (!attDesc.getIsKeyword()) {
1285 
1286 		String tok = "";
1287 
1288 		// Read in entire list.
1289 
1290 		do {
1291 		    int ptt = tt;
1292 		    lineno = tk.lineno();
1293 		    tt = tk.nextToken();
1294 
1295 		    if (tt == StreamTokenizer.TT_WORD) {
1296 
1297 			// Trim line, check for '#', indicating end of list.
1298 
1299 			String line = tk.sval.trim();
1300 
1301 			if (line.charAt(0) == TT_FIELD) {
1302 			    // it's help text already.
1303 
1304 			    if (tok.length() > 0) {
1305 				stk =
1306 				    new StreamTokenizer(new StringReader(tok));
1307 				parseDefaultValues(stk, attDesc, lineno);
1308 			    }
1309 
1310 			    tk.pushBack();
1311 			    break;
1312 
1313 			} else {
1314 
1315 			    // Otherwise concatenate onto growing list.
1316 
1317 			    tok = tok + line;
1318 
1319 			}
1320 
1321 		    } else if (tt == StreamTokenizer.TT_EOL) {
1322 
1323 			if (ptt == StreamTokenizer.TT_EOL) {
1324 			    // end of attribute definition.
1325 
1326 			    // Process any accumulated list.
1327 
1328 			    if (tok.length() > 0) {
1329 				stk =
1330 				    new StreamTokenizer(new StringReader(tok));
1331 				parseDefaultValues(stk, attDesc, lineno);
1332 			    }
1333 
1334 			    return attDesc;
1335 
1336 			}
1337 		    } else if (tt == StreamTokenizer.TT_EOF) {
1338 			throw
1339 			    new ServiceLocationException(
1340 				ServiceLocationException.PARSE_ERROR,
1341 				"template_end_error",
1342 				new Object[] {Integer.toString(tk.lineno())});
1343 
1344 		    } else {
1345 
1346 			throw
1347 			    new ServiceLocationException(
1348 				ServiceLocationException.PARSE_ERROR,
1349 				"template_unk_token",
1350 				new Object[] {Integer.toString(tk.lineno())});
1351 
1352 		    }
1353 
1354 		} while (true);
1355 
1356 	    } else {
1357 		attDesc.setDefaultValues(null);
1358 		attDesc.setAllowedValues(null);
1359 
1360 		// Check for end of definition.
1361 
1362 		if ((tt = tk.nextToken()) == StreamTokenizer.TT_EOL) {
1363 		    return attDesc;
1364 
1365 		} else if (tt == StreamTokenizer.TT_WORD) {
1366 
1367 		    // Check for start of help text.
1368 
1369 		    String line = tk.sval.trim();
1370 
1371 		    if (line.charAt(0) != TT_FIELD) {
1372 			throw
1373 			    new ServiceLocationException(
1374 				ServiceLocationException.PARSE_ERROR,
1375 				"template_attr_syntax",
1376 				new Object[] {Integer.toString(tk.lineno())});
1377 
1378 		    } else {
1379 
1380 			tk.pushBack();
1381 
1382 		    }
1383 
1384 		} else if (tt == StreamTokenizer.TT_EOF) {
1385 		    throw
1386 			new ServiceLocationException(
1387 				ServiceLocationException.PARSE_ERROR,
1388 				"template_end_error",
1389 				new Object[] {Integer.toString(tk.lineno())});
1390 
1391 		} else {
1392 
1393 		    throw
1394 			new ServiceLocationException(
1395 				ServiceLocationException.PARSE_ERROR,
1396 				"template_unk_token",
1397 				new Object[] {Integer.toString(tk.lineno())});
1398 
1399 		}
1400 	    }
1401 
1402 
1403 	    // Parse help text.
1404 
1405 	    String helpText = "";
1406 
1407 	    do {
1408 		int ptt = tt;
1409 		lineno = tk.lineno();
1410 		tt = tk.nextToken();
1411 
1412 		if (tt == StreamTokenizer.TT_WORD) {
1413 
1414 		    // Check for end of help text.
1415 
1416 		    String line = tk.sval.trim();
1417 
1418 		    if (line.charAt(0) == TT_FIELD) {
1419 
1420 			// Help text is collected verbatim after '#'.
1421 
1422 			helpText =
1423 			    helpText + line.substring(1) + "\n";
1424 
1425 		    } else {
1426 
1427 			// We've reached the end of the help text. Store it
1428 			//  and break out of the loop.
1429 
1430 			if (helpText.length() > 0) {
1431 			    attDesc.setDescription(helpText);
1432 			}
1433 
1434 			tk.pushBack();
1435 			break;
1436 
1437 		    }
1438 
1439 		} else if (tt == StreamTokenizer.TT_EOL ||
1440 			   tt == StreamTokenizer.TT_EOF) {
1441 
1442 		    // If previous token was end of line, quit.
1443 
1444 		    if (ptt == StreamTokenizer.TT_EOL) {
1445 
1446 			// Store any text first.
1447 
1448 			if (helpText.length() > 0) {
1449 			    attDesc.setDescription(helpText);
1450 			}
1451 
1452 			// If this is a keyword attribute, set the allowed
1453 			//  values list to null.
1454 
1455 			if (attDesc.getIsKeyword()) {
1456 			    attDesc.setAllowedValues(null);
1457 			}
1458 
1459 			return attDesc;
1460 
1461 		    } else if (tt == StreamTokenizer.TT_EOF) {
1462 
1463 			// Error if previous token wasn't EOL.
1464 
1465 			throw
1466 			    new ServiceLocationException(
1467 				ServiceLocationException.PARSE_ERROR,
1468 				"template_end_error",
1469 				new Object[] {Integer.toString(tk.lineno())});
1470 		    }
1471 
1472 		} else {
1473 
1474 		    throw
1475 			new ServiceLocationException(
1476 				ServiceLocationException.PARSE_ERROR,
1477 				"template_unk_token",
1478 				new Object[] {Integer.toString(tk.lineno())});
1479 		}
1480 
1481 	    } while (true);
1482 
1483 	    // Parse allowed values.
1484 
1485 	    if (!attDesc.getIsKeyword()) {
1486 
1487 		String tok = "";
1488 
1489 		// Read in entire list.
1490 
1491 		do {
1492 		    int ptt = tt;
1493 		    lineno = tk.lineno();
1494 		    tt = tk.nextToken();
1495 
1496 		    if (tt == StreamTokenizer.TT_WORD) {
1497 
1498 			// Concatenate onto growing list.
1499 
1500 			tok = tok + tk.sval;
1501 
1502 		    } else if (tt == StreamTokenizer.TT_EOL) {
1503 
1504 			if (ptt == StreamTokenizer.TT_EOL) {
1505 			    // end of attribute definition.
1506 
1507 			    // Process any accumulated list.
1508 
1509 			    if (tok.length() > 0) {
1510 				stk =
1511 				    new StreamTokenizer(new StringReader(tok));
1512 				parseAllowedValues(stk, attDesc, lineno);
1513 			    }
1514 
1515 			    return attDesc;
1516 
1517 			}
1518 		    } else if (tt == StreamTokenizer.TT_EOF) {
1519 			throw
1520 			    new ServiceLocationException(
1521 				ServiceLocationException.PARSE_ERROR,
1522 				"template_end_error",
1523 				new Object[] {Integer.toString(tk.lineno())});
1524 
1525 		    } else {
1526 
1527 			throw
1528 			    new ServiceLocationException(
1529 				ServiceLocationException.PARSE_ERROR,
1530 				"template_unk_token",
1531 				new Object[] {Integer.toString(tk.lineno())});
1532 		    }
1533 
1534 		} while (true);
1535 
1536 	    } else {
1537 
1538 		// Error. Keyword attribute should have ended during help text
1539 		//  parsing or before.
1540 
1541 		throw
1542 		    new ServiceLocationException(
1543 				ServiceLocationException.PARSE_ERROR,
1544 				"template_attr_syntax",
1545 				new Object[] {Integer.toString(tk.lineno())});
1546 	    }
1547 
1548 	} catch (IOException ex) {
1549 
1550 	    throw
1551 		new ServiceLocationException(
1552 				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
1553 				"template_io_error",
1554 				new Object[] {
1555 		    Integer.toString(tk.lineno()),
1556 			ex.getMessage()});
1557 	}
1558 
1559     }
1560 
1561     // Check whether the default values, if any, are correct.
1562 
1563     private void checkDefaultValues(AttributeDescriptor attDesc)
1564 	throws ServiceLocationException {
1565 
1566 	// Don't bother if it's a keyword attribute, parsing has checked.
1567 
1568 	if (attDesc.getIsKeyword()) {
1569 	    return;
1570 	}
1571 
1572 	Enumeration init = attDesc.getDefaultValues();
1573 	Enumeration en = attDesc.getAllowedValues();
1574 	Vector allowed = new Vector();
1575 	String attDescType = attDesc.getValueType();
1576 
1577 	// First, collect the allowed values.
1578 
1579 	while (en.hasMoreElements()) {
1580 	    Object allval = en.nextElement();
1581 
1582 	    // Lower case strings and create opaques for comparison
1583 	    // if type is opaque.
1584 
1585 	    if (attDescType.equals(JAVA_STRING_TYPE)) {
1586 		allval = ((String)allval).toLowerCase();
1587 
1588 	    } else if (attDescType.equals(JAVA_OPAQUE_TYPE)) {
1589 		allval = new Opaque((byte[])allval);
1590 
1591 	    }
1592 
1593 	    allowed.addElement(allval);
1594 	}
1595 
1596 	// Now compare the allowed with the initial.
1597 
1598 	if (allowed.size() > 0) {
1599 
1600 	    // Error if allowed is restricted but no initializers.
1601 
1602 	    if (!init.hasMoreElements()) {
1603 
1604 		throw
1605 		    new ServiceLocationException(
1606 				ServiceLocationException.PARSE_ERROR,
1607 				"template_no_init",
1608 				new Object[] {attDesc.getId()});
1609 
1610 	    }
1611 
1612 	    Object val = null;
1613 
1614 	    // Compare init values with allowed.
1615 
1616 	    while (init.hasMoreElements()) {
1617 		Object test = init.nextElement();
1618 		val = test; // for exception..
1619 
1620 		if (attDescType.equals(JAVA_STRING_TYPE)) {
1621 		    test = ((String)test).toLowerCase();
1622 
1623 		} else if (attDescType.equals(JAVA_OPAQUE_TYPE)) {
1624 		    test = new Opaque((byte[])test);
1625 
1626 		}
1627 
1628 		if (allowed.indexOf(test) != -1) {
1629 		    return; // found it!
1630 		}
1631 	    }
1632 	    // Initializer wasn't found.
1633 
1634 	    throw
1635 		new ServiceLocationException(
1636 				ServiceLocationException.PARSE_ERROR,
1637 				"template_wrong_init",
1638 				new Object[] {
1639 		    val.toString(), attDesc.getId()});
1640 	}
1641     }
1642 
1643     // Parse the attribute's id string.
1644 
1645     private void parseId(StreamTokenizer tk,
1646 			 AttributeDescriptor attDesc,
1647 			 int baseLineno)
1648 	throws ServiceLocationException, IOException {
1649 
1650 	// Parse the attribute's identifier tag.
1651 
1652 	String id = parseWord(tk, baseLineno);
1653 
1654 	int tt = tk.nextToken();
1655 
1656 	// Parse the seperator.
1657 
1658 	if (tt != TT_EQUALS) {
1659 	    throw
1660 		new ServiceLocationException(
1661 				ServiceLocationException.PARSE_ERROR,
1662 				"template_attr_syntax",
1663 				new Object[] {
1664 		    Integer.toString(tk.lineno() + baseLineno)});
1665 
1666 	}
1667 
1668 	// Expand out any escaped ``#''. It won't be handled by
1669 	// SLA.
1670 
1671 	id = unescapeHash(id);
1672 
1673 	// Expand out character escapes.
1674 
1675 	id =
1676 	    ServiceLocationAttribute.unescapeAttributeString(id, true);
1677 
1678 
1679 	attDesc.setId(id);
1680     }
1681 
1682     // Parse the attribute's type and flags.
1683 
1684     private void
1685 	parseTypeAndFlags(StreamTokenizer tk,
1686 			  AttributeDescriptor attDesc,
1687 			  int baseLineno)
1688 	throws ServiceLocationException, IOException {
1689 
1690 	int existingFlags = 0;
1691 
1692 	// Parse the attribute's type.
1693 
1694 	String type = parseWord(tk, baseLineno);
1695 
1696 	checkAndAddType(type, attDesc, tk.lineno() + baseLineno);
1697 
1698 	// Parse the flags.
1699 
1700 	do {
1701 
1702 	    // Check if any flags are left.
1703 
1704 	    if (tk.nextToken() == StreamTokenizer.TT_EOF) {
1705 		break;
1706 
1707 	    } else {
1708 		tk.pushBack();
1709 	    }
1710 
1711 	    int lineno = tk.lineno();
1712 
1713 	    // Parse the flag.
1714 
1715 	    String flag = parseWord(tk, baseLineno);
1716 
1717 	    // Error if flags with keyword.
1718 
1719 	    if (attDesc.getIsKeyword()) {
1720 		throw
1721 		    new ServiceLocationException(
1722 				ServiceLocationException.PARSE_ERROR,
1723 				"template_attr_syntax",
1724 				new Object[] {
1725 			Integer.toString(tk.lineno() + baseLineno)});
1726 	    }
1727 
1728 
1729 	    // Check and assign it to the attribute.
1730 
1731 	    existingFlags =
1732 		existingFlags | checkAndAddFlag(flag,
1733 						existingFlags,
1734 						attDesc,
1735 						baseLineno + lineno);
1736 
1737 	} while (true);
1738     }
1739 
1740     // Parse the attribute's initial value(s).
1741 
1742     private void parseDefaultValues(StreamTokenizer tk,
1743 				    AttributeDescriptor attDesc,
1744 				    int baseLineno)
1745 	throws ServiceLocationException, IOException {
1746 
1747 	// First get the vector of initial values.
1748 
1749 	Vector vals = parseValueList(tk, attDesc, baseLineno);
1750 
1751 	// Check whether it works for this attribute. Type
1752 	//  checking will be done by value list parsing.
1753 
1754 	if (!attDesc.getIsMultivalued() && vals.size() > 1) {
1755 	    throw
1756 		new ServiceLocationException(
1757 				ServiceLocationException.PARSE_ERROR,
1758 				"template_attr_syntax",
1759 				new Object[] {
1760 		    Integer.toString(tk.lineno() + baseLineno)});
1761 	}
1762 
1763 	attDesc.setDefaultValues(vals);
1764     }
1765 
1766     // Parse the attribute's allowed values.
1767 
1768     private void
1769 	parseAllowedValues(StreamTokenizer tk,
1770 			   AttributeDescriptor attDesc,
1771 			   int baseLineno)
1772 	throws ServiceLocationException, IOException {
1773 
1774 	// First get the vector of all allowed values.
1775 
1776 	Vector vals = parseValueList(tk, attDesc, baseLineno);
1777 
1778 	// Now set the allowed value vector.
1779 
1780 	attDesc.setAllowedValues(vals);
1781     }
1782 
1783     // Parse a value list.
1784 
1785     private Vector parseValueList(StreamTokenizer stk,
1786 				  AttributeDescriptor attDesc,
1787 				  int baseLineno)
1788 	throws ServiceLocationException, IOException {
1789 
1790 	Vector req = new Vector();
1791 
1792 	// Set up the tokenizer according to the type of the
1793 	//  attribute.
1794 
1795 	String type = attDesc.getValueType();
1796 
1797 	if (type.equals(JAVA_STRING_TYPE) || type.equals(JAVA_OPAQUE_TYPE)) {
1798 	    initStringItemChar(stk);
1799 	} else if (type.equals(JAVA_INTEGER_TYPE)) {
1800 	    initIntItemChar(stk);
1801 	} else if (type.equals(JAVA_BOOLEAN_TYPE)) {
1802 	    initIdChar(stk);
1803 	}
1804 
1805 	// Parse through a potentially multivalued value list.
1806 
1807 	boolean wordRequired = true;	// true when a word is required,
1808 					// false when a comma required.
1809 	boolean syntaxError = false;
1810 	String reqTok = "";
1811 	int lineno = 0;
1812 
1813 	do {
1814 	    int tt = stk.nextToken();
1815 	    lineno = stk.lineno() + baseLineno;
1816 
1817 	    if (tt ==  StreamTokenizer.TT_WORD) {
1818 
1819 		// If a word isn't required, then the case is
1820 		//  "token token" and is an error.
1821 
1822 		if (!wordRequired) {
1823 		    syntaxError = true;
1824 		}
1825 
1826 		reqTok = stk.sval.trim();
1827 
1828 		// Convert the value to the proper object.
1829 
1830 		Object reqVal = convertValue(type, reqTok, baseLineno);
1831 		req.addElement(reqVal);
1832 
1833 		wordRequired = false;
1834 
1835 	    } else if (tt == StreamTokenizer.TT_EOF) {
1836 
1837 		// If a word is required, then list ends with
1838 		//  a comma, so error.
1839 
1840 		if (wordRequired) {
1841 		    syntaxError = true;
1842 		}
1843 
1844 		break;
1845 
1846 	    } else if (tt == TT_COMMA) {
1847 
1848 		// If a word is required, then error. The case is ",,".
1849 
1850 		if (wordRequired) {
1851 		    syntaxError = true;
1852 		    break;
1853 		}
1854 
1855 		// Otherwise, the next token must be a word.
1856 
1857 		wordRequired = true;
1858 
1859 	    } else {
1860 
1861 		// No other tokens are allowed.
1862 
1863 		syntaxError = true;
1864 		break;
1865 	    }
1866 
1867 	} while (true);
1868 
1869 	if (syntaxError) {
1870 
1871 	    throw
1872 		new ServiceLocationException(
1873 				ServiceLocationException.PARSE_ERROR,
1874 				"template_attr_syntax",
1875 				new Object[] {Integer.toString(lineno)});
1876 	}
1877 
1878 	return req;
1879 
1880     }
1881 
1882     // Check the type and add it to the attribute descriptor.
1883 
1884     private void checkAndAddType(String type,
1885 				 AttributeDescriptor attDesc,
1886 				 int lineno)
1887 	throws ServiceLocationException {
1888 
1889 	// Check token against recognized types.
1890 
1891 	if (type.equalsIgnoreCase(STRING_TYPE)) {
1892 	    attDesc.setValueType(JAVA_STRING_TYPE);
1893 
1894 	} else if (type.equalsIgnoreCase(INTEGER_TYPE)) {
1895 	    attDesc.setValueType(JAVA_INTEGER_TYPE);
1896 
1897 	} else if (type.equalsIgnoreCase(BOOLEAN_TYPE)) {
1898 	    attDesc.setValueType(JAVA_BOOLEAN_TYPE);
1899 
1900 	} else if (type.equalsIgnoreCase(OPAQUE_TYPE)) {
1901 	    attDesc.setValueType(JAVA_OPAQUE_TYPE);
1902 
1903 	} else if (type.equalsIgnoreCase(KEYWORD_TYPE)) {
1904 	    attDesc.setIsKeyword(true);
1905 
1906 	} else {
1907 
1908 	    throw
1909 		new ServiceLocationException(
1910 				ServiceLocationException.PARSE_ERROR,
1911 				"template_not_slp_type",
1912 				new Object[] {Integer.toString(lineno)});
1913 	}
1914 
1915     }
1916 
1917     // Check the flag and add it to the attribute descriptor.
1918 
1919     private int checkAndAddFlag(String flag,
1920 				int matched,
1921 				AttributeDescriptor attDesc,
1922 				int lineno)
1923 	throws ServiceLocationException {
1924 
1925 	boolean duplicate = false;
1926 
1927 	// We depend on the attribute descriptor being initialized to
1928 	// nothing, i.e. false for all flags and for keyword.
1929 
1930 	if (flag.equalsIgnoreCase(MULTIPLE_FLAG)) {
1931 
1932 	    if ((matched & MULTIPLE_MASK) != 0) {
1933 		duplicate = true;
1934 
1935 	    } else {
1936 
1937 		// Check for boolean. Booleans may not have
1938 		// multiple values.
1939 
1940 		if (attDesc.getValueType().equals(JAVA_BOOLEAN_TYPE)) {
1941 
1942 		    throw
1943 			new ServiceLocationException(
1944 				ServiceLocationException.PARSE_ERROR,
1945 				"template_boolean_multi",
1946 				new Object[] {Integer.toString(lineno)});
1947 		}
1948 
1949 		attDesc.setIsMultivalued(true);
1950 		return MULTIPLE_MASK;
1951 
1952 	    }
1953 
1954 	} else if (flag.equalsIgnoreCase(LITERAL_FLAG)) {
1955 
1956 	    if ((matched & LITERAL_MASK) != 0) {
1957 		duplicate = true;
1958 
1959 	    } else {
1960 		attDesc.setIsLiteral(true);
1961 		return LITERAL_MASK;
1962 	    }
1963 
1964 	} else if (flag.equalsIgnoreCase(EXPLICIT_FLAG)) {
1965 
1966 	    if ((matched & EXPLICIT_MASK) != 0) {
1967 		duplicate = true;
1968 
1969 	    } else {
1970 		attDesc.setRequiresExplicitMatch(true);
1971 		return EXPLICIT_MASK;
1972 	    }
1973 
1974 	} else if (flag.equalsIgnoreCase(OPTIONAL_FLAG)) {
1975 
1976 	    if ((matched & OPTIONAL_MASK) != 0) {
1977 		duplicate = true;
1978 
1979 	    } else {
1980 		attDesc.setIsOptional(true);
1981 		return OPTIONAL_MASK;
1982 	    }
1983 
1984 	} else {
1985 
1986 	    throw
1987 		new ServiceLocationException(
1988 				ServiceLocationException.PARSE_ERROR,
1989 				"template_invalid_attr_flag",
1990 				new Object[] {Integer.toString(lineno)});
1991 	}
1992 
1993 
1994 	if (duplicate) {
1995 	    throw
1996 		new ServiceLocationException(
1997 				ServiceLocationException.PARSE_ERROR,
1998 				"template_dup_attr_flag",
1999 				new Object[] {Integer.toString(lineno)});
2000 	}
2001 
2002 	return 0; // never happens.
2003     }
2004 
2005     // Parse a word out of the tokenizer. The exact characters
2006     //  will depend on what the syntax tables have been set to.
2007 
2008     private String parseWord(StreamTokenizer tk, int baseLineno)
2009 	throws ServiceLocationException, IOException {
2010 
2011 	int tt = tk.nextToken();
2012 
2013 	if (tt == StreamTokenizer.TT_WORD) {
2014 	    return (tk.sval);
2015 
2016 	} else {
2017 
2018 	    String errorToken = "";
2019 
2020 	    // Report the erroneous characters.
2021 
2022 	    if (tt == StreamTokenizer.TT_NUMBER) {
2023 		errorToken = Double.toString(tk.nval);
2024 	    } else if (tt == StreamTokenizer.TT_EOL) {
2025 		errorToken = "<end of line>";
2026 	    } else if (tt == StreamTokenizer.TT_EOF) {
2027 		errorToken = "<end of file>";
2028 	    } else {
2029 		errorToken = (Character.valueOf((char)tt)).toString();
2030 	    }
2031 
2032 	    throw
2033 		new ServiceLocationException(
2034 				ServiceLocationException.PARSE_ERROR,
2035 				"template_invalid_tok",
2036 				new Object[] {
2037 		    Integer.toString(tk.lineno() + baseLineno)});
2038 
2039 	}
2040 
2041     }
2042 
2043     // Convert a value list token to the value.
2044 
2045     private Object convertValue(String type,
2046 				String reqTok,
2047 				int lineno)
2048 	throws ServiceLocationException,
2049 	       IOException {
2050 
2051 	Object reqVal = null;
2052 
2053 	if (type.equals(JAVA_STRING_TYPE)) {
2054 
2055 	    // Expand out any escaped ``#''. It won't be handled by
2056 	    //  SLA.
2057 
2058 	    reqTok = unescapeHash(reqTok);
2059 
2060 	    // Expand out character escapes.
2061 
2062 	    reqVal =
2063 		ServiceLocationAttribute.unescapeAttributeString(reqTok,
2064 								 false);
2065 
2066 	} else if (type.equals(JAVA_INTEGER_TYPE)) {
2067 
2068 	    try {
2069 
2070 		reqVal = Integer.valueOf(reqTok);
2071 
2072 	    } catch (NumberFormatException ex) {
2073 
2074 		throw
2075 		    new ServiceLocationException(
2076 				ServiceLocationException.PARSE_ERROR,
2077 				"template_expect_int",
2078 				new Object[] {
2079 			Integer.toString(lineno), reqTok });
2080 	    }
2081 	} else if (type.equals(JAVA_BOOLEAN_TYPE)) {
2082 
2083 	    // Boolean.valueOf() doesn't handle this properly.
2084 
2085 	    if (reqTok.equalsIgnoreCase(TRUE_TOKEN)) {
2086 
2087 		reqVal = Boolean.valueOf(true);
2088 
2089 	    } else if (reqTok.equalsIgnoreCase(FALSE_TOKEN)) {
2090 
2091 		reqVal = Boolean.valueOf(false);
2092 
2093 	    } else {
2094 
2095 		throw
2096 		    new ServiceLocationException(
2097 				ServiceLocationException.PARSE_ERROR,
2098 				"template_expect_bool",
2099 				new Object[] {
2100 			Integer.toString(lineno), reqTok});
2101 	    }
2102 	} else if (type.equals(JAVA_OPAQUE_TYPE)) {
2103 
2104 	    reqVal = Opaque.unescapeByteArray(reqTok);
2105 
2106 	} else {
2107 
2108 	    Assert.slpassert(false,
2109 			  "template_attr_desc",
2110 			  new Object[0]);
2111 	}
2112 
2113 	return reqVal;
2114     }
2115 
2116     // Expand out any escaped hashes. Not handled by SLA.
2117 
2118     private String unescapeHash(String str) {
2119 
2120 	StringBuffer buf = new StringBuffer();
2121 	int len = ESC_HASH.length();
2122 	int i, j = 0;
2123 
2124 	for (i = str.indexOf(ESC_HASH, j);
2125 	    i != -1;
2126 	    i = str.indexOf(ESC_HASH, j)) {
2127 
2128 	    buf.append(str.substring(j, i));
2129 	    buf.append(HASH);
2130 	    j = i + len;
2131 	}
2132 
2133 	len = str.length();
2134 
2135 	if (j < len) {
2136 	    buf.append(str.substring(j, len));
2137 
2138 	}
2139 
2140 	return buf.toString();
2141     }
2142 
2143 }
2144