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