1 /*
   2  * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package org.xml.sax.helpers;
  27 
  28 import org.xml.sax.Attributes;
  29 
  30 
  31 /**
  32  * Default implementation of the Attributes interface.
  33  *
  34  * <p>This class provides a default implementation of the SAX2
  35  * {@link org.xml.sax.Attributes Attributes} interface, with the
  36  * addition of manipulators so that the list can be modified or
  37  * reused.</p>
  38  *
  39  * <p>There are two typical uses of this class:</p>
  40  *
  41  * <ol>
  42  * <li>to take a persistent snapshot of an Attributes object
  43  *  in a {@link org.xml.sax.ContentHandler#startElement startElement} event; or</li>
  44  * <li>to construct or modify an Attributes object in a SAX2 driver or filter.</li>
  45  * </ol>
  46  *
  47  * <p>This class replaces the now-deprecated SAX1 {@link
  48  * org.xml.sax.helpers.AttributeListImpl AttributeListImpl}
  49  * class; in addition to supporting the updated Attributes
  50  * interface rather than the deprecated {@link org.xml.sax.AttributeList
  51  * AttributeList} interface, it also includes a much more efficient
  52  * implementation using a single array rather than a set of Vectors.</p>
  53  *
  54  * @since 1.4, SAX 2.0
  55  * @author David Megginson
  56  */
  57 public class AttributesImpl implements Attributes
  58 {
  59 
  60 
  61     ////////////////////////////////////////////////////////////////////
  62     // Constructors.
  63     ////////////////////////////////////////////////////////////////////
  64 
  65 
  66     /**
  67      * Construct a new, empty AttributesImpl object.
  68      */
  69     public AttributesImpl ()
  70     {
  71         length = 0;
  72         data = null;
  73     }
  74 
  75 
  76     /**
  77      * Copy an existing Attributes object.
  78      *
  79      * <p>This constructor is especially useful inside a
  80      * {@link org.xml.sax.ContentHandler#startElement startElement} event.</p>
  81      *
  82      * @param atts The existing Attributes object.
  83      */
  84     public AttributesImpl (Attributes atts)
  85     {
  86         setAttributes(atts);
  87     }
  88 
  89 
  90 
  91     ////////////////////////////////////////////////////////////////////
  92     // Implementation of org.xml.sax.Attributes.
  93     ////////////////////////////////////////////////////////////////////
  94 
  95 
  96     /**
  97      * Return the number of attributes in the list.
  98      *
  99      * @return The number of attributes in the list.
 100      * @see org.xml.sax.Attributes#getLength
 101      */
 102     public int getLength ()
 103     {
 104         return length;
 105     }
 106 
 107 
 108     /**
 109      * Return an attribute's Namespace URI.
 110      *
 111      * @param index The attribute's index (zero-based).
 112      * @return The Namespace URI, the empty string if none is
 113      *         available, or null if the index is out of range.
 114      * @see org.xml.sax.Attributes#getURI
 115      */
 116     public String getURI (int index)
 117     {
 118         if (index >= 0 && index < length) {
 119             return data[index*5];
 120         } else {
 121             return null;
 122         }
 123     }
 124 
 125 
 126     /**
 127      * Return an attribute's local name.
 128      *
 129      * @param index The attribute's index (zero-based).
 130      * @return The attribute's local name, the empty string if
 131      *         none is available, or null if the index if out of range.
 132      * @see org.xml.sax.Attributes#getLocalName
 133      */
 134     public String getLocalName (int index)
 135     {
 136         if (index >= 0 && index < length) {
 137             return data[index*5+1];
 138         } else {
 139             return null;
 140         }
 141     }
 142 
 143 
 144     /**
 145      * Return an attribute's qualified (prefixed) name.
 146      *
 147      * @param index The attribute's index (zero-based).
 148      * @return The attribute's qualified name, the empty string if
 149      *         none is available, or null if the index is out of bounds.
 150      * @see org.xml.sax.Attributes#getQName
 151      */
 152     public String getQName (int index)
 153     {
 154         if (index >= 0 && index < length) {
 155             return data[index*5+2];
 156         } else {
 157             return null;
 158         }
 159     }
 160 
 161 
 162     /**
 163      * Return an attribute's type by index.
 164      *
 165      * @param index The attribute's index (zero-based).
 166      * @return The attribute's type, "CDATA" if the type is unknown, or null
 167      *         if the index is out of bounds.
 168      * @see org.xml.sax.Attributes#getType(int)
 169      */
 170     public String getType (int index)
 171     {
 172         if (index >= 0 && index < length) {
 173             return data[index*5+3];
 174         } else {
 175             return null;
 176         }
 177     }
 178 
 179 
 180     /**
 181      * Return an attribute's value by index.
 182      *
 183      * @param index The attribute's index (zero-based).
 184      * @return The attribute's value or null if the index is out of bounds.
 185      * @see org.xml.sax.Attributes#getValue(int)
 186      */
 187     public String getValue (int index)
 188     {
 189         if (index >= 0 && index < length) {
 190             return data[index*5+4];
 191         } else {
 192             return null;
 193         }
 194     }
 195 
 196 
 197     /**
 198      * Look up an attribute's index by Namespace name.
 199      *
 200      * <p>In many cases, it will be more efficient to look up the name once and
 201      * use the index query methods rather than using the name query methods
 202      * repeatedly.</p>
 203      *
 204      * @param uri The attribute's Namespace URI, or the empty
 205      *        string if none is available.
 206      * @param localName The attribute's local name.
 207      * @return The attribute's index, or -1 if none matches.
 208      * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
 209      */
 210     public int getIndex (String uri, String localName)
 211     {
 212         int max = length * 5;
 213         for (int i = 0; i < max; i += 5) {
 214             if (data[i].equals(uri) && data[i+1].equals(localName)) {
 215                 return i / 5;
 216             }
 217         }
 218         return -1;
 219     }
 220 
 221 
 222     /**
 223      * Look up an attribute's index by qualified (prefixed) name.
 224      *
 225      * @param qName The qualified name.
 226      * @return The attribute's index, or -1 if none matches.
 227      * @see org.xml.sax.Attributes#getIndex(java.lang.String)
 228      */
 229     public int getIndex (String qName)
 230     {
 231         int max = length * 5;
 232         for (int i = 0; i < max; i += 5) {
 233             if (data[i+2].equals(qName)) {
 234                 return i / 5;
 235             }
 236         }
 237         return -1;
 238     }
 239 
 240 
 241     /**
 242      * Look up an attribute's type by Namespace-qualified name.
 243      *
 244      * @param uri The Namespace URI, or the empty string for a name
 245      *        with no explicit Namespace URI.
 246      * @param localName The local name.
 247      * @return The attribute's type, or null if there is no
 248      *         matching attribute.
 249      * @see org.xml.sax.Attributes#getType(java.lang.String,java.lang.String)
 250      */
 251     public String getType (String uri, String localName)
 252     {
 253         int max = length * 5;
 254         for (int i = 0; i < max; i += 5) {
 255             if (data[i].equals(uri) && data[i+1].equals(localName)) {
 256                 return data[i+3];
 257             }
 258         }
 259         return null;
 260     }
 261 
 262 
 263     /**
 264      * Look up an attribute's type by qualified (prefixed) name.
 265      *
 266      * @param qName The qualified name.
 267      * @return The attribute's type, or null if there is no
 268      *         matching attribute.
 269      * @see org.xml.sax.Attributes#getType(java.lang.String)
 270      */
 271     public String getType (String qName)
 272     {
 273         int max = length * 5;
 274         for (int i = 0; i < max; i += 5) {
 275             if (data[i+2].equals(qName)) {
 276                 return data[i+3];
 277             }
 278         }
 279         return null;
 280     }
 281 
 282 
 283     /**
 284      * Look up an attribute's value by Namespace-qualified name.
 285      *
 286      * @param uri The Namespace URI, or the empty string for a name
 287      *        with no explicit Namespace URI.
 288      * @param localName The local name.
 289      * @return The attribute's value, or null if there is no
 290      *         matching attribute.
 291      * @see org.xml.sax.Attributes#getValue(java.lang.String,java.lang.String)
 292      */
 293     public String getValue (String uri, String localName)
 294     {
 295         int max = length * 5;
 296         for (int i = 0; i < max; i += 5) {
 297             if (data[i].equals(uri) && data[i+1].equals(localName)) {
 298                 return data[i+4];
 299             }
 300         }
 301         return null;
 302     }
 303 
 304 
 305     /**
 306      * Look up an attribute's value by qualified (prefixed) name.
 307      *
 308      * @param qName The qualified name.
 309      * @return The attribute's value, or null if there is no
 310      *         matching attribute.
 311      * @see org.xml.sax.Attributes#getValue(java.lang.String)
 312      */
 313     public String getValue (String qName)
 314     {
 315         int max = length * 5;
 316         for (int i = 0; i < max; i += 5) {
 317             if (data[i+2].equals(qName)) {
 318                 return data[i+4];
 319             }
 320         }
 321         return null;
 322     }
 323 
 324 
 325 
 326     ////////////////////////////////////////////////////////////////////
 327     // Manipulators.
 328     ////////////////////////////////////////////////////////////////////
 329 
 330 
 331     /**
 332      * Clear the attribute list for reuse.
 333      *
 334      * <p>Note that little memory is freed by this call:
 335      * the current array is kept so it can be
 336      * reused.</p>
 337      */
 338     public void clear ()
 339     {
 340         if (data != null) {
 341             for (int i = 0; i < (length * 5); i++)
 342                 data [i] = null;
 343         }
 344         length = 0;
 345     }
 346 
 347 
 348     /**
 349      * Copy an entire Attributes object.
 350      *
 351      * <p>It may be more efficient to reuse an existing object
 352      * rather than constantly allocating new ones.</p>
 353      *
 354      * @param atts The attributes to copy.
 355      */
 356     public void setAttributes (Attributes atts)
 357     {
 358         clear();
 359         length = atts.getLength();
 360         if (length > 0) {
 361             data = new String[length*5];
 362             for (int i = 0; i < length; i++) {
 363                 data[i*5] = atts.getURI(i);
 364                 data[i*5+1] = atts.getLocalName(i);
 365                 data[i*5+2] = atts.getQName(i);
 366                 data[i*5+3] = atts.getType(i);
 367                 data[i*5+4] = atts.getValue(i);
 368             }
 369         }
 370     }
 371 
 372 
 373     /**
 374      * Add an attribute to the end of the list.
 375      *
 376      * <p>For the sake of speed, this method does no checking
 377      * to see if the attribute is already in the list: that is
 378      * the responsibility of the application.</p>
 379      *
 380      * @param uri The Namespace URI, or the empty string if
 381      *        none is available or Namespace processing is not
 382      *        being performed.
 383      * @param localName The local name, or the empty string if
 384      *        Namespace processing is not being performed.
 385      * @param qName The qualified (prefixed) name, or the empty string
 386      *        if qualified names are not available.
 387      * @param type The attribute type as a string.
 388      * @param value The attribute value.
 389      */
 390     public void addAttribute (String uri, String localName, String qName,
 391                               String type, String value)
 392     {
 393         ensureCapacity(length+1);
 394         data[length*5] = uri;
 395         data[length*5+1] = localName;
 396         data[length*5+2] = qName;
 397         data[length*5+3] = type;
 398         data[length*5+4] = value;
 399         length++;
 400     }
 401 
 402 
 403     /**
 404      * Set an attribute in the list.
 405      *
 406      * <p>For the sake of speed, this method does no checking
 407      * for name conflicts or well-formedness: such checks are the
 408      * responsibility of the application.</p>
 409      *
 410      * @param index The index of the attribute (zero-based).
 411      * @param uri The Namespace URI, or the empty string if
 412      *        none is available or Namespace processing is not
 413      *        being performed.
 414      * @param localName The local name, or the empty string if
 415      *        Namespace processing is not being performed.
 416      * @param qName The qualified name, or the empty string
 417      *        if qualified names are not available.
 418      * @param type The attribute type as a string.
 419      * @param value The attribute value.
 420      * @exception java.lang.ArrayIndexOutOfBoundsException When the
 421      *            supplied index does not point to an attribute
 422      *            in the list.
 423      */
 424     public void setAttribute (int index, String uri, String localName,
 425                               String qName, String type, String value)
 426     {
 427         if (index >= 0 && index < length) {
 428             data[index*5] = uri;
 429             data[index*5+1] = localName;
 430             data[index*5+2] = qName;
 431             data[index*5+3] = type;
 432             data[index*5+4] = value;
 433         } else {
 434             badIndex(index);
 435         }
 436     }
 437 
 438 
 439     /**
 440      * Remove an attribute from the list.
 441      *
 442      * @param index The index of the attribute (zero-based).
 443      * @exception java.lang.ArrayIndexOutOfBoundsException When the
 444      *            supplied index does not point to an attribute
 445      *            in the list.
 446      */
 447     public void removeAttribute (int index)
 448     {
 449         if (index >= 0 && index < length) {
 450             if (index < length - 1) {
 451                 System.arraycopy(data, (index+1)*5, data, index*5,
 452                                  (length-index-1)*5);
 453             }
 454             index = (length - 1) * 5;
 455             data [index++] = null;
 456             data [index++] = null;
 457             data [index++] = null;
 458             data [index++] = null;
 459             data [index] = null;
 460             length--;
 461         } else {
 462             badIndex(index);
 463         }
 464     }
 465 
 466 
 467     /**
 468      * Set the Namespace URI of a specific attribute.
 469      *
 470      * @param index The index of the attribute (zero-based).
 471      * @param uri The attribute's Namespace URI, or the empty
 472      *        string for none.
 473      * @exception java.lang.ArrayIndexOutOfBoundsException When the
 474      *            supplied index does not point to an attribute
 475      *            in the list.
 476      */
 477     public void setURI (int index, String uri)
 478     {
 479         if (index >= 0 && index < length) {
 480             data[index*5] = uri;
 481         } else {
 482             badIndex(index);
 483         }
 484     }
 485 
 486 
 487     /**
 488      * Set the local name of a specific attribute.
 489      *
 490      * @param index The index of the attribute (zero-based).
 491      * @param localName The attribute's local name, or the empty
 492      *        string for none.
 493      * @exception java.lang.ArrayIndexOutOfBoundsException When the
 494      *            supplied index does not point to an attribute
 495      *            in the list.
 496      */
 497     public void setLocalName (int index, String localName)
 498     {
 499         if (index >= 0 && index < length) {
 500             data[index*5+1] = localName;
 501         } else {
 502             badIndex(index);
 503         }
 504     }
 505 
 506 
 507     /**
 508      * Set the qualified name of a specific attribute.
 509      *
 510      * @param index The index of the attribute (zero-based).
 511      * @param qName The attribute's qualified name, or the empty
 512      *        string for none.
 513      * @exception java.lang.ArrayIndexOutOfBoundsException When the
 514      *            supplied index does not point to an attribute
 515      *            in the list.
 516      */
 517     public void setQName (int index, String qName)
 518     {
 519         if (index >= 0 && index < length) {
 520             data[index*5+2] = qName;
 521         } else {
 522             badIndex(index);
 523         }
 524     }
 525 
 526 
 527     /**
 528      * Set the type of a specific attribute.
 529      *
 530      * @param index The index of the attribute (zero-based).
 531      * @param type The attribute's type.
 532      * @exception java.lang.ArrayIndexOutOfBoundsException When the
 533      *            supplied index does not point to an attribute
 534      *            in the list.
 535      */
 536     public void setType (int index, String type)
 537     {
 538         if (index >= 0 && index < length) {
 539             data[index*5+3] = type;
 540         } else {
 541             badIndex(index);
 542         }
 543     }
 544 
 545 
 546     /**
 547      * Set the value of a specific attribute.
 548      *
 549      * @param index The index of the attribute (zero-based).
 550      * @param value The attribute's value.
 551      * @exception java.lang.ArrayIndexOutOfBoundsException When the
 552      *            supplied index does not point to an attribute
 553      *            in the list.
 554      */
 555     public void setValue (int index, String value)
 556     {
 557         if (index >= 0 && index < length) {
 558             data[index*5+4] = value;
 559         } else {
 560             badIndex(index);
 561         }
 562     }
 563 
 564 
 565 
 566     ////////////////////////////////////////////////////////////////////
 567     // Internal methods.
 568     ////////////////////////////////////////////////////////////////////
 569 
 570 
 571     /**
 572      * Ensure the internal array's capacity.
 573      *
 574      * @param n The minimum number of attributes that the array must
 575      *        be able to hold.
 576      */
 577     private void ensureCapacity (int n)    {
 578         if (n <= 0) {
 579             return;
 580         }
 581         int max;
 582         if (data == null || data.length == 0) {
 583             max = 25;
 584         }
 585         else if (data.length >= n * 5) {
 586             return;
 587         }
 588         else {
 589             max = data.length;
 590         }
 591         while (max < n * 5) {
 592             max *= 2;
 593         }
 594 
 595         String newData[] = new String[max];
 596         if (length > 0) {
 597             System.arraycopy(data, 0, newData, 0, length*5);
 598         }
 599         data = newData;
 600     }
 601 
 602 
 603     /**
 604      * Report a bad array index in a manipulator.
 605      *
 606      * @param index The index to report.
 607      * @exception java.lang.ArrayIndexOutOfBoundsException Always.
 608      */
 609     private void badIndex (int index)
 610         throws ArrayIndexOutOfBoundsException
 611     {
 612         String msg =
 613             "Attempt to modify attribute at illegal index: " + index;
 614         throw new ArrayIndexOutOfBoundsException(msg);
 615     }
 616 
 617 
 618 
 619     ////////////////////////////////////////////////////////////////////
 620     // Internal state.
 621     ////////////////////////////////////////////////////////////////////
 622 
 623     int length;
 624     String data [];
 625 
 626 }
 627 
 628 // end of AttributesImpl.java