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