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