1 /* 2 * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. 3 */ 4 /* 5 * Licensed to the Apache Software Foundation (ASF) under one or more 6 * contributor license agreements. See the NOTICE file distributed with 7 * this work for additional information regarding copyright ownership. 8 * The ASF licenses this file to You under the Apache License, Version 2.0 9 * (the "License"); you may not use this file except in compliance with 10 * the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 package com.sun.org.apache.xerces.internal.util; 22 23 import com.sun.xml.internal.stream.XMLBufferListener; 24 import com.sun.org.apache.xerces.internal.xni.Augmentations; 25 import com.sun.org.apache.xerces.internal.xni.QName; 26 import com.sun.org.apache.xerces.internal.xni.XMLAttributes; 27 import com.sun.org.apache.xerces.internal.xni.XMLString; 28 /** 29 * The XMLAttributesImpl class is an implementation of the XMLAttributes 30 * interface which defines a collection of attributes for an element. 31 * In the parser, the document source would scan the entire start element 32 * and collect the attributes. The attributes are communicated to the 33 * document handler in the startElement method. 34 * <p> 35 * The attributes are read-write so that subsequent stages in the document 36 * pipeline can modify the values or change the attributes that are 37 * propogated to the next stage. 38 * 39 * @see com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler#startElement 40 * 41 * @author Andy Clark, IBM 42 * @author Elena Litani, IBM 43 * @author Michael Glavassevich, IBM 44 * 45 */ 46 public class XMLAttributesImpl 47 implements XMLAttributes, XMLBufferListener { 48 49 // 50 // Constants 51 // 52 53 /** Default table size. */ 54 protected static final int TABLE_SIZE = 101; 55 56 /** Maximum hash collisions per bucket. */ 57 protected static final int MAX_HASH_COLLISIONS = 40; 58 59 protected static final int MULTIPLIERS_SIZE = 1 << 5; 60 protected static final int MULTIPLIERS_MASK = MULTIPLIERS_SIZE - 1; 61 62 /** 63 * Threshold at which an instance is treated 64 * as a large attribute list. 65 */ 66 protected static final int SIZE_LIMIT = 20; 67 68 // 69 // Data 70 // 71 72 // features 73 74 /** Namespaces. */ 75 protected boolean fNamespaces = true; 76 77 // data 78 79 /** 80 * Usage count for the attribute table view. 81 * Incremented each time all attributes are removed 82 * when the attribute table view is in use. 83 */ 84 protected int fLargeCount = 1; 85 86 /** Attribute count. */ 87 protected int fLength; 88 89 /** Attribute information. */ 90 protected Attribute[] fAttributes = new Attribute[4]; 91 92 /** 93 * Provides an alternate view of the attribute specification. 94 */ 95 protected Attribute[] fAttributeTableView; 96 97 /** 98 * Tracks whether each chain in the hash table is stale 99 * with respect to the current state of this object. 100 * A chain is stale if its state is not the same as the number 101 * of times the attribute table view has been used. 102 */ 103 protected int[] fAttributeTableViewChainState; 104 105 /** 106 * Actual number of buckets in the table view. 107 */ 108 protected int fTableViewBuckets; 109 110 /** 111 * Indicates whether the table view contains consistent data. 112 */ 113 protected boolean fIsTableViewConsistent; 114 115 /** 116 * Array of randomly selected hash function multipliers or <code>null</code> 117 * if the default String.hashCode() function should be used. 118 */ 119 protected int[] fHashMultipliers; 120 121 // 122 // Constructors 123 // 124 125 /** Default constructor. */ XMLAttributesImpl()126 public XMLAttributesImpl() { 127 this(TABLE_SIZE); 128 } 129 130 /** 131 * @param tableSize initial size of table view 132 */ XMLAttributesImpl(int tableSize)133 public XMLAttributesImpl(int tableSize) { 134 fTableViewBuckets = tableSize; 135 for (int i = 0; i < fAttributes.length; i++) { 136 fAttributes[i] = new Attribute(); 137 } 138 } // <init>() 139 140 // 141 // Public methods 142 // 143 144 /** 145 * Sets whether namespace processing is being performed. This state 146 * is needed to return the correct value from the getLocalName method. 147 * 148 * @param namespaces True if namespace processing is turned on. 149 * 150 * @see #getLocalName 151 */ setNamespaces(boolean namespaces)152 public void setNamespaces(boolean namespaces) { 153 fNamespaces = namespaces; 154 } // setNamespaces(boolean) 155 156 // 157 // XMLAttributes methods 158 // 159 160 /** 161 * Adds an attribute. The attribute's non-normalized value of the 162 * attribute will have the same value as the attribute value until 163 * set using the <code>setNonNormalizedValue</code> method. Also, 164 * the added attribute will be marked as specified in the XML instance 165 * document unless set otherwise using the <code>setSpecified</code> 166 * method. 167 * <p> 168 * <strong>Note:</strong> If an attribute of the same name already 169 * exists, the old values for the attribute are replaced by the new 170 * values. 171 * 172 * @param name The attribute name. 173 * @param type The attribute type. The type name is determined by 174 * the type specified for this attribute in the DTD. 175 * For example: "CDATA", "ID", "NMTOKEN", etc. However, 176 * attributes of type enumeration will have the type 177 * value specified as the pipe ('|') separated list of 178 * the enumeration values prefixed by an open 179 * parenthesis and suffixed by a close parenthesis. 180 * For example: "(true|false)". 181 * @param value The attribute value. 182 * 183 * @return Returns the attribute index. 184 * 185 * @see #setNonNormalizedValue 186 * @see #setSpecified 187 */ addAttribute(QName name, String type, String value)188 public int addAttribute(QName name, String type, String value) { 189 return addAttribute(name,type,value,null); 190 } addAttribute(QName name, String type, String value,XMLString valueCache)191 public int addAttribute(QName name, String type, String value,XMLString valueCache) { 192 193 int index; 194 if (fLength < SIZE_LIMIT) { 195 index = name.uri != null && name.uri.length() != 0 196 ? getIndexFast(name.uri, name.localpart) 197 : getIndexFast(name.rawname); 198 199 if (index == -1) { 200 index = fLength; 201 if (fLength++ == fAttributes.length) { 202 Attribute[] attributes = new Attribute[fAttributes.length + 4]; 203 System.arraycopy(fAttributes, 0, attributes, 0, fAttributes.length); 204 for (int i = fAttributes.length; i < attributes.length; i++) { 205 attributes[i] = new Attribute(); 206 } 207 fAttributes = attributes; 208 } 209 } 210 } 211 else if (name.uri == null || 212 name.uri.length() == 0 || 213 (index = getIndexFast(name.uri, name.localpart)) == -1) { 214 215 /** 216 * If attributes were removed from the list after the table 217 * becomes in use this isn't reflected in the table view. It's 218 * assumed that once a user starts removing attributes they're 219 * not likely to add more. We only make the view consistent if 220 * the user of this class adds attributes, removes them, and 221 * then adds more. 222 */ 223 if (!fIsTableViewConsistent || fLength == SIZE_LIMIT || 224 (fLength > SIZE_LIMIT && fLength > fTableViewBuckets)) { 225 prepareAndPopulateTableView(); 226 fIsTableViewConsistent = true; 227 } 228 229 int bucket = getTableViewBucket(name.rawname); 230 231 // The chain is stale. 232 // This must be a unique attribute. 233 if (fAttributeTableViewChainState[bucket] != fLargeCount) { 234 index = fLength; 235 if (fLength++ == fAttributes.length) { 236 Attribute[] attributes = new Attribute[fAttributes.length << 1]; 237 System.arraycopy(fAttributes, 0, attributes, 0, fAttributes.length); 238 for (int i = fAttributes.length; i < attributes.length; i++) { 239 attributes[i] = new Attribute(); 240 } 241 fAttributes = attributes; 242 } 243 244 // Update table view. 245 fAttributeTableViewChainState[bucket] = fLargeCount; 246 fAttributes[index].next = null; 247 fAttributeTableView[bucket] = fAttributes[index]; 248 } 249 // This chain is active. 250 // We need to check if any of the attributes has the same rawname. 251 else { 252 // Search the table. 253 int collisionCount = 0; 254 Attribute found = fAttributeTableView[bucket]; 255 while (found != null) { 256 if (found.name.rawname == name.rawname) { 257 break; 258 } 259 found = found.next; 260 ++collisionCount; 261 } 262 // This attribute is unique. 263 if (found == null) { 264 index = fLength; 265 if (fLength++ == fAttributes.length) { 266 Attribute[] attributes = new Attribute[fAttributes.length << 1]; 267 System.arraycopy(fAttributes, 0, attributes, 0, fAttributes.length); 268 for (int i = fAttributes.length; i < attributes.length; i++) { 269 attributes[i] = new Attribute(); 270 } 271 fAttributes = attributes; 272 } 273 274 // Select a new hash function and rehash the table view 275 // if the collision threshold is exceeded. 276 if (collisionCount >= MAX_HASH_COLLISIONS) { 277 // The current attribute will be processed in the rehash. 278 // Need to set its name first. 279 fAttributes[index].name.setValues(name); 280 rebalanceTableView(fLength); 281 } 282 else { 283 // Update table view 284 fAttributes[index].next = fAttributeTableView[bucket]; 285 fAttributeTableView[bucket] = fAttributes[index]; 286 } 287 } 288 // Duplicate. We still need to find the index. 289 else { 290 index = getIndexFast(name.rawname); 291 } 292 } 293 } 294 295 // set values 296 Attribute attribute = fAttributes[index]; 297 attribute.name.setValues(name); 298 attribute.type = type; 299 attribute.value = value; 300 attribute.xmlValue = valueCache; 301 attribute.nonNormalizedValue = value; 302 attribute.specified = false; 303 304 // clear augmentations 305 if(attribute.augs != null) 306 attribute.augs.removeAllItems(); 307 308 return index; 309 310 } // addAttribute(QName,String,XMLString) 311 312 /** 313 * Removes all of the attributes. This method will also remove all 314 * entities associated to the attributes. 315 */ removeAllAttributes()316 public void removeAllAttributes() { 317 fLength = 0; 318 } // removeAllAttributes() 319 320 /** 321 * Removes the attribute at the specified index. 322 * <p> 323 * <strong>Note:</strong> This operation changes the indexes of all 324 * attributes following the attribute at the specified index. 325 * 326 * @param attrIndex The attribute index. 327 */ removeAttributeAt(int attrIndex)328 public void removeAttributeAt(int attrIndex) { 329 fIsTableViewConsistent = false; 330 if (attrIndex < fLength - 1) { 331 Attribute removedAttr = fAttributes[attrIndex]; 332 System.arraycopy(fAttributes, attrIndex + 1, 333 fAttributes, attrIndex, fLength - attrIndex - 1); 334 // Make the discarded Attribute object available for re-use 335 // by tucking it after the Attributes that are still in use 336 fAttributes[fLength-1] = removedAttr; 337 } 338 fLength--; 339 } // removeAttributeAt(int) 340 341 /** 342 * Sets the name of the attribute at the specified index. 343 * 344 * @param attrIndex The attribute index. 345 * @param attrName The new attribute name. 346 */ setName(int attrIndex, QName attrName)347 public void setName(int attrIndex, QName attrName) { 348 fAttributes[attrIndex].name.setValues(attrName); 349 } // setName(int,QName) 350 351 /** 352 * Sets the fields in the given QName structure with the values 353 * of the attribute name at the specified index. 354 * 355 * @param attrIndex The attribute index. 356 * @param attrName The attribute name structure to fill in. 357 */ getName(int attrIndex, QName attrName)358 public void getName(int attrIndex, QName attrName) { 359 attrName.setValues(fAttributes[attrIndex].name); 360 } // getName(int,QName) 361 362 /** 363 * Sets the type of the attribute at the specified index. 364 * 365 * @param attrIndex The attribute index. 366 * @param attrType The attribute type. The type name is determined by 367 * the type specified for this attribute in the DTD. 368 * For example: "CDATA", "ID", "NMTOKEN", etc. However, 369 * attributes of type enumeration will have the type 370 * value specified as the pipe ('|') separated list of 371 * the enumeration values prefixed by an open 372 * parenthesis and suffixed by a close parenthesis. 373 * For example: "(true|false)". 374 */ setType(int attrIndex, String attrType)375 public void setType(int attrIndex, String attrType) { 376 fAttributes[attrIndex].type = attrType; 377 } // setType(int,String) 378 379 /** 380 * Sets the value of the attribute at the specified index. This 381 * method will overwrite the non-normalized value of the attribute. 382 * 383 * @param attrIndex The attribute index. 384 * @param attrValue The new attribute value. 385 * 386 * @see #setNonNormalizedValue 387 */ setValue(int attrIndex, String attrValue)388 public void setValue(int attrIndex, String attrValue) { 389 setValue(attrIndex,attrValue,null); 390 } 391 setValue(int attrIndex, String attrValue,XMLString value)392 public void setValue(int attrIndex, String attrValue,XMLString value) { 393 Attribute attribute = fAttributes[attrIndex]; 394 attribute.value = attrValue; 395 attribute.nonNormalizedValue = attrValue; 396 attribute.xmlValue = value; 397 } // setValue(int,String) 398 399 /** 400 * Sets the non-normalized value of the attribute at the specified 401 * index. 402 * 403 * @param attrIndex The attribute index. 404 * @param attrValue The new non-normalized attribute value. 405 */ setNonNormalizedValue(int attrIndex, String attrValue)406 public void setNonNormalizedValue(int attrIndex, String attrValue) { 407 if (attrValue == null) { 408 attrValue = fAttributes[attrIndex].value; 409 } 410 fAttributes[attrIndex].nonNormalizedValue = attrValue; 411 } // setNonNormalizedValue(int,String) 412 413 /** 414 * Returns the non-normalized value of the attribute at the specified 415 * index. If no non-normalized value is set, this method will return 416 * the same value as the <code>getValue(int)</code> method. 417 * 418 * @param attrIndex The attribute index. 419 */ getNonNormalizedValue(int attrIndex)420 public String getNonNormalizedValue(int attrIndex) { 421 String value = fAttributes[attrIndex].nonNormalizedValue; 422 return value; 423 } // getNonNormalizedValue(int):String 424 425 /** 426 * Sets whether an attribute is specified in the instance document 427 * or not. 428 * 429 * @param attrIndex The attribute index. 430 * @param specified True if the attribute is specified in the instance 431 * document. 432 */ setSpecified(int attrIndex, boolean specified)433 public void setSpecified(int attrIndex, boolean specified) { 434 fAttributes[attrIndex].specified = specified; 435 } // setSpecified(int,boolean) 436 437 /** 438 * Returns true if the attribute is specified in the instance document. 439 * 440 * @param attrIndex The attribute index. 441 */ isSpecified(int attrIndex)442 public boolean isSpecified(int attrIndex) { 443 return fAttributes[attrIndex].specified; 444 } // isSpecified(int):boolean 445 446 // 447 // AttributeList and Attributes methods 448 // 449 450 /** 451 * Return the number of attributes in the list. 452 * 453 * <p>Once you know the number of attributes, you can iterate 454 * through the list.</p> 455 * 456 * @return The number of attributes in the list. 457 */ getLength()458 public int getLength() { 459 return fLength; 460 } // getLength():int 461 462 /** 463 * Look up an attribute's type by index. 464 * 465 * <p>The attribute type is one of the strings "CDATA", "ID", 466 * "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", 467 * or "NOTATION" (always in upper case).</p> 468 * 469 * <p>If the parser has not read a declaration for the attribute, 470 * or if the parser does not report attribute types, then it must 471 * return the value "CDATA" as stated in the XML 1.0 Recommentation 472 * (clause 3.3.3, "Attribute-Value Normalization").</p> 473 * 474 * <p>For an enumerated attribute that is not a notation, the 475 * parser will report the type as "NMTOKEN".</p> 476 * 477 * @param index The attribute index (zero-based). 478 * @return The attribute's type as a string, or null if the 479 * index is out of range. 480 * @see #getLength 481 */ getType(int index)482 public String getType(int index) { 483 if (index < 0 || index >= fLength) { 484 return null; 485 } 486 return getReportableType(fAttributes[index].type); 487 } // getType(int):String 488 489 /** 490 * Look up an attribute's type by XML 1.0 qualified name. 491 * 492 * <p>See {@link #getType(int) getType(int)} for a description 493 * of the possible types.</p> 494 * 495 * @param qname The XML 1.0 qualified name. 496 * @return The attribute type as a string, or null if the 497 * attribute is not in the list or if qualified names 498 * are not available. 499 */ getType(String qname)500 public String getType(String qname) { 501 int index = getIndex(qname); 502 return index != -1 ? getReportableType(fAttributes[index].type) : null; 503 } // getType(String):String 504 505 /** 506 * Look up an attribute's value by index. 507 * 508 * <p>If the attribute value is a list of tokens (IDREFS, 509 * ENTITIES, or NMTOKENS), the tokens will be concatenated 510 * into a single string with each token separated by a 511 * single space.</p> 512 * 513 * @param index The attribute index (zero-based). 514 * @return The attribute's value as a string, or null if the 515 * index is out of range. 516 * @see #getLength 517 */ getValue(int index)518 public String getValue(int index) { 519 if (index < 0 || index >= fLength) { 520 return null; 521 } 522 if(fAttributes[index].value == null && fAttributes[index].xmlValue != null) 523 fAttributes[index].value = fAttributes[index].xmlValue.toString(); 524 return fAttributes[index].value; 525 } // getValue(int):String 526 527 /** 528 * Look up an attribute's value by XML 1.0 qualified name. 529 * 530 * <p>See {@link #getValue(int) getValue(int)} for a description 531 * of the possible values.</p> 532 * 533 * @param qname The XML 1.0 qualified name. 534 * @return The attribute value as a string, or null if the 535 * attribute is not in the list or if qualified names 536 * are not available. 537 */ getValue(String qname)538 public String getValue(String qname) { 539 int index = getIndex(qname); 540 if(index == -1 ) 541 return null; 542 if(fAttributes[index].value == null) 543 fAttributes[index].value = fAttributes[index].xmlValue.toString(); 544 return fAttributes[index].value; 545 } // getValue(String):String 546 547 // 548 // AttributeList methods 549 // 550 551 /** 552 * Return the name of an attribute in this list (by position). 553 * 554 * <p>The names must be unique: the SAX parser shall not include the 555 * same attribute twice. Attributes without values (those declared 556 * #IMPLIED without a value specified in the start tag) will be 557 * omitted from the list.</p> 558 * 559 * <p>If the attribute name has a namespace prefix, the prefix 560 * will still be attached.</p> 561 * 562 * @param i The index of the attribute in the list (starting at 0). 563 * @return The name of the indexed attribute, or null 564 * if the index is out of range. 565 * @see #getLength 566 */ getName(int index)567 public String getName(int index) { 568 if (index < 0 || index >= fLength) { 569 return null; 570 } 571 return fAttributes[index].name.rawname; 572 } // getName(int):String 573 574 // 575 // Attributes methods 576 // 577 578 /** 579 * Look up the index of an attribute by XML 1.0 qualified name. 580 * 581 * @param qName The qualified (prefixed) name. 582 * @return The index of the attribute, or -1 if it does not 583 * appear in the list. 584 */ getIndex(String qName)585 public int getIndex(String qName) { 586 for (int i = 0; i < fLength; i++) { 587 Attribute attribute = fAttributes[i]; 588 if (attribute.name.rawname != null && 589 attribute.name.rawname.equals(qName)) { 590 return i; 591 } 592 } 593 return -1; 594 } // getIndex(String):int 595 596 /** 597 * Look up the index of an attribute by Namespace name. 598 * 599 * @param uri The Namespace URI, or null if 600 * the name has no Namespace URI. 601 * @param localName The attribute's local name. 602 * @return The index of the attribute, or -1 if it does not 603 * appear in the list. 604 */ getIndex(String uri, String localPart)605 public int getIndex(String uri, String localPart) { 606 for (int i = 0; i < fLength; i++) { 607 Attribute attribute = fAttributes[i]; 608 if (attribute.name.localpart != null && 609 attribute.name.localpart.equals(localPart) && 610 ((uri==attribute.name.uri) || 611 (uri!=null && attribute.name.uri!=null && attribute.name.uri.equals(uri)))) { 612 return i; 613 } 614 } 615 return -1; 616 } // getIndex(String,String):int 617 618 /** 619 * Look up the index of an attribute by local name only, 620 * ignoring its namespace. 621 * 622 * @param localName The attribute's local name. 623 * @return The index of the attribute, or -1 if it does not 624 * appear in the list. 625 */ getIndexByLocalName(String localPart)626 public int getIndexByLocalName(String localPart) { 627 for (int i = 0; i < fLength; i++) { 628 Attribute attribute = fAttributes[i]; 629 if (attribute.name.localpart != null && 630 attribute.name.localpart.equals(localPart)) { 631 return i; 632 } 633 } 634 return -1; 635 } // getIndex(String):int 636 637 /** 638 * Look up an attribute's local name by index. 639 * 640 * @param index The attribute index (zero-based). 641 * @return The local name, or the empty string if Namespace 642 * processing is not being performed, or null 643 * if the index is out of range. 644 * @see #getLength 645 */ getLocalName(int index)646 public String getLocalName(int index) { 647 if (!fNamespaces) { 648 return ""; 649 } 650 if (index < 0 || index >= fLength) { 651 return null; 652 } 653 return fAttributes[index].name.localpart; 654 } // getLocalName(int):String 655 656 /** 657 * Look up an attribute's XML 1.0 qualified name by index. 658 * 659 * @param index The attribute index (zero-based). 660 * @return The XML 1.0 qualified name, or the empty string 661 * if none is available, or null if the index 662 * is out of range. 663 * @see #getLength 664 */ getQName(int index)665 public String getQName(int index) { 666 if (index < 0 || index >= fLength) { 667 return null; 668 } 669 String rawname = fAttributes[index].name.rawname; 670 return rawname != null ? rawname : ""; 671 } // getQName(int):String 672 getQualifiedName(int index)673 public QName getQualifiedName(int index){ 674 if (index < 0 || index >= fLength) { 675 return null; 676 } 677 return fAttributes[index].name; 678 } 679 680 /** 681 * Look up an attribute's type by Namespace name. 682 * 683 * <p>See {@link #getType(int) getType(int)} for a description 684 * of the possible types.</p> 685 * 686 * @param uri The Namespace URI, or null if the 687 * name has no Namespace URI. 688 * @param localName The local name of the attribute. 689 * @return The attribute type as a string, or null if the 690 * attribute is not in the list or if Namespace 691 * processing is not being performed. 692 */ getType(String uri, String localName)693 public String getType(String uri, String localName) { 694 if (!fNamespaces) { 695 return null; 696 } 697 int index = getIndex(uri, localName); 698 return index != -1 ? getType(index) : null; 699 } // getType(String,String):String 700 /** 701 * Look up the index of an attribute by XML 1.0 qualified name. 702 * <p> 703 * <strong>Note:</strong> 704 * This method uses reference comparison, and thus should 705 * only be used internally. We cannot use this method in any 706 * code exposed to users as they may not pass in unique strings. 707 * 708 * @param qName The qualified (prefixed) name. 709 * @return The index of the attribute, or -1 if it does not 710 * appear in the list. 711 */ getIndexFast(String qName)712 public int getIndexFast(String qName) { 713 for (int i = 0; i < fLength; ++i) { 714 Attribute attribute = fAttributes[i]; 715 if (attribute.name.rawname == qName) { 716 return i; 717 } 718 } 719 return -1; 720 } // getIndexFast(String):int 721 722 /** 723 * Adds an attribute. The attribute's non-normalized value of the 724 * attribute will have the same value as the attribute value until 725 * set using the <code>setNonNormalizedValue</code> method. Also, 726 * the added attribute will be marked as specified in the XML instance 727 * document unless set otherwise using the <code>setSpecified</code> 728 * method. 729 * <p> 730 * This method differs from <code>addAttribute</code> in that it 731 * does not check if an attribute of the same name already exists 732 * in the list before adding it. In order to improve performance 733 * of namespace processing, this method allows uniqueness checks 734 * to be deferred until all the namespace information is available 735 * after the entire attribute specification has been read. 736 * <p> 737 * <strong>Caution:</strong> If this method is called it should 738 * not be mixed with calls to <code>addAttribute</code> unless 739 * it has been determined that all the attribute names are unique. 740 * 741 * @param name the attribute name 742 * @param type the attribute type 743 * @param value the attribute value 744 * 745 * @see #setNonNormalizedValue 746 * @see #setSpecified 747 * @see #checkDuplicatesNS 748 */ addAttributeNS(QName name, String type, String value)749 public void addAttributeNS(QName name, String type, String value) { 750 int index = fLength; 751 if (fLength++ == fAttributes.length) { 752 Attribute[] attributes; 753 if (fLength < SIZE_LIMIT) { 754 attributes = new Attribute[fAttributes.length + 4]; 755 } 756 else { 757 attributes = new Attribute[fAttributes.length << 1]; 758 } 759 System.arraycopy(fAttributes, 0, attributes, 0, fAttributes.length); 760 for (int i = fAttributes.length; i < attributes.length; i++) { 761 attributes[i] = new Attribute(); 762 } 763 fAttributes = attributes; 764 } 765 766 // set values 767 Attribute attribute = fAttributes[index]; 768 attribute.name.setValues(name); 769 attribute.type = type; 770 attribute.value = value; 771 attribute.nonNormalizedValue = value; 772 attribute.specified = false; 773 774 // clear augmentations 775 attribute.augs.removeAllItems(); 776 } 777 778 /** 779 * Checks for duplicate expanded names (local part and namespace name 780 * pairs) in the attribute specification. If a duplicate is found its 781 * name is returned. 782 * <p> 783 * This should be called once all the in-scope namespaces for the element 784 * enclosing these attributes is known, and after all the attributes 785 * have gone through namespace binding. 786 * 787 * @return the name of a duplicate attribute found in the search, 788 * otherwise null. 789 */ checkDuplicatesNS()790 public QName checkDuplicatesNS() { 791 // If the list is small check for duplicates using pairwise comparison. 792 final int length = fLength; 793 if (length <= SIZE_LIMIT) { 794 final Attribute[] attributes = fAttributes; 795 for (int i = 0; i < length - 1; ++i) { 796 Attribute att1 = attributes[i]; 797 for (int j = i + 1; j < length; ++j) { 798 Attribute att2 = attributes[j]; 799 if (att1.name.localpart == att2.name.localpart && 800 att1.name.uri == att2.name.uri) { 801 return att2.name; 802 } 803 } 804 } 805 return null; 806 } 807 // If the list is large check duplicates using a hash table. 808 else { 809 return checkManyDuplicatesNS(); 810 } 811 } 812 checkManyDuplicatesNS()813 private QName checkManyDuplicatesNS() { 814 // We don't want this table view to be read if someone calls 815 // addAttribute so we invalidate it up front. 816 fIsTableViewConsistent = false; 817 818 prepareTableView(); 819 820 Attribute attr; 821 int bucket; 822 823 final int length = fLength; 824 final Attribute[] attributes = fAttributes; 825 final Attribute[] attributeTableView = fAttributeTableView; 826 final int[] attributeTableViewChainState = fAttributeTableViewChainState; 827 int largeCount = fLargeCount; 828 829 for (int i = 0; i < length; ++i) { 830 attr = attributes[i]; 831 bucket = getTableViewBucket(attr.name.localpart, attr.name.uri); 832 833 // The chain is stale. 834 // This must be a unique attribute. 835 if (attributeTableViewChainState[bucket] != largeCount) { 836 attributeTableViewChainState[bucket] = largeCount; 837 attr.next = null; 838 attributeTableView[bucket] = attr; 839 } 840 // This chain is active. 841 // We need to check if any of the attributes has the same name. 842 else { 843 // Search the table. 844 int collisionCount = 0; 845 Attribute found = attributeTableView[bucket]; 846 while (found != null) { 847 if (found.name.localpart == attr.name.localpart && 848 found.name.uri == attr.name.uri) { 849 return attr.name; 850 } 851 found = found.next; 852 ++collisionCount; 853 } 854 // Select a new hash function and rehash the table view 855 // if the collision threshold is exceeded. 856 if (collisionCount >= MAX_HASH_COLLISIONS) { 857 // The current attribute will be processed in the rehash. 858 rebalanceTableViewNS(i+1); 859 largeCount = fLargeCount; 860 } 861 else { 862 // Update table view 863 attr.next = attributeTableView[bucket]; 864 attributeTableView[bucket] = attr; 865 } 866 } 867 } 868 return null; 869 } 870 871 /** 872 * Look up the index of an attribute by Namespace name. 873 * <p> 874 * <strong>Note:</strong> 875 * This method uses reference comparison, and thus should 876 * only be used internally. We cannot use this method in any 877 * code exposed to users as they may not pass in unique strings. 878 * 879 * @param uri The Namespace URI, or null if 880 * the name has no Namespace URI. 881 * @param localName The attribute's local name. 882 * @return The index of the attribute, or -1 if it does not 883 * appear in the list. 884 */ getIndexFast(String uri, String localPart)885 public int getIndexFast(String uri, String localPart) { 886 for (int i = 0; i < fLength; ++i) { 887 Attribute attribute = fAttributes[i]; 888 if (attribute.name.localpart == localPart && 889 attribute.name.uri == uri) { 890 return i; 891 } 892 } 893 return -1; 894 } // getIndexFast(String,String):int 895 896 /** 897 * Returns the value passed in or NMTOKEN if it's an enumerated type. 898 * 899 * @param type attribute type 900 * @return the value passed in or NMTOKEN if it's an enumerated type. 901 */ getReportableType(String type)902 private String getReportableType(String type) { 903 904 if (type.charAt(0) == '(') { 905 return "NMTOKEN"; 906 } 907 return type; 908 } 909 910 /** 911 * Returns the position in the table view 912 * where the given attribute name would be hashed. 913 * 914 * @param qname the attribute name 915 * @return the position in the table view where the given attribute 916 * would be hashed 917 */ getTableViewBucket(String qname)918 protected int getTableViewBucket(String qname) { 919 return (hash(qname) & 0x7FFFFFFF) % fTableViewBuckets; 920 } 921 922 /** 923 * Returns the position in the table view 924 * where the given attribute name would be hashed. 925 * 926 * @param localpart the local part of the attribute 927 * @param uri the namespace name of the attribute 928 * @return the position in the table view where the given attribute 929 * would be hashed 930 */ getTableViewBucket(String localpart, String uri)931 protected int getTableViewBucket(String localpart, String uri) { 932 if (uri == null) { 933 return (hash(localpart) & 0x7FFFFFFF) % fTableViewBuckets; 934 } 935 else { 936 return (hash(localpart, uri) & 0x7FFFFFFF) % fTableViewBuckets; 937 } 938 } 939 hash(String localpart)940 private int hash(String localpart) { 941 if (fHashMultipliers == null) { 942 return localpart.hashCode(); 943 } 944 return hash0(localpart); 945 } // hash(String):int 946 hash(String localpart, String uri)947 private int hash(String localpart, String uri) { 948 if (fHashMultipliers == null) { 949 return localpart.hashCode() + uri.hashCode() * 31; 950 } 951 return hash0(localpart) + hash0(uri) * fHashMultipliers[MULTIPLIERS_SIZE]; 952 } // hash(String,String):int 953 hash0(String symbol)954 private int hash0(String symbol) { 955 int code = 0; 956 final int length = symbol.length(); 957 final int[] multipliers = fHashMultipliers; 958 for (int i = 0; i < length; ++i) { 959 code = code * multipliers[i & MULTIPLIERS_MASK] + symbol.charAt(i); 960 } 961 return code; 962 } // hash0(String):int 963 964 /** 965 * Purges all elements from the table view. 966 */ cleanTableView()967 protected void cleanTableView() { 968 if (++fLargeCount < 0) { 969 // Overflow. We actually need to visit the chain state array. 970 if (fAttributeTableViewChainState != null) { 971 for (int i = fTableViewBuckets - 1; i >= 0; --i) { 972 fAttributeTableViewChainState[i] = 0; 973 } 974 } 975 fLargeCount = 1; 976 } 977 } 978 979 /** 980 * Increases the capacity of the table view. 981 */ growTableView()982 private void growTableView() { 983 final int length = fLength; 984 int tableViewBuckets = fTableViewBuckets; 985 do { 986 tableViewBuckets = (tableViewBuckets << 1) + 1; 987 if (tableViewBuckets < 0) { 988 tableViewBuckets = Integer.MAX_VALUE; 989 break; 990 } 991 } 992 while (length > tableViewBuckets); 993 fTableViewBuckets = tableViewBuckets; 994 fAttributeTableView = null; 995 fLargeCount = 1; 996 } 997 998 /** 999 * Prepares the table view of the attributes list for use. 1000 */ prepareTableView()1001 protected void prepareTableView() { 1002 if (fLength > fTableViewBuckets) { 1003 growTableView(); 1004 } 1005 if (fAttributeTableView == null) { 1006 fAttributeTableView = new Attribute[fTableViewBuckets]; 1007 fAttributeTableViewChainState = new int[fTableViewBuckets]; 1008 } 1009 else { 1010 cleanTableView(); 1011 } 1012 } 1013 1014 /** 1015 * Prepares the table view of the attributes list for use, 1016 * and populates it with the attributes which have been 1017 * previously read. 1018 */ prepareAndPopulateTableView()1019 protected void prepareAndPopulateTableView() { 1020 prepareAndPopulateTableView(fLength); 1021 } 1022 prepareAndPopulateTableView(final int count)1023 private void prepareAndPopulateTableView(final int count) { 1024 prepareTableView(); 1025 // Need to populate the hash table with the attributes we've processed so far. 1026 Attribute attr; 1027 int bucket; 1028 for (int i = 0; i < count; ++i) { 1029 attr = fAttributes[i]; 1030 bucket = getTableViewBucket(attr.name.rawname); 1031 if (fAttributeTableViewChainState[bucket] != fLargeCount) { 1032 fAttributeTableViewChainState[bucket] = fLargeCount; 1033 attr.next = null; 1034 fAttributeTableView[bucket] = attr; 1035 } 1036 else { 1037 // Update table view 1038 attr.next = fAttributeTableView[bucket]; 1039 fAttributeTableView[bucket] = attr; 1040 } 1041 } 1042 } 1043 1044 1045 /** 1046 * Returns the prefix of the attribute at the specified index. 1047 * 1048 * @param index The index of the attribute. 1049 */ getPrefix(int index)1050 public String getPrefix(int index) { 1051 if (index < 0 || index >= fLength) { 1052 return null; 1053 } 1054 String prefix = fAttributes[index].name.prefix; 1055 // REVISIT: The empty string is not entered in the symbol table! 1056 return prefix != null ? prefix : ""; 1057 } // getPrefix(int):String 1058 1059 /** 1060 * Look up an attribute's Namespace URI by index. 1061 * 1062 * @param index The attribute index (zero-based). 1063 * @return The Namespace URI 1064 * @see #getLength 1065 */ getURI(int index)1066 public String getURI(int index) { 1067 if (index < 0 || index >= fLength) { 1068 return null; 1069 } 1070 String uri = fAttributes[index].name.uri; 1071 return uri; 1072 } // getURI(int):String 1073 1074 /** 1075 * Look up an attribute's value by Namespace name and 1076 * Local name. If Namespace is null, ignore namespace 1077 * comparison. If Namespace is "", treat the name as 1078 * having no Namespace URI. 1079 * 1080 * <p>See {@link #getValue(int) getValue(int)} for a description 1081 * of the possible values.</p> 1082 * 1083 * @param uri The Namespace URI, or null namespaces are ignored. 1084 * @param localName The local name of the attribute. 1085 * @return The attribute value as a string, or null if the 1086 * attribute is not in the list. 1087 */ getValue(String uri, String localName)1088 public String getValue(String uri, String localName) { 1089 int index = getIndex(uri, localName); 1090 return index != -1 ? getValue(index) : null; 1091 } // getValue(String,String):String 1092 1093 /** 1094 * Look up an augmentations by Namespace name. 1095 * 1096 * @param uri The Namespace URI, or null if the 1097 * @param localName The local name of the attribute. 1098 * @return Augmentations 1099 */ getAugmentations(String uri, String localName)1100 public Augmentations getAugmentations (String uri, String localName) { 1101 int index = getIndex(uri, localName); 1102 return index != -1 ? fAttributes[index].augs : null; 1103 } 1104 1105 /** 1106 * Look up an augmentation by XML 1.0 qualified name. 1107 * <p> 1108 * 1109 * @param qName The XML 1.0 qualified name. 1110 * 1111 * @return Augmentations 1112 * 1113 */ getAugmentations(String qName)1114 public Augmentations getAugmentations(String qName){ 1115 int index = getIndex(qName); 1116 return index != -1 ? fAttributes[index].augs : null; 1117 } 1118 1119 1120 1121 /** 1122 * Look up an augmentations by attributes index. 1123 * 1124 * @param attributeIndex The attribute index. 1125 * @return Augmentations 1126 */ getAugmentations(int attributeIndex)1127 public Augmentations getAugmentations (int attributeIndex){ 1128 if (attributeIndex < 0 || attributeIndex >= fLength) { 1129 return null; 1130 } 1131 return fAttributes[attributeIndex].augs; 1132 } 1133 1134 /** 1135 * Sets the augmentations of the attribute at the specified index. 1136 * 1137 * @param attrIndex The attribute index. 1138 * @param augs The augmentations. 1139 */ setAugmentations(int attrIndex, Augmentations augs)1140 public void setAugmentations(int attrIndex, Augmentations augs) { 1141 fAttributes[attrIndex].augs = augs; 1142 } 1143 1144 /** 1145 * Sets the uri of the attribute at the specified index. 1146 * 1147 * @param attrIndex The attribute index. 1148 * @param uri Namespace uri 1149 */ setURI(int attrIndex, String uri)1150 public void setURI(int attrIndex, String uri) { 1151 fAttributes[attrIndex].name.uri = uri; 1152 } // getURI(int,QName) 1153 1154 // Implementation methods 1155 1156 //XMLBufferListener methods 1157 /** 1158 * This method will be invoked by XMLEntityReader before ScannedEntities buffer 1159 * is reloaded. 1160 */ refresh()1161 public void refresh() { 1162 if(fLength > 0){ 1163 for(int i = 0 ; i < fLength ; i++){ 1164 getValue(i); 1165 } 1166 } 1167 } refresh(int pos)1168 public void refresh(int pos) { 1169 } 1170 prepareAndPopulateTableViewNS(final int count)1171 private void prepareAndPopulateTableViewNS(final int count) { 1172 prepareTableView(); 1173 // Need to populate the hash table with the attributes we've processed so far. 1174 Attribute attr; 1175 int bucket; 1176 for (int i = 0; i < count; ++i) { 1177 attr = fAttributes[i]; 1178 bucket = getTableViewBucket(attr.name.localpart, attr.name.uri); 1179 if (fAttributeTableViewChainState[bucket] != fLargeCount) { 1180 fAttributeTableViewChainState[bucket] = fLargeCount; 1181 attr.next = null; 1182 fAttributeTableView[bucket] = attr; 1183 } 1184 else { 1185 // Update table view 1186 attr.next = fAttributeTableView[bucket]; 1187 fAttributeTableView[bucket] = attr; 1188 } 1189 } 1190 } 1191 1192 /** 1193 * Randomly selects a new hash function and reorganizes the table view 1194 * in order to more evenly distribute its entries. This method is called 1195 * automatically when the number of attributes in one bucket exceeds 1196 * MAX_HASH_COLLISIONS. 1197 */ rebalanceTableView(final int count)1198 private void rebalanceTableView(final int count) { 1199 if (fHashMultipliers == null) { 1200 fHashMultipliers = new int[MULTIPLIERS_SIZE + 1]; 1201 } 1202 PrimeNumberSequenceGenerator.generateSequence(fHashMultipliers); 1203 prepareAndPopulateTableView(count); 1204 } 1205 1206 /** 1207 * Randomly selects a new hash function and reorganizes the table view 1208 * in order to more evenly distribute its entries. This method is called 1209 * automatically when the number of attributes in one bucket exceeds 1210 * MAX_HASH_COLLISIONS. 1211 */ rebalanceTableViewNS(final int count)1212 private void rebalanceTableViewNS(final int count) { 1213 if (fHashMultipliers == null) { 1214 fHashMultipliers = new int[MULTIPLIERS_SIZE + 1]; 1215 } 1216 PrimeNumberSequenceGenerator.generateSequence(fHashMultipliers); 1217 prepareAndPopulateTableViewNS(count); 1218 } 1219 1220 // 1221 // Classes 1222 // 1223 1224 /** 1225 * Attribute information. 1226 * 1227 * @author Andy Clark, IBM 1228 */ 1229 static class Attribute { 1230 1231 // 1232 // Data 1233 // 1234 1235 // basic info 1236 1237 /** Name. */ 1238 public final QName name = new QName(); 1239 1240 /** Type. */ 1241 public String type; 1242 1243 /** Value. */ 1244 public String value; 1245 1246 /** This will point to the ScannedEntities buffer.*/ 1247 public XMLString xmlValue; 1248 1249 /** Non-normalized value. */ 1250 public String nonNormalizedValue; 1251 1252 /** Specified. */ 1253 public boolean specified; 1254 1255 1256 /** 1257 * Augmentations information for this attribute. 1258 * XMLAttributes has no knowledge if any augmentations 1259 * were attached to Augmentations. 1260 */ 1261 public Augmentations augs = new AugmentationsImpl(); 1262 1263 // Additional data for attribute table view 1264 1265 /** Pointer to the next attribute in the chain. **/ 1266 public Attribute next; 1267 1268 } // class Attribute 1269 1270 } // class XMLAttributesImpl 1271