xref: /illumos-gate/usr/src/lib/libslp/javalib/com/sun/slp/ServiceLocationAttribute.java (revision 48edc7cf07b5dccc3ad84bf2dafe4150bd666d60)
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 2001,2003 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  */
26 
27 //  ServiceLocationAttribute.java : Class for attributes in SLP.
28 //  Author:           James Kempf, Erik Guttman
29 //
30 
31 package com.sun.slp;
32 
33 import java.util.*;
34 import java.io.*;
35 
36 /**
37  * The ServiceLocationAttribute class models SLP attributes.
38  *
39  * @author James Kempf, Erik Guttman
40  */
41 
42 public class ServiceLocationAttribute extends Object
43     implements Serializable {
44 
45     // Characters to escape.
46 
47     final static String RESERVED = "(),\\!<=>~";
48     final static String ESCAPED = RESERVED + "*";
49     final static char ESCAPE = '\\';
50 
51     final static char CTL_LOWER = (char)0x00;
52     final static char CTL_UPPER = (char)0x1F;
53     final static char DEL = (char)0x7F;
54 
55     // Whitespace chars.
56 
57     static final String WHITESPACE = " \n\t\r";
58     static final char SPACE = ' ';
59 
60     // For character escaping.
61 
62     static final char COMMA = ',';
63     static final char PERCENT = '%';
64 
65     // Bad tag characters.
66 
67     final private static String BAD_TAG_CHARS = "*\n\t\r";
68 
69     // For identifying booleans.
70 
71     final static String TRUE = "true";
72     final static String FALSE = "false";
73 
74     //
75     // Package accessable fields.
76     //
77 
78     Vector values = null;
79     String id = null;
80 
81     // For V1 compatibility subclass.
82 
83     ServiceLocationAttribute() {}
84 
85     /**
86      * Construct a service location attribute.
87      *
88      * @param id		The attribute name
89      * @param values_in	Vector of one or more attribute values. Vector
90      *			contents must be uniform in type and one of
91      *			Integer, String, Boolean, or byte[]. If the attribute
92      *			is a keyword attribute, then values_in should be null.
93      * @exception IllegalArgumentException Thrown if the
94      *			vector contents is not of the right type or
95      *			an argument is null or syntactically incorrect.
96      */
97 
98     public ServiceLocationAttribute(String id_in, Vector values_in)
99 	throws IllegalArgumentException {
100 
101 	Assert.nonNullParameter(id_in, "id");
102 
103 	id = id_in;
104 	if (values_in != null &&
105 	    values_in.size() > 0) { // null, empty indicate keyword attribute.
106 
107 	    values = (Vector)values_in.clone();
108 
109 	    verifyValueTypes(values, false);
110 
111 	}
112     }
113 
114     /**
115      * Construct a service location attribute from a parenthesized expression.
116      * The syntax is:
117      *
118      *	 exp = "(" id "=" value-list ")" | keyword
119      *    value-list = value | value "," value-list
120      *
121      *
122      * @param exp The expression
123      * @param dontTypeCheck True if multivalued booleans and vectors
124      *			   of varying types are allowed.
125      * @exception ServiceLocationException If there are any syntax errors.
126      */
127 
128     ServiceLocationAttribute(String exp, boolean allowMultiValuedBooleans)
129 	throws ServiceLocationException {
130 
131 	if (exp == null || exp.length() <= 0) {
132 	    new ServiceLocationException(ServiceLocationException.PARSE_ERROR,
133 					 "null_string_parameter",
134 					 new Object[] {exp});
135 
136 	}
137 
138 	// If start and end paren, then parse out assignment.
139 
140 	if (exp.startsWith("(") && exp.endsWith(")")) {
141 
142 	    StringTokenizer tk =
143 		new StringTokenizer(exp.substring(1, exp.length() - 1),
144 				    "=",
145 				    true);
146 
147 	    try {
148 
149 		// Get the tag.
150 
151 		id =
152 		    unescapeAttributeString(tk.nextToken(), true);
153 
154 		if (id.length() <= 0) {
155 		    throw
156 			new ServiceLocationException(
157 				ServiceLocationException.PARSE_ERROR,
158 				"null_id",
159 				new Object[] {exp});
160 		}
161 
162 		tk.nextToken();  // get rid of "="
163 
164 		// Gather the rest.
165 
166 		String rest = tk.nextToken("");
167 
168 		// Parse the comma separated list.
169 
170 		values = SrvLocHeader.parseCommaSeparatedListIn(rest, true);
171 
172 		// Convert to objects.
173 
174 		int i, n = values.size();
175 		Class vecClass = null;
176 
177 		for (i = 0; i < n; i++) {
178 		    String value = (String)values.elementAt(i);
179 
180 		    // Need to determine which type to use.
181 
182 		    Object o = evaluate(value);
183 
184 		    values.setElementAt(o, i);
185 
186 		}
187 
188 	    } catch (NoSuchElementException ex) {
189 		throw
190 		    new ServiceLocationException(
191 				ServiceLocationException.PARSE_ERROR,
192 				"assignment_syntax_err",
193 				new Object[] {exp});
194 	    }
195 
196 	    verifyValueTypes(values, allowMultiValuedBooleans);
197 
198 	} else {
199 
200 	    // Check to make sure there's no parens.
201 
202 	    if (exp.indexOf('(') != -1 || exp.indexOf(')') != -1) {
203 		throw
204 		    new ServiceLocationException(
205 				ServiceLocationException.PARSE_ERROR,
206 				"assignment_syntax_err",
207 				new Object[] {exp});
208 	    }
209 
210 	    // Unescape the keyword.
211 
212 	    id = unescapeAttributeString(exp, true);
213 
214 	}
215     }
216 
217     static Object evaluate(String value)
218 	throws ServiceLocationException {
219 
220 	Object o = null;
221 
222 	// If it can be converted into an integer, then convert it.
223 
224 	try {
225 
226 	    o = Integer.valueOf(value);
227 
228 	} catch (NumberFormatException ex) {
229 
230 	    // Wasn't an integer. Try boolean.
231 
232 	    if (value.equalsIgnoreCase(TRUE) ||
233 		value.equalsIgnoreCase(FALSE)) {
234 		o = Boolean.valueOf(value);
235 
236 	    } else {
237 
238 		// Process the string to remove escapes.
239 
240 		String val = (String)value;
241 
242 		// If it begins with the opaque prefix, treat it as an
243 		//  opaque.
244 
245 		if (val.startsWith(Opaque.OPAQUE_HEADER)) {
246 		    o = Opaque.unescapeByteArray(val);
247 
248 		} else {
249 		    o = unescapeAttributeString(val, false);
250 
251 		}
252 	    }
253 	}
254 
255 	return o;
256 
257     }
258 
259     //
260     // Property accessors.
261     //
262 
263     /**
264      * @return A vector of attribute values, or null if the attribute is
265      *         a keyword attribute. If the attribute is single-valued, then
266      *         the vector contains only one object.
267      *
268      */
269 
270     public Vector getValues() {
271 
272 	if (values == null) {
273 	    return null;	// keyword case.
274 	}
275 
276 	Vector ret = (Vector)values.clone();
277 
278 	// Need to process Opaques.
279 
280 	int i, n = ret.size();
281 
282 	for (i = 0; i < n; i++) {
283 	    Object o = ret.elementAt(i);
284 
285 	    if (o instanceof Opaque) {
286 		o = ((Opaque)o).bytes;
287 
288 	    }
289 
290 	    ret.setElementAt(o, i);
291 	}
292 
293 	return ret;
294     }
295 
296     /**
297      * @return The attribute name.
298      */
299 
300     public String getId() {
301 
302 	return id;
303 
304     }
305 
306     /**
307      * Return an escaped version of the id parameter , suitable for inclusion
308      * in a query.
309      *
310      * @param str The string to escape as an id.
311      * @return The string with any reserved characters escaped.
312      * @exception IllegalArgumentException Thrown if the
313      *			string contains bad tag characters.
314      */
315 
316     static public String escapeId(String str)
317 	throws IllegalArgumentException {
318 	String ret = null;
319 
320 	try {
321 	    ret = escapeAttributeString(str, true);
322 
323 	} catch (ServiceLocationException ex) {
324 	    throw new IllegalArgumentException(ex.getMessage());
325 
326 	}
327 
328 	return ret;
329     }
330 
331     /**
332      * Return an escaped version of the value parameter, suitable for inclusion
333      * in a query. Opaques are stringified.
334      *
335      * @param val The value to escape.
336      * @return The stringified value.
337      * @exception IllegalArgumentException Thrown if the object is not
338      * 	         one of byte[], Integer, Boolean, or String.
339      */
340 
341     static public String escapeValue(Object val)
342 	throws IllegalArgumentException {
343 
344 	// Check type first.
345 
346 	typeCheckValue(val);
347 
348 	// Make Opaque out of byte[].
349 
350 	if (val instanceof byte[]) {
351 	    val = new Opaque((byte[])val);
352 
353 	}
354 
355 	return  escapeValueInternal(val);
356 
357     }
358 
359     // Check type to make sure it's OK.
360 
361     static private void typeCheckValue(Object obj) {
362 	SLPConfig conf = SLPConfig.getSLPConfig();
363 
364 	Assert.nonNullParameter(obj, "attribute value vector element");
365 
366 	if (obj.equals("")) {
367 	    throw
368 		new IllegalArgumentException(
369 				conf.formatMessage("empty_string_value",
370 						   new Object[0]));
371 	}
372 
373 	if (!(obj instanceof Integer) && !(obj instanceof Boolean) &&
374 	    !(obj instanceof String) && !(obj instanceof byte[])) {
375 	    throw
376 		new IllegalArgumentException(
377 				conf.formatMessage("value_type_error",
378 						   new Object[0]));
379 	}
380 
381     }
382 
383     // We know the value's type is OK, so just escape it.
384 
385     private static String escapeValueInternal(Object val) {
386 
387 	String s;
388 
389 	// Escape any characters needing it.
390 
391 	if (val instanceof String) {
392 
393 	    try {
394 
395 		s = escapeAttributeString((String)val, false);
396 
397 	    } catch (ServiceLocationException ex) {
398 		throw
399 		    new IllegalArgumentException(ex.getMessage());
400 
401 	    }
402 
403 	} else {
404 	    s = val.toString();
405 
406 	}
407 
408 	return s;
409     }
410 
411     //
412     // Methods for dealing with the type of attribute values.
413     //
414 
415     // Verify the types of incoming attributes.
416 
417     protected void
418 	verifyValueTypes(Vector values_in, boolean dontTypeCheck) {
419 
420 	SLPConfig conf = SLPConfig.getSLPConfig();
421 
422 	// Make sure the types of objects passed in are acceptable
423 	//  and that all objects in the vector have the same type.
424 
425 	int i, n = values_in.size();
426 	Class cls = null;
427 
428 	for (i = 0; i < n; i++) {
429 	    Object obj = values_in.elementAt(i);
430 
431 	    typeCheckValue(obj);
432 
433 	    if (i == 0) {
434 		cls = obj.getClass();
435 
436 	    } else if (!cls.equals(obj.getClass()) && !dontTypeCheck) {
437 		throw
438 		    new IllegalArgumentException(
439 				conf.formatMessage("type_mismatch_error",
440 						   new Object[0]));
441 	    }
442 
443 	    // If it's a boolean and there's more than one, signal error
444 	    // unless multivalued booleans are allowed.
445 
446 	    if (!dontTypeCheck && i != 0 && obj instanceof Boolean) {
447 		throw
448 		    new IllegalArgumentException(
449 				conf.formatMessage("multivalued_boolean",
450 						   new Object[0]));
451 
452 	    }
453 
454 	    // If it's a byte array, create a Opaque object.
455 
456 	    if (obj instanceof byte[]) {
457 		values_in.setElementAt(new Opaque((byte[])obj), i);
458 
459 	    } else if (obj instanceof String) {
460 		String val = (String)obj;
461 
462 		// If it's a string and looks like "1" or "true", then
463 		//  append a space onto the end.
464 
465 		try {
466 
467 		    Object obj2 = evaluate(val);
468 
469 		    if (!(obj2 instanceof String)) {
470 			values_in.setElementAt((String)val + " ", i);
471 
472 		    }
473 
474 		} catch (ServiceLocationException ex) {
475 
476 		    // Ignore for now.
477 
478 		}
479 	    }
480 	}
481 
482     }
483 
484     //
485     // Methods for externalizing attributes.
486     //
487 
488     /**
489      * Externalize the attribute into a string that can be written
490      * to a byte stream. Includes escaping any characters that
491      * need to be escaped.
492      *
493      * @return String with attribute's external representation.
494      * @exception ServiceLocationException Thrown if the
495      *			string contains unencodable characters.
496      */
497 
498     String externalize()
499 	throws ServiceLocationException {
500 
501 	if (values == null) {	// keyword attribute...
502 	    return escapeAttributeString(id, true);
503 	}
504 
505 	Vector v = new Vector();
506 
507 	for (Enumeration e = values.elements(); e.hasMoreElements(); ) {
508 	    Object o = e.nextElement();
509 	    String s = null;
510 
511 	    s = escapeValueInternal(o);
512 
513 	    v.addElement(s);
514 	}
515 
516 	StringBuffer buf =
517 	    new StringBuffer("(" +
518 			     escapeAttributeString(id, true) +
519 			     "=");
520 
521 	buf.append(SrvLocHeader.vectorToCommaSeparatedList(v));
522 
523 	buf.append(")");
524 
525 	return buf.toString();
526     }
527 
528     //
529     // Escaping and unescaping strings.
530     //
531 
532     /**
533      * Escape any escapable characters to a 2 character escape
534      * in the attribute string.
535      *
536      * @param string The String.
537      * @param badTag Check for bad tag characters if true.
538      * @return The escaped string.
539      * @exception ServiceLocationException Thrown if the string
540      *			contains a character that can't be encoded.
541      */
542 
543     static String escapeAttributeString(String string,
544 					boolean badTag)
545 	throws ServiceLocationException {
546 
547 	StringBuffer buf = new StringBuffer();
548 	int i, n = string.length();
549 
550 	for (i = 0; i < n; i++) {
551 	    char c = string.charAt(i);
552 
553 	    // Check for bad tag characters first.
554 
555 	    if (badTag && BAD_TAG_CHARS.indexOf(c) != -1) {
556 		throw
557 		    new ServiceLocationException(
558 				ServiceLocationException.PARSE_ERROR,
559 				"bad_id_char",
560 				new Object[] {Integer.toHexString(c)});
561 	    }
562 
563 	    // Escape if the character is reserved.
564 
565 	    if (canEscape(c)) {
566 		buf.append(ESCAPE);
567 
568 		String str = escapeChar(c);
569 
570 		// Pad with zero if less than 2 characters.
571 
572 		if (str.length() <= 1) {
573 		    str = "0" + str;
574 
575 		}
576 
577 		buf.append(str);
578 
579 	    } else {
580 
581 		buf.append(c);
582 
583 	    }
584 	}
585 
586 	return buf.toString();
587 
588     }
589 
590 
591     /**
592      * Convert any 2 character escapes to the corresponding characters.
593      *
594      * @param string The string to be processed.
595      * @param badTag Check for bad tag characters if true.
596      * @return The processed string.
597      * @exception ServiceLocationException Thrown if an escape
598      *			is improperly formatted.
599      */
600 
601     static String unescapeAttributeString(String string,
602 					  boolean badTag)
603 	throws ServiceLocationException {
604 
605 	// Process escapes.
606 
607 	int i, n = string.length();
608 	StringBuffer buf = new StringBuffer(n);
609 
610 	for (i = 0; i < n; i++) {
611 	    char c = string.charAt(i);
612 
613 	    // Check for escaped characters.
614 
615 	    if (c == ESCAPE) {
616 
617 		// Get the next two characters.
618 
619 		if (i >= n - 2) {
620 		    throw
621 			new ServiceLocationException(
622 				ServiceLocationException.PARSE_ERROR,
623 				"nonterminating_escape",
624 				new Object[] {string});
625 		}
626 
627 		i++;
628 		c = unescapeChar(string.substring(i, i+2));
629 		i++;
630 
631 		// Check whether it's reserved.
632 
633 		if (!canEscape(c)) {
634 		    throw
635 			new ServiceLocationException(
636 				ServiceLocationException.PARSE_ERROR,
637 				"char_not_reserved_attr",
638 				new Object[] {new Character(c), string});
639 		}
640 
641 	    } else {
642 
643 		// Check whether the character is reserved.
644 
645 		if (isReserved(c)) {
646 		    throw
647 			new ServiceLocationException(
648 				ServiceLocationException.PARSE_ERROR,
649 				"reserved_not_escaped",
650 				new Object[] {new Character(c)});
651 		}
652 
653 	    }
654 
655 	    // If we need to check for a bad tag character, do so now.
656 
657 	    if (badTag && BAD_TAG_CHARS.indexOf(c) != -1) {
658 		throw
659 		    new ServiceLocationException(
660 				ServiceLocationException.PARSE_ERROR,
661 				"bad_id_char",
662 				new Object[] {Integer.toHexString(c)});
663 
664 	    }
665 
666 	    buf.append(c);
667 
668 	}
669 
670 	return buf.toString();
671     }
672 
673     // Return true if the character c can be escaped.
674 
675     private static boolean canEscape(char c) {
676 
677 	return ((ESCAPED.indexOf(c) != -1) ||
678 		((c >= CTL_LOWER && c <= CTL_UPPER) || c == DEL));
679 
680     }
681 
682     // Return true if the character c is reserved.
683 
684     private static boolean isReserved(char c) {
685 
686 	return ((RESERVED.indexOf(c) != -1) ||
687 		((c >= CTL_LOWER && c <= CTL_UPPER) || c == DEL));
688 
689     }
690 
691     /**
692      * Return a string of integers giving the character's encoding in
693      * the character set passed in as encoding.
694      *
695      * @param c The character to escape.
696      * @return The character as a string of integers for the encoding.
697      */
698 
699     static String escapeChar(char c) {
700 
701 	byte[] b = null;
702 
703 	try {
704 
705 	    b = ("" + c).getBytes(Defaults.UTF8);
706 
707 	} catch (UnsupportedEncodingException ex) {
708 
709 	    Assert.slpassert(false, "no_utf8", new Object[0]);
710 
711 	}
712 
713 	int code = 0;
714 
715 	// Assemble the character code.
716 
717 	if (b.length > 3) {
718 	    Assert.slpassert(false,
719 			  "illegal_utf8",
720 			  new Object[] {new Character(c)});
721 
722 	}
723 
724 	code = (int)(b[0] & 0xFF);
725 
726 	if (b.length > 1) {
727 	    code = (int)(code | ((b[1] & 0xFF) << 8));
728 	}
729 
730 	if (b.length > 2) {
731 	    code = (int)(code | ((b[2] & 0xFF) << 16));
732 	}
733 
734 	String str = Integer.toHexString(code);
735 
736 	return str;
737     }
738 
739     /**
740      * Unescape the character encoded as the string.
741      *
742      * @param ch The character as a string of hex digits.
743      * @return The character.
744      * @exception ServiceLocationException If the characters can't be
745      *		 converted into a hex string.
746      */
747 
748     static char unescapeChar(String ch)
749 	throws ServiceLocationException {
750 
751 	int code = 0;
752 
753 	try {
754 	    code = Integer.parseInt(ch, 16);
755 
756 	} catch (NumberFormatException ex) {
757 
758 	    throw
759 		new ServiceLocationException(
760 				ServiceLocationException.PARSE_ERROR,
761 				"not_a_character",
762 				new Object[] {ch});
763 	}
764 
765 	// Convert to bytes.
766 
767 	String str = null;
768 	byte b0 = 0, b1 = 0, b2 = 0, b3 = 0;
769 	byte b[] = null;
770 
771 	b0 = (byte) (code & 0xFF);
772 	b1 = (byte) ((code >> 8) & 0xFF);
773 	b2 = (byte) ((code >> 16) & 0xFF);
774 	b3 = (byte) ((code >> 24) & 0xFF);
775 
776 	// We allow illegal UTF8 encoding so we can decode byte arrays.
777 
778 	if (b3 != 0) {
779 	    b = new byte[3];
780 	    b[3] = b3;
781 	    b[2] = b2;
782 	    b[1] = b1;
783 	    b[0] = b0;
784 	} else if (b2 != 0) {
785 	    b = new byte[3];
786 	    b[2] = b2;
787 	    b[1] = b1;
788 	    b[0] = b0;
789 	} else if (b1 != 0) {
790 	    b = new byte[2];
791 	    b[1] = b1;
792 	    b[0] = b0;
793 	} else {
794 	    b = new byte[1];
795 	    b[0] = b0;
796 	}
797 
798 	// Make a string out of it.
799 
800 	try {
801 	    str = new String(b, Defaults.UTF8);
802 
803 	} catch (UnsupportedEncodingException ex) {
804 
805 	    Assert.slpassert(false, "no_utf8", new Object[0]);
806 
807 	}
808 
809 	int len = str.length();
810 
811 	if (str.length() > 1) {
812 	    throw
813 		new ServiceLocationException(
814 				ServiceLocationException.PARSE_ERROR,
815 				"more_than_one",
816 				new Object[] {ch});
817 
818 	}
819 
820 	return (len == 1 ? str.charAt(0):(char)0);
821     }
822 
823     /**
824      * Merge the values in newAttr into the attribute in the hashtable
825      * if a duplicate attribute, signal error if a type mismatch.
826      * Both the return vector and hashtable are updated, but the
827      * newAttr parameter is left unchanged.
828      *
829      * @param attr The ServiceLocationAttribute to check.
830      * @param attrHash A Hashtable containing the attribute tags as
831      *			keys and the attributes as values.
832      * @param returns A Vector in which to put the attribute when done.
833      * @param dontTypeCheck If this flag is true, the value vector
834      *			   may have two booleans, may
835      *			   contain differently typed objects, or the
836      *			   function may merge a keyword and nonkeyword
837      *			   attribute.
838      * @exception ServiceLocationException Thrown if a type mismatch
839      *			occurs.
840      */
841 
842     static void
843 	mergeDuplicateAttributes(ServiceLocationAttribute newAttr,
844 				 Hashtable attrTable,
845 				 Vector returns,
846 				 boolean dontTypeCheck)
847 	throws ServiceLocationException {
848 
849 	// Look up the attribute
850 
851 	String tag = newAttr.getId().toLowerCase();
852 	ServiceLocationAttribute attr =
853 	    (ServiceLocationAttribute)attrTable.get(tag);
854 
855 	// Don't try this trick with ServerAttributes!
856 
857 	Assert.slpassert((!(attr instanceof ServerAttribute) &&
858 		       !(newAttr instanceof ServerAttribute)),
859 		      "merge_servattr",
860 		      new Object[0]);
861 
862 	// If the attribute isn't in the hashtable, then add to
863 	//  vector and hashtable.
864 
865 	if (attr == null) {
866 	    attrTable.put(tag, newAttr);
867 	    returns.addElement(newAttr);
868 	    return;
869 
870 	}
871 
872 
873 	Vector attrNewVals = newAttr.values;
874 	Vector attrVals = attr.values;
875 
876 	// If both keywords, nothing further to do.
877 
878 	if (attrVals == null && attrNewVals == null) {
879 	    return;
880 
881 	}
882 
883 	// If we are not typechecking and one is keyword while the other
884 	//  is not, then simply merge in the nonkeyword. Otherwise,
885 	//  throw a type check exception.
886 
887 	if ((attrVals == null && attrNewVals != null) ||
888 	    (attrNewVals == null && attrVals != null)) {
889 
890 	    if (dontTypeCheck) {
891 		Vector vals = (attrNewVals != null ? attrNewVals:attrVals);
892 		attr.values = vals;
893 		newAttr.values = vals;
894 
895 	    } else {
896 		throw
897 		    new ServiceLocationException(
898 				ServiceLocationException.PARSE_ERROR,
899 				"attribute_type_mismatch",
900 				new Object[] {newAttr.getId()});
901 
902 	    }
903 
904 	} else {
905 
906 	    // Merge the two vectors. We type check against the attrVals
907 	    //  vector, if we are type checking.
908 
909 	    int i, n = attrNewVals.size();
910 	    Object o = attrVals.elementAt(0);
911 	    Class c = o.getClass();
912 
913 	    for (i = 0; i < n; i++) {
914 		Object no = attrNewVals.elementAt(i);
915 
916 		// Check for type mismatch, throw exception if
917 		//  we are type checking.
918 
919 		if ((c != no.getClass()) && !dontTypeCheck) {
920 		    throw
921 			new ServiceLocationException(
922 				ServiceLocationException.PARSE_ERROR,
923 				"attribute_type_mismatch",
924 				new Object[] {newAttr.getId()});
925 
926 		}
927 
928 		// If we are typechecking, and we get two opposite
929 		//  booleans, we need to throw an exception.
930 
931 		if (no instanceof Boolean && !no.equals(o) && !dontTypeCheck) {
932 		    throw
933 			new ServiceLocationException(
934 				ServiceLocationException.PARSE_ERROR,
935 				"boolean_incompat",
936 				new Object[] {newAttr.getId()});
937 
938 		}
939 
940 		// Add the value if it isn't already there.
941 
942 		if (!attrVals.contains(no)) {
943 		    attrVals.addElement(no);
944 
945 		}
946 	    }
947 
948 	    // Set the new attribute's values so they are the same as the old.
949 
950 	    newAttr.values = attrVals;
951 
952 	}
953     }
954 
955     //
956     // Object overrides.
957     //
958 
959     /**
960      * Return true if the object equals this attribute.
961      */
962 
963     public boolean equals(Object o) {
964 
965 	if (!(o instanceof ServiceLocationAttribute)) {
966 	    return false;
967 
968 	}
969 
970 	if (o == this) {
971 	    return true;
972 
973 	}
974 
975 	ServiceLocationAttribute sla = (ServiceLocationAttribute)o;
976 
977 	// check equality of contents, deferring check of all values
978 
979 	Vector vSLA = sla.values;
980 
981 	if (!sla.getId().equalsIgnoreCase(id)) {
982 	    return false;
983 
984 	}
985 
986 	if (values == null && vSLA == null) {
987 	    return true;
988 
989 	}
990 
991 	if ((values == null && vSLA != null) ||
992 	    (values != null && vSLA == null)) {
993 	    return false;
994 
995 	}
996 
997 	if (values.size() != vSLA.size()) {
998 	    return false;
999 
1000 	}
1001 
1002 	// Check contents.
1003 
1004 	Object oSLA = vSLA.elementAt(0);
1005 	o = values.elementAt(0);
1006 
1007 	if (o.getClass() != oSLA.getClass()) {
1008 	    return false;
1009 
1010 	}
1011 
1012 	int i, n = vSLA.size();
1013 
1014 	for (i = 0; i < n; i++) {
1015 	    oSLA = vSLA.elementAt(i);
1016 
1017 	    if (!values.contains(oSLA)) {
1018 		return false;
1019 
1020 	    }
1021 	}
1022 
1023 	return true;
1024     }
1025 
1026     /**
1027      * Return a human readable string for the attribute.
1028      */
1029 
1030     public String toString() {
1031 
1032 	StringBuffer s = new StringBuffer("(");
1033 
1034 	s.append(id);
1035 
1036 	if (values != null) {
1037 	    s.append("=");
1038 
1039 	    int i, n = values.size();
1040 
1041 	    for (i = 0; i < n; i++) {
1042 		Object o = values.elementAt(i);
1043 
1044 		// Identify type.
1045 
1046 		if (i == 0) {
1047 		    s.append(o.getClass().getName());
1048 		    s.append(":");
1049 
1050 		} else {
1051 		    s.append(",");
1052 
1053 		}
1054 
1055 		// Stringify object.
1056 
1057 		s.append(o.toString());
1058 
1059 	    }
1060 	}
1061 
1062 	s.append(")");
1063 
1064 	return s.toString();
1065     }
1066 
1067     // Overrides Object.hashCode().
1068 
1069     public int hashCode() {
1070 	return id.toLowerCase().hashCode();
1071 
1072     }
1073 
1074 }
1075