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