xref: /illumos-gate/usr/src/lib/libslp/javalib/com/sun/slp/AttributeVerifier.java (revision 89b2a9fbeabf42fa54594df0e5927bcc50a07cc9)
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 
952 		    new Float(version);
953 		} catch (NumberFormatException ex) {
954 
955 		    throw
956 			new ServiceLocationException(
957 				ServiceLocationException.PARSE_ERROR,
958 				"template_vers_err",
959 				new Object[] {Integer.toString(tk.lineno())});
960 
961 		}
962 
963 		this.version = version;
964 
965 		// Check for extra stuff.
966 
967 		if ((tt = stk.nextToken()) != StreamTokenizer.TT_EOF) {
968 		    throw
969 			new ServiceLocationException(
970 				ServiceLocationException.PARSE_ERROR,
971 				"template_vers_err",
972 				new Object[] {Integer.toString(tk.lineno())});
973 		}
974 
975 		mask = VERSION_MASK;
976 	    } else {
977 
978 		duplicate = true;
979 	    }
980 	} else if (id.equalsIgnoreCase(
981 				SLPTemplateRegistry.DESCRIPTION_ATTR_ID)) {
982 
983 	    // Make sure there is nothing else on that line.
984 
985 	    if (stk.nextToken() != StreamTokenizer.TT_EOF) {
986 
987 		throw
988 		    new ServiceLocationException(
989 				ServiceLocationException.PARSE_ERROR,
990 				"template_attr_syntax",
991 				new Object[] {Integer.toString(tk.lineno())});
992 	    }
993 
994 	    if ((found & DESCRIPTION_MASK) == 0) {
995 
996 		// Need to continue parsing help text until we reach a blank
997 		// line.
998 
999 		String helpText = "";
1000 
1001 		do {
1002 		    int ptt = tt;
1003 		    tt = tk.nextToken();
1004 
1005 		    if (tt == StreamTokenizer.TT_WORD) {
1006 
1007 			helpText = helpText + tk.sval + "\n";
1008 
1009 		    } else if (tt == StreamTokenizer.TT_EOL) {
1010 
1011 			// If previous token was end of line, quit.
1012 
1013 			if (ptt == StreamTokenizer.TT_EOL) {
1014 
1015 			    // Store any text first.
1016 
1017 			    if (helpText.length() > 0) {
1018 				description = helpText;
1019 
1020 			    }
1021 
1022 			    tk.pushBack();  // so same as above
1023 
1024 			    break;
1025 			}
1026 		    } else if (tt == StreamTokenizer.TT_EOF) {
1027 			throw
1028 			    new ServiceLocationException(
1029 				ServiceLocationException.PARSE_ERROR,
1030 				"template_end_error",
1031 				new Object[] {Integer.toString(tk.lineno())});
1032 
1033 		    } else {
1034 
1035 			throw
1036 			    new ServiceLocationException(
1037 				ServiceLocationException.PARSE_ERROR,
1038 				"template_unk_token",
1039 				new Object[] {Integer.toString(tk.lineno())});
1040 
1041 		    }
1042 
1043 		} while (true);
1044 
1045 		mask = DESCRIPTION_MASK;
1046 	    } else {
1047 
1048 		duplicate = true;
1049 	    }
1050 	} else if (id.equalsIgnoreCase(
1051 				SLPTemplateRegistry.SERVICE_URL_ATTR_ID)) {
1052 
1053 	    if ((found & URL_PATH_RULES_MASK) == 0) {
1054 
1055 		String serviceURLGrammer = "";
1056 
1057 		// Pull everything out of the rdr StringReader until empty.
1058 
1059 		int ic;
1060 
1061 		while ((ic = rdr.read()) != -1) {
1062 		    serviceURLGrammer += (char)ic;
1063 
1064 		}
1065 
1066 		serviceURLGrammer += "\n";
1067 
1068 		// Need to continue parsing service URL syntax until we
1069 		// reach a blank line.
1070 
1071 		tt = StreamTokenizer.TT_EOL;
1072 
1073 		do {
1074 		    int ptt = tt;
1075 		    tt = tk.nextToken();
1076 
1077 		    if (tt == StreamTokenizer.TT_WORD) {
1078 
1079 			serviceURLGrammer = serviceURLGrammer + tk.sval + "\n";
1080 
1081 		    } else if (tt == StreamTokenizer.TT_EOL) {
1082 
1083 			// If previous token was end of line, quit.
1084 
1085 			if (ptt == StreamTokenizer.TT_EOL) {
1086 
1087 			    // Store any text first.
1088 
1089 			    if (serviceURLGrammer.length() > 0) {
1090 				URLSyntax = serviceURLGrammer;
1091 
1092 			    }
1093 
1094 			    tk.pushBack();  // so same as above.
1095 
1096 			    break;
1097 			}
1098 		    } else if (tt == StreamTokenizer.TT_EOF) {
1099 			throw
1100 			    new ServiceLocationException(
1101 				ServiceLocationException.PARSE_ERROR,
1102 				"template_end_error",
1103 				new Object[] {Integer.toString(tk.lineno())});
1104 
1105 		    } else {
1106 
1107 			throw
1108 			    new ServiceLocationException(
1109 				ServiceLocationException.PARSE_ERROR,
1110 				"template_unk_token",
1111 				new Object[] {Integer.toString(tk.lineno())});
1112 
1113 		    }
1114 
1115 		} while (true);
1116 
1117 		mask = URL_PATH_RULES_MASK;
1118 	    } else {
1119 
1120 		duplicate = true;
1121 	    }
1122 	} else {
1123 
1124 	    throw
1125 		new ServiceLocationException(
1126 				ServiceLocationException.PARSE_ERROR,
1127 				"template_nontattribute_err",
1128 				new Object[] {Integer.toString(tk.lineno())});
1129 
1130 	}
1131 
1132 	// Throw exception if a duplicate definition was detected.
1133 
1134 	if (duplicate) {
1135 
1136 	    throw
1137 		new ServiceLocationException(
1138 				ServiceLocationException.PARSE_ERROR,
1139 				"template_dup_def",
1140 				new Object[] {Integer.toString(tk.lineno())});
1141 
1142 	}
1143 
1144 
1145 	// Make sure the assignment ends with a blank line.
1146 
1147 	if ((tt = tk.nextToken()) != StreamTokenizer.TT_EOL) {
1148 
1149 	    throw
1150 		new ServiceLocationException(
1151 				ServiceLocationException.PARSE_ERROR,
1152 				"template_attr_syntax",
1153 				new Object[] {Integer.toString(tk.lineno())});
1154 
1155 	}
1156 
1157 	return mask;
1158 
1159     }
1160 
1161 
1162     // Parse the attributes from the tokenizer.
1163 
1164     private void parseAttributes(StreamTokenizer tk)
1165 	throws ServiceLocationException {
1166 
1167 	try {
1168 
1169 	    do {
1170 
1171 		// Check if at end of file yet.
1172 
1173 		int tt = tk.nextToken();
1174 
1175 		if (tt == StreamTokenizer.TT_EOF) {
1176 		    break;
1177 		}
1178 
1179 		// If not, push token back so we can get it next time.
1180 
1181 		tk.pushBack();
1182 
1183 		// Parse off the attribute descriptor.
1184 
1185 		AttributeDescriptor attDesc = parseAttribute(tk);
1186 
1187 		// Check whether default values, if any, are correct.
1188 
1189 		checkDefaultValues(attDesc);
1190 
1191 		// If the attribute already exists, then throw exception.
1192 		//  We could arguably replace existing, but it might
1193 		//  suprise the user.
1194 
1195 		String attrId = attDesc.getId().toLowerCase();
1196 
1197 		if (attributeDescriptors.get(attrId) != null) {
1198 
1199 		    throw
1200 			new ServiceLocationException(
1201 				ServiceLocationException.PARSE_ERROR,
1202 				"template_dup_def",
1203 				new Object[] {Integer.toString(tk.lineno())});
1204 
1205 		}
1206 
1207 		// Add the attribute to the descriptor table.
1208 
1209 		attributeDescriptors.put(attrId, attDesc);
1210 
1211 	    } while (true);
1212 
1213 	} catch (IOException ex) {
1214 
1215 	    throw
1216 		new ServiceLocationException(
1217 				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
1218 				"template_io_error",
1219 				new Object[] {Integer.toString(tk.lineno())});
1220 	}
1221 
1222     }
1223 
1224     // Parse a single attribute description from the tokenizer.
1225 
1226     private AttributeDescriptor
1227 	parseAttribute(StreamTokenizer tk) throws ServiceLocationException {
1228 
1229 	AttributeDescriptor attDesc = new AttributeDescriptor();
1230 	int lineno = 0;
1231 
1232 	try {
1233 
1234 	    // Parse the string for attribute id, type, and flags.
1235 
1236 	    lineno = tk.lineno();
1237 
1238 	    int tt = tk.nextToken();
1239 
1240 	    if (tt != StreamTokenizer.TT_WORD) {
1241 		throw
1242 		    new ServiceLocationException(
1243 				ServiceLocationException.PARSE_ERROR,
1244 				"template_attr_syntax",
1245 				new Object[] {Integer.toString(tk.lineno())});
1246 	    }
1247 
1248 	    StreamTokenizer stk =
1249 		new StreamTokenizer(new StringReader(tk.sval));
1250 
1251 	    initIdChar(stk);
1252 
1253 	    // Parse the attribute id.
1254 
1255 	    parseId(stk, attDesc, lineno);
1256 
1257 	    // Parse the type and flags.
1258 
1259 	    parseTypeAndFlags(stk, attDesc, lineno);
1260 
1261 	    tt = tk.nextToken();
1262 
1263 	    if (tt == StreamTokenizer.TT_EOF) {
1264 
1265 		throw
1266 		    new ServiceLocationException(
1267 				ServiceLocationException.PARSE_ERROR,
1268 				"template_end_error",
1269 				new Object[] {Integer.toString(tk.lineno())});
1270 
1271 	    }
1272 
1273 	    if (tt != StreamTokenizer.TT_EOL) {
1274 
1275 		throw
1276 		    new ServiceLocationException(
1277 				ServiceLocationException.PARSE_ERROR,
1278 				"template_unk_token",
1279 				new Object[] {Integer.toString(tk.lineno())});
1280 
1281 	    }
1282 
1283 	    // Parse initial values.
1284 
1285 	    if (!attDesc.getIsKeyword()) {
1286 
1287 		String tok = "";
1288 
1289 		// Read in entire list.
1290 
1291 		do {
1292 		    int ptt = tt;
1293 		    lineno = tk.lineno();
1294 		    tt = tk.nextToken();
1295 
1296 		    if (tt == StreamTokenizer.TT_WORD) {
1297 
1298 			// Trim line, check for '#', indicating end of list.
1299 
1300 			String line = tk.sval.trim();
1301 
1302 			if (line.charAt(0) == TT_FIELD) {
1303 			    // it's help text already.
1304 
1305 			    if (tok.length() > 0) {
1306 				stk =
1307 				    new StreamTokenizer(new StringReader(tok));
1308 				parseDefaultValues(stk, attDesc, lineno);
1309 			    }
1310 
1311 			    tk.pushBack();
1312 			    break;
1313 
1314 			} else {
1315 
1316 			    // Otherwise concatenate onto growing list.
1317 
1318 			    tok = tok + line;
1319 
1320 			}
1321 
1322 		    } else if (tt == StreamTokenizer.TT_EOL) {
1323 
1324 			if (ptt == StreamTokenizer.TT_EOL) {
1325 			    // end of attribute definition.
1326 
1327 			    // Process any accumulated list.
1328 
1329 			    if (tok.length() > 0) {
1330 				stk =
1331 				    new StreamTokenizer(new StringReader(tok));
1332 				parseDefaultValues(stk, attDesc, lineno);
1333 			    }
1334 
1335 			    return attDesc;
1336 
1337 			}
1338 		    } else if (tt == StreamTokenizer.TT_EOF) {
1339 			throw
1340 			    new ServiceLocationException(
1341 				ServiceLocationException.PARSE_ERROR,
1342 				"template_end_error",
1343 				new Object[] {Integer.toString(tk.lineno())});
1344 
1345 		    } else {
1346 
1347 			throw
1348 			    new ServiceLocationException(
1349 				ServiceLocationException.PARSE_ERROR,
1350 				"template_unk_token",
1351 				new Object[] {Integer.toString(tk.lineno())});
1352 
1353 		    }
1354 
1355 		} while (true);
1356 
1357 	    } else {
1358 		attDesc.setDefaultValues(null);
1359 		attDesc.setAllowedValues(null);
1360 
1361 		// Check for end of definition.
1362 
1363 		if ((tt = tk.nextToken()) == StreamTokenizer.TT_EOL) {
1364 		    return attDesc;
1365 
1366 		} else if (tt == StreamTokenizer.TT_WORD) {
1367 
1368 		    // Check for start of help text.
1369 
1370 		    String line = tk.sval.trim();
1371 
1372 		    if (line.charAt(0) != TT_FIELD) {
1373 			throw
1374 			    new ServiceLocationException(
1375 				ServiceLocationException.PARSE_ERROR,
1376 				"template_attr_syntax",
1377 				new Object[] {Integer.toString(tk.lineno())});
1378 
1379 		    } else {
1380 
1381 			tk.pushBack();
1382 
1383 		    }
1384 
1385 		} else if (tt == StreamTokenizer.TT_EOF) {
1386 		    throw
1387 			new ServiceLocationException(
1388 				ServiceLocationException.PARSE_ERROR,
1389 				"template_end_error",
1390 				new Object[] {Integer.toString(tk.lineno())});
1391 
1392 		} else {
1393 
1394 		    throw
1395 			new ServiceLocationException(
1396 				ServiceLocationException.PARSE_ERROR,
1397 				"template_unk_token",
1398 				new Object[] {Integer.toString(tk.lineno())});
1399 
1400 		}
1401 	    }
1402 
1403 
1404 	    // Parse help text.
1405 
1406 	    String helpText = "";
1407 
1408 	    do {
1409 		int ptt = tt;
1410 		lineno = tk.lineno();
1411 		tt = tk.nextToken();
1412 
1413 		if (tt == StreamTokenizer.TT_WORD) {
1414 
1415 		    // Check for end of help text.
1416 
1417 		    String line = tk.sval.trim();
1418 
1419 		    if (line.charAt(0) == TT_FIELD) {
1420 
1421 			// Help text is collected verbatim after '#'.
1422 
1423 			helpText =
1424 			    helpText + line.substring(1) + "\n";
1425 
1426 		    } else {
1427 
1428 			// We've reached the end of the help text. Store it
1429 			//  and break out of the loop.
1430 
1431 			if (helpText.length() > 0) {
1432 			    attDesc.setDescription(helpText);
1433 			}
1434 
1435 			tk.pushBack();
1436 			break;
1437 
1438 		    }
1439 
1440 		} else if (tt == StreamTokenizer.TT_EOL ||
1441 			   tt == StreamTokenizer.TT_EOF) {
1442 
1443 		    // If previous token was end of line, quit.
1444 
1445 		    if (ptt == StreamTokenizer.TT_EOL) {
1446 
1447 			// Store any text first.
1448 
1449 			if (helpText.length() > 0) {
1450 			    attDesc.setDescription(helpText);
1451 			}
1452 
1453 			// If this is a keyword attribute, set the allowed
1454 			//  values list to null.
1455 
1456 			if (attDesc.getIsKeyword()) {
1457 			    attDesc.setAllowedValues(null);
1458 			}
1459 
1460 			return attDesc;
1461 
1462 		    } else if (tt == StreamTokenizer.TT_EOF) {
1463 
1464 			// Error if previous token wasn't EOL.
1465 
1466 			throw
1467 			    new ServiceLocationException(
1468 				ServiceLocationException.PARSE_ERROR,
1469 				"template_end_error",
1470 				new Object[] {Integer.toString(tk.lineno())});
1471 		    }
1472 
1473 		} else {
1474 
1475 		    throw
1476 			new ServiceLocationException(
1477 				ServiceLocationException.PARSE_ERROR,
1478 				"template_unk_token",
1479 				new Object[] {Integer.toString(tk.lineno())});
1480 		}
1481 
1482 	    } while (true);
1483 
1484 	    // Parse allowed values.
1485 
1486 	    if (!attDesc.getIsKeyword()) {
1487 
1488 		String tok = "";
1489 
1490 		// Read in entire list.
1491 
1492 		do {
1493 		    int ptt = tt;
1494 		    lineno = tk.lineno();
1495 		    tt = tk.nextToken();
1496 
1497 		    if (tt == StreamTokenizer.TT_WORD) {
1498 
1499 			// Concatenate onto growing list.
1500 
1501 			tok = tok + tk.sval;
1502 
1503 		    } else if (tt == StreamTokenizer.TT_EOL) {
1504 
1505 			if (ptt == StreamTokenizer.TT_EOL) {
1506 			    // end of attribute definition.
1507 
1508 			    // Process any accumulated list.
1509 
1510 			    if (tok.length() > 0) {
1511 				stk =
1512 				    new StreamTokenizer(new StringReader(tok));
1513 				parseAllowedValues(stk, attDesc, lineno);
1514 			    }
1515 
1516 			    return attDesc;
1517 
1518 			}
1519 		    } else if (tt == StreamTokenizer.TT_EOF) {
1520 			throw
1521 			    new ServiceLocationException(
1522 				ServiceLocationException.PARSE_ERROR,
1523 				"template_end_error",
1524 				new Object[] {Integer.toString(tk.lineno())});
1525 
1526 		    } else {
1527 
1528 			throw
1529 			    new ServiceLocationException(
1530 				ServiceLocationException.PARSE_ERROR,
1531 				"template_unk_token",
1532 				new Object[] {Integer.toString(tk.lineno())});
1533 		    }
1534 
1535 		} while (true);
1536 
1537 	    } else {
1538 
1539 		// Error. Keyword attribute should have ended during help text
1540 		//  parsing or before.
1541 
1542 		throw
1543 		    new ServiceLocationException(
1544 				ServiceLocationException.PARSE_ERROR,
1545 				"template_attr_syntax",
1546 				new Object[] {Integer.toString(tk.lineno())});
1547 	    }
1548 
1549 	} catch (IOException ex) {
1550 
1551 	    throw
1552 		new ServiceLocationException(
1553 				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
1554 				"template_io_error",
1555 				new Object[] {
1556 		    Integer.toString(tk.lineno()),
1557 			ex.getMessage()});
1558 	}
1559 
1560     }
1561 
1562     // Check whether the default values, if any, are correct.
1563 
1564     private void checkDefaultValues(AttributeDescriptor attDesc)
1565 	throws ServiceLocationException {
1566 
1567 	// Don't bother if it's a keyword attribute, parsing has checked.
1568 
1569 	if (attDesc.getIsKeyword()) {
1570 	    return;
1571 	}
1572 
1573 	Enumeration init = attDesc.getDefaultValues();
1574 	Enumeration en = attDesc.getAllowedValues();
1575 	Vector allowed = new Vector();
1576 	String attDescType = attDesc.getValueType();
1577 
1578 	// First, collect the allowed values.
1579 
1580 	while (en.hasMoreElements()) {
1581 	    Object allval = en.nextElement();
1582 
1583 	    // Lower case strings and create opaques for comparison
1584 	    // if type is opaque.
1585 
1586 	    if (attDescType.equals(JAVA_STRING_TYPE)) {
1587 		allval = ((String)allval).toLowerCase();
1588 
1589 	    } else if (attDescType.equals(JAVA_OPAQUE_TYPE)) {
1590 		allval = new Opaque((byte[])allval);
1591 
1592 	    }
1593 
1594 	    allowed.addElement(allval);
1595 	}
1596 
1597 	// Now compare the allowed with the initial.
1598 
1599 	if (allowed.size() > 0) {
1600 
1601 	    // Error if allowed is restricted but no initializers.
1602 
1603 	    if (!init.hasMoreElements()) {
1604 
1605 		throw
1606 		    new ServiceLocationException(
1607 				ServiceLocationException.PARSE_ERROR,
1608 				"template_no_init",
1609 				new Object[] {attDesc.getId()});
1610 
1611 	    }
1612 
1613 	    Object val = null;
1614 
1615 	    // Compare init values with allowed.
1616 
1617 	    while (init.hasMoreElements()) {
1618 		Object test = init.nextElement();
1619 		val = test; // for exception..
1620 
1621 		if (attDescType.equals(JAVA_STRING_TYPE)) {
1622 		    test = ((String)test).toLowerCase();
1623 
1624 		} else if (attDescType.equals(JAVA_OPAQUE_TYPE)) {
1625 		    test = new Opaque((byte[])test);
1626 
1627 		}
1628 
1629 		if (allowed.indexOf(test) != -1) {
1630 		    return; // found it!
1631 		}
1632 	    }
1633 	    // Initializer wasn't found.
1634 
1635 	    throw
1636 		new ServiceLocationException(
1637 				ServiceLocationException.PARSE_ERROR,
1638 				"template_wrong_init",
1639 				new Object[] {
1640 		    val.toString(), attDesc.getId()});
1641 	}
1642     }
1643 
1644     // Parse the attribute's id string.
1645 
1646     private void parseId(StreamTokenizer tk,
1647 			 AttributeDescriptor attDesc,
1648 			 int baseLineno)
1649 	throws ServiceLocationException, IOException {
1650 
1651 	// Parse the attribute's identifier tag.
1652 
1653 	String id = parseWord(tk, baseLineno);
1654 
1655 	int tt = tk.nextToken();
1656 
1657 	// Parse the seperator.
1658 
1659 	if (tt != TT_EQUALS) {
1660 	    throw
1661 		new ServiceLocationException(
1662 				ServiceLocationException.PARSE_ERROR,
1663 				"template_attr_syntax",
1664 				new Object[] {
1665 		    Integer.toString(tk.lineno() + baseLineno)});
1666 
1667 	}
1668 
1669 	// Expand out any escaped ``#''. It won't be handled by
1670 	// SLA.
1671 
1672 	id = unescapeHash(id);
1673 
1674 	// Expand out character escapes.
1675 
1676 	id =
1677 	    ServiceLocationAttribute.unescapeAttributeString(id, true);
1678 
1679 
1680 	attDesc.setId(id);
1681     }
1682 
1683     // Parse the attribute's type and flags.
1684 
1685     private void
1686 	parseTypeAndFlags(StreamTokenizer tk,
1687 			  AttributeDescriptor attDesc,
1688 			  int baseLineno)
1689 	throws ServiceLocationException, IOException {
1690 
1691 	int existingFlags = 0;
1692 
1693 	// Parse the attribute's type.
1694 
1695 	String type = parseWord(tk, baseLineno);
1696 
1697 	checkAndAddType(type, attDesc, tk.lineno() + baseLineno);
1698 
1699 	// Parse the flags.
1700 
1701 	do {
1702 
1703 	    // Check if any flags are left.
1704 
1705 	    if (tk.nextToken() == StreamTokenizer.TT_EOF) {
1706 		break;
1707 
1708 	    } else {
1709 		tk.pushBack();
1710 	    }
1711 
1712 	    int lineno = tk.lineno();
1713 
1714 	    // Parse the flag.
1715 
1716 	    String flag = parseWord(tk, baseLineno);
1717 
1718 	    // Error if flags with keyword.
1719 
1720 	    if (attDesc.getIsKeyword()) {
1721 		throw
1722 		    new ServiceLocationException(
1723 				ServiceLocationException.PARSE_ERROR,
1724 				"template_attr_syntax",
1725 				new Object[] {
1726 			Integer.toString(tk.lineno() + baseLineno)});
1727 	    }
1728 
1729 
1730 	    // Check and assign it to the attribute.
1731 
1732 	    existingFlags =
1733 		existingFlags | checkAndAddFlag(flag,
1734 						existingFlags,
1735 						attDesc,
1736 						baseLineno + lineno);
1737 
1738 	} while (true);
1739     }
1740 
1741     // Parse the attribute's initial value(s).
1742 
1743     private void parseDefaultValues(StreamTokenizer tk,
1744 				    AttributeDescriptor attDesc,
1745 				    int baseLineno)
1746 	throws ServiceLocationException, IOException {
1747 
1748 	// First get the vector of initial values.
1749 
1750 	Vector vals = parseValueList(tk, attDesc, baseLineno);
1751 
1752 	// Check whether it works for this attribute. Type
1753 	//  checking will be done by value list parsing.
1754 
1755 	if (!attDesc.getIsMultivalued() && vals.size() > 1) {
1756 	    throw
1757 		new ServiceLocationException(
1758 				ServiceLocationException.PARSE_ERROR,
1759 				"template_attr_syntax",
1760 				new Object[] {
1761 		    Integer.toString(tk.lineno() + baseLineno)});
1762 	}
1763 
1764 	attDesc.setDefaultValues(vals);
1765     }
1766 
1767     // Parse the attribute's allowed values.
1768 
1769     private void
1770 	parseAllowedValues(StreamTokenizer tk,
1771 			   AttributeDescriptor attDesc,
1772 			   int baseLineno)
1773 	throws ServiceLocationException, IOException {
1774 
1775 	// First get the vector of all allowed values.
1776 
1777 	Vector vals = parseValueList(tk, attDesc, baseLineno);
1778 
1779 	// Now set the allowed value vector.
1780 
1781 	attDesc.setAllowedValues(vals);
1782     }
1783 
1784     // Parse a value list.
1785 
1786     private Vector parseValueList(StreamTokenizer stk,
1787 				  AttributeDescriptor attDesc,
1788 				  int baseLineno)
1789 	throws ServiceLocationException, IOException {
1790 
1791 	Vector req = new Vector();
1792 
1793 	// Set up the tokenizer according to the type of the
1794 	//  attribute.
1795 
1796 	String type = attDesc.getValueType();
1797 
1798 	if (type.equals(JAVA_STRING_TYPE) || type.equals(JAVA_OPAQUE_TYPE)) {
1799 	    initStringItemChar(stk);
1800 	} else if (type.equals(JAVA_INTEGER_TYPE)) {
1801 	    initIntItemChar(stk);
1802 	} else if (type.equals(JAVA_BOOLEAN_TYPE)) {
1803 	    initIdChar(stk);
1804 	}
1805 
1806 	// Parse through a potentially multivalued value list.
1807 
1808 	boolean wordRequired = true;	// true when a word is required,
1809 					// false when a comma required.
1810 	boolean syntaxError = false;
1811 	String reqTok = "";
1812 	int lineno = 0;
1813 
1814 	do {
1815 	    int tt = stk.nextToken();
1816 	    lineno = stk.lineno() + baseLineno;
1817 
1818 	    if (tt ==  StreamTokenizer.TT_WORD) {
1819 
1820 		// If a word isn't required, then the case is
1821 		//  "token token" and is an error.
1822 
1823 		if (!wordRequired) {
1824 		    syntaxError = true;
1825 		}
1826 
1827 		reqTok = stk.sval.trim();
1828 
1829 		// Convert the value to the proper object.
1830 
1831 		Object reqVal = convertValue(type, reqTok, baseLineno);
1832 		req.addElement(reqVal);
1833 
1834 		wordRequired = false;
1835 
1836 	    } else if (tt == StreamTokenizer.TT_EOF) {
1837 
1838 		// If a word is required, then list ends with
1839 		//  a comma, so error.
1840 
1841 		if (wordRequired) {
1842 		    syntaxError = true;
1843 		}
1844 
1845 		break;
1846 
1847 	    } else if (tt == TT_COMMA) {
1848 
1849 		// If a word is required, then error. The case is ",,".
1850 
1851 		if (wordRequired) {
1852 		    syntaxError = true;
1853 		    break;
1854 		}
1855 
1856 		// Otherwise, the next token must be a word.
1857 
1858 		wordRequired = true;
1859 
1860 	    } else {
1861 
1862 		// No other tokens are allowed.
1863 
1864 		syntaxError = true;
1865 		break;
1866 	    }
1867 
1868 	} while (true);
1869 
1870 	if (syntaxError) {
1871 
1872 	    throw
1873 		new ServiceLocationException(
1874 				ServiceLocationException.PARSE_ERROR,
1875 				"template_attr_syntax",
1876 				new Object[] {Integer.toString(lineno)});
1877 	}
1878 
1879 	return req;
1880 
1881     }
1882 
1883     // Check the type and add it to the attribute descriptor.
1884 
1885     private void checkAndAddType(String type,
1886 				 AttributeDescriptor attDesc,
1887 				 int lineno)
1888 	throws ServiceLocationException {
1889 
1890 	// Check token against recognized types.
1891 
1892 	if (type.equalsIgnoreCase(STRING_TYPE)) {
1893 	    attDesc.setValueType(JAVA_STRING_TYPE);
1894 
1895 	} else if (type.equalsIgnoreCase(INTEGER_TYPE)) {
1896 	    attDesc.setValueType(JAVA_INTEGER_TYPE);
1897 
1898 	} else if (type.equalsIgnoreCase(BOOLEAN_TYPE)) {
1899 	    attDesc.setValueType(JAVA_BOOLEAN_TYPE);
1900 
1901 	} else if (type.equalsIgnoreCase(OPAQUE_TYPE)) {
1902 	    attDesc.setValueType(JAVA_OPAQUE_TYPE);
1903 
1904 	} else if (type.equalsIgnoreCase(KEYWORD_TYPE)) {
1905 	    attDesc.setIsKeyword(true);
1906 
1907 	} else {
1908 
1909 	    throw
1910 		new ServiceLocationException(
1911 				ServiceLocationException.PARSE_ERROR,
1912 				"template_not_slp_type",
1913 				new Object[] {Integer.toString(lineno)});
1914 	}
1915 
1916     }
1917 
1918     // Check the flag and add it to the attribute descriptor.
1919 
1920     private int checkAndAddFlag(String flag,
1921 				int matched,
1922 				AttributeDescriptor attDesc,
1923 				int lineno)
1924 	throws ServiceLocationException {
1925 
1926 	boolean duplicate = false;
1927 
1928 	// We depend on the attribute descriptor being initialized to
1929 	// nothing, i.e. false for all flags and for keyword.
1930 
1931 	if (flag.equalsIgnoreCase(MULTIPLE_FLAG)) {
1932 
1933 	    if ((matched & MULTIPLE_MASK) != 0) {
1934 		duplicate = true;
1935 
1936 	    } else {
1937 
1938 		// Check for boolean. Booleans may not have
1939 		// multiple values.
1940 
1941 		if (attDesc.getValueType().equals(JAVA_BOOLEAN_TYPE)) {
1942 
1943 		    throw
1944 			new ServiceLocationException(
1945 				ServiceLocationException.PARSE_ERROR,
1946 				"template_boolean_multi",
1947 				new Object[] {Integer.toString(lineno)});
1948 		}
1949 
1950 		attDesc.setIsMultivalued(true);
1951 		return MULTIPLE_MASK;
1952 
1953 	    }
1954 
1955 	} else if (flag.equalsIgnoreCase(LITERAL_FLAG)) {
1956 
1957 	    if ((matched & LITERAL_MASK) != 0) {
1958 		duplicate = true;
1959 
1960 	    } else {
1961 		attDesc.setIsLiteral(true);
1962 		return LITERAL_MASK;
1963 	    }
1964 
1965 	} else if (flag.equalsIgnoreCase(EXPLICIT_FLAG)) {
1966 
1967 	    if ((matched & EXPLICIT_MASK) != 0) {
1968 		duplicate = true;
1969 
1970 	    } else {
1971 		attDesc.setRequiresExplicitMatch(true);
1972 		return EXPLICIT_MASK;
1973 	    }
1974 
1975 	} else if (flag.equalsIgnoreCase(OPTIONAL_FLAG)) {
1976 
1977 	    if ((matched & OPTIONAL_MASK) != 0) {
1978 		duplicate = true;
1979 
1980 	    } else {
1981 		attDesc.setIsOptional(true);
1982 		return OPTIONAL_MASK;
1983 	    }
1984 
1985 	} else {
1986 
1987 	    throw
1988 		new ServiceLocationException(
1989 				ServiceLocationException.PARSE_ERROR,
1990 				"template_invalid_attr_flag",
1991 				new Object[] {Integer.toString(lineno)});
1992 	}
1993 
1994 
1995 	if (duplicate) {
1996 	    throw
1997 		new ServiceLocationException(
1998 				ServiceLocationException.PARSE_ERROR,
1999 				"template_dup_attr_flag",
2000 				new Object[] {Integer.toString(lineno)});
2001 	}
2002 
2003 	return 0; // never happens.
2004     }
2005 
2006     // Parse a word out of the tokenizer. The exact characters
2007     //  will depend on what the syntax tables have been set to.
2008 
2009     private String parseWord(StreamTokenizer tk, int baseLineno)
2010 	throws ServiceLocationException, IOException {
2011 
2012 	int tt = tk.nextToken();
2013 
2014 	if (tt == StreamTokenizer.TT_WORD) {
2015 	    return (tk.sval);
2016 
2017 	} else {
2018 
2019 	    String errorToken = "";
2020 
2021 	    // Report the erroneous characters.
2022 
2023 	    if (tt == StreamTokenizer.TT_NUMBER) {
2024 		errorToken = Double.toString(tk.nval);
2025 	    } else if (tt == StreamTokenizer.TT_EOL) {
2026 		errorToken = "<end of line>";
2027 	    } else if (tt == StreamTokenizer.TT_EOF) {
2028 		errorToken = "<end of file>";
2029 	    } else {
2030 		errorToken = (new Character((char)tt)).toString();
2031 	    }
2032 
2033 	    throw
2034 		new ServiceLocationException(
2035 				ServiceLocationException.PARSE_ERROR,
2036 				"template_invalid_tok",
2037 				new Object[] {
2038 		    Integer.toString(tk.lineno() + baseLineno)});
2039 
2040 	}
2041 
2042     }
2043 
2044     // Convert a value list token to the value.
2045 
2046     private Object convertValue(String type,
2047 				String reqTok,
2048 				int lineno)
2049 	throws ServiceLocationException,
2050 	       IOException {
2051 
2052 	Object reqVal = null;
2053 
2054 	if (type.equals(JAVA_STRING_TYPE)) {
2055 
2056 	    // Expand out any escaped ``#''. It won't be handled by
2057 	    //  SLA.
2058 
2059 	    reqTok = unescapeHash(reqTok);
2060 
2061 	    // Expand out character escapes.
2062 
2063 	    reqVal =
2064 		ServiceLocationAttribute.unescapeAttributeString(reqTok,
2065 								 false);
2066 
2067 	} else if (type.equals(JAVA_INTEGER_TYPE)) {
2068 
2069 	    try {
2070 
2071 		reqVal = Integer.valueOf(reqTok);
2072 
2073 	    } catch (NumberFormatException ex) {
2074 
2075 		throw
2076 		    new ServiceLocationException(
2077 				ServiceLocationException.PARSE_ERROR,
2078 				"template_expect_int",
2079 				new Object[] {
2080 			Integer.toString(lineno), reqTok });
2081 	    }
2082 	} else if (type.equals(JAVA_BOOLEAN_TYPE)) {
2083 
2084 	    // Boolean.valueOf() doesn't handle this properly.
2085 
2086 	    if (reqTok.equalsIgnoreCase(TRUE_TOKEN)) {
2087 
2088 		reqVal = new Boolean(true);
2089 
2090 	    } else if (reqTok.equalsIgnoreCase(FALSE_TOKEN)) {
2091 
2092 		reqVal = new Boolean(false);
2093 
2094 	    } else {
2095 
2096 		throw
2097 		    new ServiceLocationException(
2098 				ServiceLocationException.PARSE_ERROR,
2099 				"template_expect_bool",
2100 				new Object[] {
2101 			Integer.toString(lineno), reqTok});
2102 	    }
2103 	} else if (type.equals(JAVA_OPAQUE_TYPE)) {
2104 
2105 	    reqVal = Opaque.unescapeByteArray(reqTok);
2106 
2107 	} else {
2108 
2109 	    Assert.slpassert(false,
2110 			  "template_attr_desc",
2111 			  new Object[0]);
2112 	}
2113 
2114 	return reqVal;
2115     }
2116 
2117     // Expand out any escaped hashes. Not handled by SLA.
2118 
2119     private String unescapeHash(String str) {
2120 
2121 	StringBuffer buf = new StringBuffer();
2122 	int len = ESC_HASH.length();
2123 	int i, j = 0;
2124 
2125 	for (i = str.indexOf(ESC_HASH, j);
2126 	    i != -1;
2127 	    i = str.indexOf(ESC_HASH, j)) {
2128 
2129 	    buf.append(str.substring(j, i));
2130 	    buf.append(HASH);
2131 	    j = i + len;
2132 	}
2133 
2134 	len = str.length();
2135 
2136 	if (j < len) {
2137 	    buf.append(str.substring(j, len));
2138 
2139 	}
2140 
2141 	return buf.toString();
2142     }
2143 
2144 }
2145