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