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