/* |
|
* Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. |
|
*/ |
|
/* |
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
|
* contributor license agreements. See the NOTICE file distributed with |
|
* this work for additional information regarding copyright ownership. |
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
|
* (the "License"); you may not use this file except in compliance with |
|
* the License. You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
package com.sun.org.apache.xerces.internal.util; |
|
import com.sun.xml.internal.stream.XMLBufferListener; |
|
import com.sun.org.apache.xerces.internal.xni.Augmentations; |
|
import com.sun.org.apache.xerces.internal.xni.QName; |
|
import com.sun.org.apache.xerces.internal.xni.XMLAttributes; |
|
import com.sun.org.apache.xerces.internal.xni.XMLString; |
|
/** |
|
* The XMLAttributesImpl class is an implementation of the XMLAttributes |
|
* interface which defines a collection of attributes for an element. |
|
* In the parser, the document source would scan the entire start element |
|
* and collect the attributes. The attributes are communicated to the |
|
* document handler in the startElement method. |
|
* <p> |
|
* The attributes are read-write so that subsequent stages in the document |
|
* pipeline can modify the values or change the attributes that are |
|
* propogated to the next stage. |
|
* |
|
* @see com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler#startElement |
|
* |
|
* @author Andy Clark, IBM |
|
* @author Elena Litani, IBM |
|
* @author Michael Glavassevich, IBM |
|
* |
|
*/ |
|
public class XMLAttributesImpl |
|
implements XMLAttributes, XMLBufferListener { |
|
// |
|
// Constants |
|
// |
|
/** Default table size. */ |
|
protected static final int TABLE_SIZE = 101; |
|
/** Maximum hash collisions per bucket. */ |
|
protected static final int MAX_HASH_COLLISIONS = 40; |
|
protected static final int MULTIPLIERS_SIZE = 1 << 5; |
|
protected static final int MULTIPLIERS_MASK = MULTIPLIERS_SIZE - 1; |
|
/** |
|
* Threshold at which an instance is treated |
|
* as a large attribute list. |
|
*/ |
|
protected static final int SIZE_LIMIT = 20; |
|
// |
|
// Data |
|
// |
|
// features |
|
/** Namespaces. */ |
|
protected boolean fNamespaces = true; |
|
// data |
|
/** |
|
* Usage count for the attribute table view. |
|
* Incremented each time all attributes are removed |
|
* when the attribute table view is in use. |
|
*/ |
|
protected int fLargeCount = 1; |
|
/** Attribute count. */ |
|
protected int fLength; |
|
/** Attribute information. */ |
|
protected Attribute[] fAttributes = new Attribute[4]; |
|
/** |
|
* Provides an alternate view of the attribute specification. |
|
*/ |
|
protected Attribute[] fAttributeTableView; |
|
/** |
|
* Tracks whether each chain in the hash table is stale |
|
* with respect to the current state of this object. |
|
* A chain is stale if its state is not the same as the number |
|
* of times the attribute table view has been used. |
|
*/ |
|
protected int[] fAttributeTableViewChainState; |
|
/** |
|
* Actual number of buckets in the table view. |
|
*/ |
|
protected int fTableViewBuckets; |
|
/** |
|
* Indicates whether the table view contains consistent data. |
|
*/ |
|
protected boolean fIsTableViewConsistent; |
|
/** |
|
* Array of randomly selected hash function multipliers or <code>null</code> |
|
* if the default String.hashCode() function should be used. |
|
*/ |
|
protected int[] fHashMultipliers; |
|
// |
|
// Constructors |
|
// |
|
/** Default constructor. */ |
|
public XMLAttributesImpl() { |
|
this(TABLE_SIZE); |
|
} |
|
/** |
|
* @param tableSize initial size of table view |
|
*/ |
|
public XMLAttributesImpl(int tableSize) { |
|
fTableViewBuckets = tableSize; |
|
for (int i = 0; i < fAttributes.length; i++) { |
|
fAttributes[i] = new Attribute(); |
|
} |
|
} // <init>() |
|
// |
|
// Public methods |
|
// |
|
/** |
|
* Sets whether namespace processing is being performed. This state |
|
* is needed to return the correct value from the getLocalName method. |
|
* |
|
* @param namespaces True if namespace processing is turned on. |
|
* |
|
* @see #getLocalName |
|
*/ |
|
public void setNamespaces(boolean namespaces) { |
|
fNamespaces = namespaces; |
|
} // setNamespaces(boolean) |
|
// |
|
// XMLAttributes methods |
|
// |
|
/** |
|
* Adds an attribute. The attribute's non-normalized value of the |
|
* attribute will have the same value as the attribute value until |
|
* set using the <code>setNonNormalizedValue</code> method. Also, |
|
* the added attribute will be marked as specified in the XML instance |
|
* document unless set otherwise using the <code>setSpecified</code> |
|
* method. |
|
* <p> |
|
* <strong>Note:</strong> If an attribute of the same name already |
|
* exists, the old values for the attribute are replaced by the new |
|
* values. |
|
* |
|
* @param name The attribute name. |
|
* @param type The attribute type. The type name is determined by |
|
* the type specified for this attribute in the DTD. |
|
* For example: "CDATA", "ID", "NMTOKEN", etc. However, |
|
* attributes of type enumeration will have the type |
|
* value specified as the pipe ('|') separated list of |
|
* the enumeration values prefixed by an open |
|
* parenthesis and suffixed by a close parenthesis. |
|
* For example: "(true|false)". |
|
* @param value The attribute value. |
|
* |
|
* @return Returns the attribute index. |
|
* |
|
* @see #setNonNormalizedValue |
|
* @see #setSpecified |
|
*/ |
|
public int addAttribute(QName name, String type, String value) { |
|
return addAttribute(name,type,value,null); |
|
} |
|
public int addAttribute(QName name, String type, String value,XMLString valueCache) { |
|
int index; |
|
if (fLength < SIZE_LIMIT) { |
|
index = name.uri != null && name.uri.length() != 0 |
|
? getIndexFast(name.uri, name.localpart) |
|
: getIndexFast(name.rawname); |
|
if (index == -1) { |
|
index = fLength; |
|
if (fLength++ == fAttributes.length) { |
|
Attribute[] attributes = new Attribute[fAttributes.length + 4]; |
|
System.arraycopy(fAttributes, 0, attributes, 0, fAttributes.length); |
|
for (int i = fAttributes.length; i < attributes.length; i++) { |
|
attributes[i] = new Attribute(); |
|
} |
|
fAttributes = attributes; |
|
} |
|
} |
|
} |
|
else if (name.uri == null || |
|
name.uri.length() == 0 || |
|
(index = getIndexFast(name.uri, name.localpart)) == -1) { |
|
/** |
|
* If attributes were removed from the list after the table |
|
* becomes in use this isn't reflected in the table view. It's |
|
* assumed that once a user starts removing attributes they're |
|
* not likely to add more. We only make the view consistent if |
|
* the user of this class adds attributes, removes them, and |
|
* then adds more. |
|
*/ |
|
if (!fIsTableViewConsistent || fLength == SIZE_LIMIT || |
|
(fLength > SIZE_LIMIT && fLength > fTableViewBuckets)) { |
|
prepareAndPopulateTableView(); |
|
fIsTableViewConsistent = true; |
|
} |
|
int bucket = getTableViewBucket(name.rawname); |
|
// The chain is stale. |
|
// This must be a unique attribute. |
|
if (fAttributeTableViewChainState[bucket] != fLargeCount) { |
|
index = fLength; |
|
if (fLength++ == fAttributes.length) { |
|
Attribute[] attributes = new Attribute[fAttributes.length << 1]; |
|
System.arraycopy(fAttributes, 0, attributes, 0, fAttributes.length); |
|
for (int i = fAttributes.length; i < attributes.length; i++) { |
|
attributes[i] = new Attribute(); |
|
} |
|
fAttributes = attributes; |
|
} |
|
// Update table view. |
|
fAttributeTableViewChainState[bucket] = fLargeCount; |
|
fAttributes[index].next = null; |
|
fAttributeTableView[bucket] = fAttributes[index]; |
|
} |
|
// This chain is active. |
|
// We need to check if any of the attributes has the same rawname. |
|
else { |
|
// Search the table. |
|
int collisionCount = 0; |
|
Attribute found = fAttributeTableView[bucket]; |
|
while (found != null) { |
|
if (found.name.rawname == name.rawname) { |
|
break; |
|
} |
|
found = found.next; |
|
++collisionCount; |
|
} |
|
// This attribute is unique. |
|
if (found == null) { |
|
index = fLength; |
|
if (fLength++ == fAttributes.length) { |
|
Attribute[] attributes = new Attribute[fAttributes.length << 1]; |
|
System.arraycopy(fAttributes, 0, attributes, 0, fAttributes.length); |
|
for (int i = fAttributes.length; i < attributes.length; i++) { |
|
attributes[i] = new Attribute(); |
|
} |
|
fAttributes = attributes; |
|
} |
|
// Select a new hash function and rehash the table view |
|
// if the collision threshold is exceeded. |
|
if (collisionCount >= MAX_HASH_COLLISIONS) { |
|
// The current attribute will be processed in the rehash. |
|
// Need to set its name first. |
|
fAttributes[index].name.setValues(name); |
|
rebalanceTableView(fLength); |
|
} |
|
else { |
|
// Update table view |
|
fAttributes[index].next = fAttributeTableView[bucket]; |
|
fAttributeTableView[bucket] = fAttributes[index]; |
|
} |
|
} |
|
// Duplicate. We still need to find the index. |
|
else { |
|
index = getIndexFast(name.rawname); |
|
} |
|
} |
|
} |
|
// set values |
|
Attribute attribute = fAttributes[index]; |
|
attribute.name.setValues(name); |
|
attribute.type = type; |
|
attribute.value = value; |
|
attribute.xmlValue = valueCache; |
|
attribute.nonNormalizedValue = value; |
|
attribute.specified = false; |
|
// clear augmentations |
|
if(attribute.augs != null) |
|
attribute.augs.removeAllItems(); |
|
return index; |
|
} // addAttribute(QName,String,XMLString) |
|
/** |
|
* Removes all of the attributes. This method will also remove all |
|
* entities associated to the attributes. |
|
*/ |
|
public void removeAllAttributes() { |
|
fLength = 0; |
|
} // removeAllAttributes() |
|
/** |
|
* Removes the attribute at the specified index. |
|
* <p> |
|
* <strong>Note:</strong> This operation changes the indexes of all |
|
* attributes following the attribute at the specified index. |
|
* |
|
* @param attrIndex The attribute index. |
|
*/ |
|
public void removeAttributeAt(int attrIndex) { |
|
fIsTableViewConsistent = false; |
|
if (attrIndex < fLength - 1) { |
|
Attribute removedAttr = fAttributes[attrIndex]; |
|
System.arraycopy(fAttributes, attrIndex + 1, |
|
fAttributes, attrIndex, fLength - attrIndex - 1); |
|
// Make the discarded Attribute object available for re-use |
|
// by tucking it after the Attributes that are still in use |
|
fAttributes[fLength-1] = removedAttr; |
|
} |
|
fLength--; |
|
} // removeAttributeAt(int) |
|
/** |
|
* Sets the name of the attribute at the specified index. |
|
* |
|
* @param attrIndex The attribute index. |
|
* @param attrName The new attribute name. |
|
*/ |
|
public void setName(int attrIndex, QName attrName) { |
|
fAttributes[attrIndex].name.setValues(attrName); |
|
} // setName(int,QName) |
|
/** |
|
* Sets the fields in the given QName structure with the values |
|
* of the attribute name at the specified index. |
|
* |
|
* @param attrIndex The attribute index. |
|
* @param attrName The attribute name structure to fill in. |
|
*/ |
|
public void getName(int attrIndex, QName attrName) { |
|
attrName.setValues(fAttributes[attrIndex].name); |
|
} // getName(int,QName) |
|
/** |
|
* Sets the type of the attribute at the specified index. |
|
* |
|
* @param attrIndex The attribute index. |
|
* @param attrType The attribute type. The type name is determined by |
|
* the type specified for this attribute in the DTD. |
|
* For example: "CDATA", "ID", "NMTOKEN", etc. However, |
|
* attributes of type enumeration will have the type |
|
* value specified as the pipe ('|') separated list of |
|
* the enumeration values prefixed by an open |
|
* parenthesis and suffixed by a close parenthesis. |
|
* For example: "(true|false)". |
|
*/ |
|
public void setType(int attrIndex, String attrType) { |
|
fAttributes[attrIndex].type = attrType; |
|
} // setType(int,String) |
|
/** |
|
* Sets the value of the attribute at the specified index. This |
|
* method will overwrite the non-normalized value of the attribute. |
|
* |
|
* @param attrIndex The attribute index. |
|
* @param attrValue The new attribute value. |
|
* |
|
* @see #setNonNormalizedValue |
|
*/ |
|
public void setValue(int attrIndex, String attrValue) { |
|
setValue(attrIndex,attrValue,null); |
|
} |
|
public void setValue(int attrIndex, String attrValue,XMLString value) { |
|
Attribute attribute = fAttributes[attrIndex]; |
|
attribute.value = attrValue; |
|
attribute.nonNormalizedValue = attrValue; |
|
attribute.xmlValue = value; |
|
} // setValue(int,String) |
|
/** |
|
* Sets the non-normalized value of the attribute at the specified |
|
* index. |
|
* |
|
* @param attrIndex The attribute index. |
|
* @param attrValue The new non-normalized attribute value. |
|
*/ |
|
public void setNonNormalizedValue(int attrIndex, String attrValue) { |
|
if (attrValue == null) { |
|
attrValue = fAttributes[attrIndex].value; |
|
} |
|
fAttributes[attrIndex].nonNormalizedValue = attrValue; |
|
} // setNonNormalizedValue(int,String) |
|
/** |
|
* Returns the non-normalized value of the attribute at the specified |
|
* index. If no non-normalized value is set, this method will return |
|
* the same value as the <code>getValue(int)</code> method. |
|
* |
|
* @param attrIndex The attribute index. |
|
*/ |
|
public String getNonNormalizedValue(int attrIndex) { |
|
String value = fAttributes[attrIndex].nonNormalizedValue; |
|
return value; |
|
} // getNonNormalizedValue(int):String |
|
/** |
|
* Sets whether an attribute is specified in the instance document |
|
* or not. |
|
* |
|
* @param attrIndex The attribute index. |
|
* @param specified True if the attribute is specified in the instance |
|
* document. |
|
*/ |
|
public void setSpecified(int attrIndex, boolean specified) { |
|
fAttributes[attrIndex].specified = specified; |
|
} // setSpecified(int,boolean) |
|
/** |
|
* Returns true if the attribute is specified in the instance document. |
|
* |
|
* @param attrIndex The attribute index. |
|
*/ |
|
public boolean isSpecified(int attrIndex) { |
|
return fAttributes[attrIndex].specified; |
|
} // isSpecified(int):boolean |
|
// |
|
// AttributeList and Attributes methods |
|
// |
|
/** |
|
* Return the number of attributes in the list. |
|
* |
|
* <p>Once you know the number of attributes, you can iterate |
|
* through the list.</p> |
|
* |
|
* @return The number of attributes in the list. |
|
*/ |
|
public int getLength() { |
|
return fLength; |
|
} // getLength():int |
|
/** |
|
* Look up an attribute's type by index. |
|
* |
|
* <p>The attribute type is one of the strings "CDATA", "ID", |
|
* "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", "ENTITIES", |
|
* or "NOTATION" (always in upper case).</p> |
|
* |
|
* <p>If the parser has not read a declaration for the attribute, |
|
* or if the parser does not report attribute types, then it must |
|
* return the value "CDATA" as stated in the XML 1.0 Recommentation |
|
* (clause 3.3.3, "Attribute-Value Normalization").</p> |
|
* |
|
* <p>For an enumerated attribute that is not a notation, the |
|
* parser will report the type as "NMTOKEN".</p> |
|
* |
|
* @param index The attribute index (zero-based). |
|
* @return The attribute's type as a string, or null if the |
|
* index is out of range. |
|
* @see #getLength |
|
*/ |
|
public String getType(int index) { |
|
if (index < 0 || index >= fLength) { |
|
return null; |
|
} |
|
return getReportableType(fAttributes[index].type); |
|
} // getType(int):String |
|
/** |
|
* Look up an attribute's type by XML 1.0 qualified name. |
|
* |
|
* <p>See {@link #getType(int) getType(int)} for a description |
|
* of the possible types.</p> |
|
* |
|
* @param qname The XML 1.0 qualified name. |
|
* @return The attribute type as a string, or null if the |
|
* attribute is not in the list or if qualified names |
|
* are not available. |
|
*/ |
|
public String getType(String qname) { |
|
int index = getIndex(qname); |
|
return index != -1 ? getReportableType(fAttributes[index].type) : null; |
|
} // getType(String):String |
|
/** |
|
* Look up an attribute's value by index. |
|
* |
|
* <p>If the attribute value is a list of tokens (IDREFS, |
|
* ENTITIES, or NMTOKENS), the tokens will be concatenated |
|
* into a single string with each token separated by a |
|
* single space.</p> |
|
* |
|
* @param index The attribute index (zero-based). |
|
* @return The attribute's value as a string, or null if the |
|
* index is out of range. |
|
* @see #getLength |
|
*/ |
|
public String getValue(int index) { |
|
if (index < 0 || index >= fLength) { |
|
return null; |
|
} |
|
if(fAttributes[index].value == null && fAttributes[index].xmlValue != null) |
|
fAttributes[index].value = fAttributes[index].xmlValue.toString(); |
|
return fAttributes[index].value; |
|
} // getValue(int):String |
|
/** |
|
* Look up an attribute's value by XML 1.0 qualified name. |
|
* |
|
* <p>See {@link #getValue(int) getValue(int)} for a description |
|
* of the possible values.</p> |
|
* |
|
* @param qname The XML 1.0 qualified name. |
|
* @return The attribute value as a string, or null if the |
|
* attribute is not in the list or if qualified names |
|
* are not available. |
|
*/ |
|
public String getValue(String qname) { |
|
int index = getIndex(qname); |
|
if(index == -1 ) |
|
return null; |
|
if(fAttributes[index].value == null) |
|
fAttributes[index].value = fAttributes[index].xmlValue.toString(); |
|
return fAttributes[index].value; |
|
} // getValue(String):String |
|
// |
|
// AttributeList methods |
|
// |
|
/** |
|
* Return the name of an attribute in this list (by position). |
|
* |
|
* <p>The names must be unique: the SAX parser shall not include the |
|
* same attribute twice. Attributes without values (those declared |
|
* #IMPLIED without a value specified in the start tag) will be |
|
* omitted from the list.</p> |
|
* |
|
* <p>If the attribute name has a namespace prefix, the prefix |
|
* will still be attached.</p> |
|
* |
|
* @param i The index of the attribute in the list (starting at 0). |
|
* @return The name of the indexed attribute, or null |
|
* if the index is out of range. |
|
* @see #getLength |
|
*/ |
|
public String getName(int index) { |
|
if (index < 0 || index >= fLength) { |
|
return null; |
|
} |
|
return fAttributes[index].name.rawname; |
|
} // getName(int):String |
|
// |
|
// Attributes methods |
|
// |
|
/** |
|
* Look up the index of an attribute by XML 1.0 qualified name. |
|
* |
|
* @param qName The qualified (prefixed) name. |
|
* @return The index of the attribute, or -1 if it does not |
|
* appear in the list. |
|
*/ |
|
public int getIndex(String qName) { |
|
for (int i = 0; i < fLength; i++) { |
|
Attribute attribute = fAttributes[i]; |
|
if (attribute.name.rawname != null && |
|
attribute.name.rawname.equals(qName)) { |
|
return i; |
|
} |
|
} |
|
return -1; |
|
} // getIndex(String):int |
|
/** |
|
* Look up the index of an attribute by Namespace name. |
|
* |
|
* @param uri The Namespace URI, or null if |
|
* the name has no Namespace URI. |
|
* @param localName The attribute's local name. |
|
* @return The index of the attribute, or -1 if it does not |
|
* appear in the list. |
|
*/ |
|
public int getIndex(String uri, String localPart) { |
|
for (int i = 0; i < fLength; i++) { |
|
Attribute attribute = fAttributes[i]; |
|
if (attribute.name.localpart != null && |
|
attribute.name.localpart.equals(localPart) && |
|
((uri==attribute.name.uri) || |
|
(uri!=null && attribute.name.uri!=null && attribute.name.uri.equals(uri)))) { |
|
return i; |
|
} |
|
} |
|
return -1; |
|
} // getIndex(String,String):int |
|
/** |
|
* Look up the index of an attribute by local name only, |
|
* ignoring its namespace. |
|
* |
|
* @param localName The attribute's local name. |
|
* @return The index of the attribute, or -1 if it does not |
|
* appear in the list. |
|
*/ |
|
public int getIndexByLocalName(String localPart) { |
|
for (int i = 0; i < fLength; i++) { |
|
Attribute attribute = fAttributes[i]; |
|
if (attribute.name.localpart != null && |
|
attribute.name.localpart.equals(localPart)) { |
|
return i; |
|
} |
|
} |
|
return -1; |
|
} // getIndex(String):int |
|
/** |
|
* Look up an attribute's local name by index. |
|
* |
|
* @param index The attribute index (zero-based). |
|
* @return The local name, or the empty string if Namespace |
|
* processing is not being performed, or null |
|
* if the index is out of range. |
|
* @see #getLength |
|
*/ |
|
public String getLocalName(int index) { |
|
if (!fNamespaces) { |
|
return ""; |
|
} |
|
if (index < 0 || index >= fLength) { |
|
return null; |
|
} |
|
return fAttributes[index].name.localpart; |
|
} // getLocalName(int):String |
|
/** |
|
* Look up an attribute's XML 1.0 qualified name by index. |
|
* |
|
* @param index The attribute index (zero-based). |
|
* @return The XML 1.0 qualified name, or the empty string |
|
* if none is available, or null if the index |
|
* is out of range. |
|
* @see #getLength |
|
*/ |
|
public String getQName(int index) { |
|
if (index < 0 || index >= fLength) { |
|
return null; |
|
} |
|
String rawname = fAttributes[index].name.rawname; |
|
return rawname != null ? rawname : ""; |
|
} // getQName(int):String |
|
public QName getQualifiedName(int index){ |
|
if (index < 0 || index >= fLength) { |
|
return null; |
|
} |
|
return fAttributes[index].name; |
|
} |
|
/** |
|
* Look up an attribute's type by Namespace name. |
|
* |
|
* <p>See {@link #getType(int) getType(int)} for a description |
|
* of the possible types.</p> |
|
* |
|
* @param uri The Namespace URI, or null if the |
|
* name has no Namespace URI. |
|
* @param localName The local name of the attribute. |
|
* @return The attribute type as a string, or null if the |
|
* attribute is not in the list or if Namespace |
|
* processing is not being performed. |
|
*/ |
|
public String getType(String uri, String localName) { |
|
if (!fNamespaces) { |
|
return null; |
|
} |
|
int index = getIndex(uri, localName); |
|
return index != -1 ? getType(index) : null; |
|
} // getType(String,String):String |
|
/** |
|
* Look up the index of an attribute by XML 1.0 qualified name. |
|
* <p> |
|
* <strong>Note:</strong> |
|
* This method uses reference comparison, and thus should |
|
* only be used internally. We cannot use this method in any |
|
* code exposed to users as they may not pass in unique strings. |
|
* |
|
* @param qName The qualified (prefixed) name. |
|
* @return The index of the attribute, or -1 if it does not |
|
* appear in the list. |
|
*/ |
|
public int getIndexFast(String qName) { |
|
for (int i = 0; i < fLength; ++i) { |
|
Attribute attribute = fAttributes[i]; |
|
if (attribute.name.rawname == qName) { |
|
return i; |
|
} |
|
} |
|
return -1; |
|
} // getIndexFast(String):int |
|
/** |
|
* Adds an attribute. The attribute's non-normalized value of the |
|
* attribute will have the same value as the attribute value until |
|
* set using the <code>setNonNormalizedValue</code> method. Also, |
|
* the added attribute will be marked as specified in the XML instance |
|
* document unless set otherwise using the <code>setSpecified</code> |
|
* method. |
|
* <p> |
|
* This method differs from <code>addAttribute</code> in that it |
|
* does not check if an attribute of the same name already exists |
|
* in the list before adding it. In order to improve performance |
|
* of namespace processing, this method allows uniqueness checks |
|
* to be deferred until all the namespace information is available |
|
* after the entire attribute specification has been read. |
|
* <p> |
|
* <strong>Caution:</strong> If this method is called it should |
|
* not be mixed with calls to <code>addAttribute</code> unless |
|
* it has been determined that all the attribute names are unique. |
|
* |
|
* @param name the attribute name |
|
* @param type the attribute type |
|
* @param value the attribute value |
|
* |
|
* @see #setNonNormalizedValue |
|
* @see #setSpecified |
|
* @see #checkDuplicatesNS |
|
*/ |
|
public void addAttributeNS(QName name, String type, String value) { |
|
int index = fLength; |
|
if (fLength++ == fAttributes.length) { |
|
Attribute[] attributes; |
|
if (fLength < SIZE_LIMIT) { |
|
attributes = new Attribute[fAttributes.length + 4]; |
|
} |
|
else { |
|
attributes = new Attribute[fAttributes.length << 1]; |
|
} |
|
System.arraycopy(fAttributes, 0, attributes, 0, fAttributes.length); |
|
for (int i = fAttributes.length; i < attributes.length; i++) { |
|
attributes[i] = new Attribute(); |
|
} |
|
fAttributes = attributes; |
|
} |
|
// set values |
|
Attribute attribute = fAttributes[index]; |
|
attribute.name.setValues(name); |
|
attribute.type = type; |
|
attribute.value = value; |
|
attribute.nonNormalizedValue = value; |
|
attribute.specified = false; |
|
// clear augmentations |
|
attribute.augs.removeAllItems(); |
|
} |
|
/** |
|
* Checks for duplicate expanded names (local part and namespace name |
|
* pairs) in the attribute specification. If a duplicate is found its |
|
* name is returned. |
|
* <p> |
|
* This should be called once all the in-scope namespaces for the element |
|
* enclosing these attributes is known, and after all the attributes |
|
* have gone through namespace binding. |
|
* |
|
* @return the name of a duplicate attribute found in the search, |
|
* otherwise null. |
|
*/ |
|
public QName checkDuplicatesNS() { |
|
// If the list is small check for duplicates using pairwise comparison. |
|
final int length = fLength; |
|
if (length <= SIZE_LIMIT) { |
|
final Attribute[] attributes = fAttributes; |
|
for (int i = 0; i < length - 1; ++i) { |
|
Attribute att1 = attributes[i]; |
|
for (int j = i + 1; j < length; ++j) { |
|
Attribute att2 = attributes[j]; |
|
if (att1.name.localpart == att2.name.localpart && |
|
att1.name.uri == att2.name.uri) { |
|
return att2.name; |
|
} |
|
} |
|
} |
|
return null; |
|
} |
|
// If the list is large check duplicates using a hash table. |
|
else { |
|
return checkManyDuplicatesNS(); |
|
} |
|
} |
|
private QName checkManyDuplicatesNS() { |
|
// We don't want this table view to be read if someone calls |
|
// addAttribute so we invalidate it up front. |
|
fIsTableViewConsistent = false; |
|
prepareTableView(); |
|
Attribute attr; |
|
int bucket; |
|
final int length = fLength; |
|
final Attribute[] attributes = fAttributes; |
|
final Attribute[] attributeTableView = fAttributeTableView; |
|
final int[] attributeTableViewChainState = fAttributeTableViewChainState; |
|
int largeCount = fLargeCount; |
|
for (int i = 0; i < length; ++i) { |
|
attr = attributes[i]; |
|
bucket = getTableViewBucket(attr.name.localpart, attr.name.uri); |
|
// The chain is stale. |
|
// This must be a unique attribute. |
|
if (attributeTableViewChainState[bucket] != largeCount) { |
|
attributeTableViewChainState[bucket] = largeCount; |
|
attr.next = null; |
|
attributeTableView[bucket] = attr; |
|
} |
|
// This chain is active. |
|
// We need to check if any of the attributes has the same name. |
|
else { |
|
// Search the table. |
|
int collisionCount = 0; |
|
Attribute found = attributeTableView[bucket]; |
|
while (found != null) { |
|
if (found.name.localpart == attr.name.localpart && |
|
found.name.uri == attr.name.uri) { |
|
return attr.name; |
|
} |
|
found = found.next; |
|
++collisionCount; |
|
} |
|
// Select a new hash function and rehash the table view |
|
// if the collision threshold is exceeded. |
|
if (collisionCount >= MAX_HASH_COLLISIONS) { |
|
// The current attribute will be processed in the rehash. |
|
rebalanceTableViewNS(i+1); |
|
largeCount = fLargeCount; |
|
} |
|
else { |
|
// Update table view |
|
attr.next = attributeTableView[bucket]; |
|
attributeTableView[bucket] = attr; |
|
} |
|
} |
|
} |
|
return null; |
|
} |
|
/** |
|
* Look up the index of an attribute by Namespace name. |
|
* <p> |
|
* <strong>Note:</strong> |
|
* This method uses reference comparison, and thus should |
|
* only be used internally. We cannot use this method in any |
|
* code exposed to users as they may not pass in unique strings. |
|
* |
|
* @param uri The Namespace URI, or null if |
|
* the name has no Namespace URI. |
|
* @param localName The attribute's local name. |
|
* @return The index of the attribute, or -1 if it does not |
|
* appear in the list. |
|
*/ |
|
public int getIndexFast(String uri, String localPart) { |
|
for (int i = 0; i < fLength; ++i) { |
|
Attribute attribute = fAttributes[i]; |
|
if (attribute.name.localpart == localPart && |
|
attribute.name.uri == uri) { |
|
return i; |
|
} |
|
} |
|
return -1; |
|
} // getIndexFast(String,String):int |
|
/** |
|
* Returns the value passed in or NMTOKEN if it's an enumerated type. |
|
* |
|
* @param type attribute type |
|
* @return the value passed in or NMTOKEN if it's an enumerated type. |
|
*/ |
|
private String getReportableType(String type) { |
|
if (type.charAt(0) == '(') { |
|
return "NMTOKEN"; |
|
} |
|
return type; |
|
} |
|
/** |
|
* Returns the position in the table view |
|
* where the given attribute name would be hashed. |
|
* |
|
* @param qname the attribute name |
|
* @return the position in the table view where the given attribute |
|
* would be hashed |
|
*/ |
|
protected int getTableViewBucket(String qname) { |
|
return (hash(qname) & 0x7FFFFFFF) % fTableViewBuckets; |
|
} |
|
/** |
|
* Returns the position in the table view |
|
* where the given attribute name would be hashed. |
|
* |
|
* @param localpart the local part of the attribute |
|
* @param uri the namespace name of the attribute |
|
* @return the position in the table view where the given attribute |
|
* would be hashed |
|
*/ |
|
protected int getTableViewBucket(String localpart, String uri) { |
|
if (uri == null) { |
|
return (hash(localpart) & 0x7FFFFFFF) % fTableViewBuckets; |
|
} |
|
else { |
|
return (hash(localpart, uri) & 0x7FFFFFFF) % fTableViewBuckets; |
|
} |
|
} |
|
private int hash(String localpart) { |
|
if (fHashMultipliers == null) { |
|
return localpart.hashCode(); |
|
} |
|
return hash0(localpart); |
|
} // hash(String):int |
|
private int hash(String localpart, String uri) { |
|
if (fHashMultipliers == null) { |
|
return localpart.hashCode() + uri.hashCode() * 31; |
|
} |
|
return hash0(localpart) + hash0(uri) * fHashMultipliers[MULTIPLIERS_SIZE]; |
|
} // hash(String,String):int |
|
private int hash0(String symbol) { |
|
int code = 0; |
|
final int length = symbol.length(); |
|
final int[] multipliers = fHashMultipliers; |
|
for (int i = 0; i < length; ++i) { |
|
code = code * multipliers[i & MULTIPLIERS_MASK] + symbol.charAt(i); |
|
} |
|
return code; |
|
} // hash0(String):int |
|
/** |
|
* Purges all elements from the table view. |
|
*/ |
|
protected void cleanTableView() { |
|
if (++fLargeCount < 0) { |
|
// Overflow. We actually need to visit the chain state array. |
|
if (fAttributeTableViewChainState != null) { |
|
for (int i = fTableViewBuckets - 1; i >= 0; --i) { |
|
fAttributeTableViewChainState[i] = 0; |
|
} |
|
} |
|
fLargeCount = 1; |
|
} |
|
} |
|
/** |
|
* Increases the capacity of the table view. |
|
*/ |
|
private void growTableView() { |
|
final int length = fLength; |
|
int tableViewBuckets = fTableViewBuckets; |
|
do { |
|
tableViewBuckets = (tableViewBuckets << 1) + 1; |
|
if (tableViewBuckets < 0) { |
|
tableViewBuckets = Integer.MAX_VALUE; |
|
break; |
|
} |
|
} |
|
while (length > tableViewBuckets); |
|
fTableViewBuckets = tableViewBuckets; |
|
fAttributeTableView = null; |
|
fLargeCount = 1; |
|
} |
|
/** |
|
* Prepares the table view of the attributes list for use. |
|
*/ |
|
protected void prepareTableView() { |
|
if (fLength > fTableViewBuckets) { |
|
growTableView(); |
|
} |
|
if (fAttributeTableView == null) { |
|
fAttributeTableView = new Attribute[fTableViewBuckets]; |
|
fAttributeTableViewChainState = new int[fTableViewBuckets]; |
|
} |
|
else { |
|
cleanTableView(); |
|
} |
|
} |
|
/** |
|
* Prepares the table view of the attributes list for use, |
|
* and populates it with the attributes which have been |
|
* previously read. |
|
*/ |
|
protected void prepareAndPopulateTableView() { |
|
prepareAndPopulateTableView(fLength); |
|
} |
|
private void prepareAndPopulateTableView(final int count) { |
|
prepareTableView(); |
|
// Need to populate the hash table with the attributes we've processed so far. |
|
Attribute attr; |
|
int bucket; |
|
for (int i = 0; i < count; ++i) { |
|
attr = fAttributes[i]; |
|
bucket = getTableViewBucket(attr.name.rawname); |
|
if (fAttributeTableViewChainState[bucket] != fLargeCount) { |
|
fAttributeTableViewChainState[bucket] = fLargeCount; |
|
attr.next = null; |
|
fAttributeTableView[bucket] = attr; |
|
} |
|
else { |
|
// Update table view |
|
attr.next = fAttributeTableView[bucket]; |
|
fAttributeTableView[bucket] = attr; |
|
} |
|
} |
|
} |
|
/** |
|
* Returns the prefix of the attribute at the specified index. |
|
* |
|
* @param index The index of the attribute. |
|
*/ |
|
public String getPrefix(int index) { |
|
if (index < 0 || index >= fLength) { |
|
return null; |
|
} |
|
String prefix = fAttributes[index].name.prefix; |
|
// REVISIT: The empty string is not entered in the symbol table! |
|
return prefix != null ? prefix : ""; |
|
} // getPrefix(int):String |
|
/** |
|
* Look up an attribute's Namespace URI by index. |
|
* |
|
* @param index The attribute index (zero-based). |
|
* @return The Namespace URI |
|
* @see #getLength |
|
*/ |
|
public String getURI(int index) { |
|
if (index < 0 || index >= fLength) { |
|
return null; |
|
} |
|
String uri = fAttributes[index].name.uri; |
|
return uri; |
|
} // getURI(int):String |
|
/** |
|
* Look up an attribute's value by Namespace name and |
|
* Local name. If Namespace is null, ignore namespace |
|
* comparison. If Namespace is "", treat the name as |
|
* having no Namespace URI. |
|
* |
|
* <p>See {@link #getValue(int) getValue(int)} for a description |
|
* of the possible values.</p> |
|
* |
|
* @param uri The Namespace URI, or null namespaces are ignored. |
|
* @param localName The local name of the attribute. |
|
* @return The attribute value as a string, or null if the |
|
* attribute is not in the list. |
|
*/ |
|
public String getValue(String uri, String localName) { |
|
int index = getIndex(uri, localName); |
|
return index != -1 ? getValue(index) : null; |
|
} // getValue(String,String):String |
|
/** |
|
* Look up an augmentations by Namespace name. |
|
* |
|
* @param uri The Namespace URI, or null if the |
|
* @param localName The local name of the attribute. |
|
* @return Augmentations |
|
*/ |
|
public Augmentations getAugmentations (String uri, String localName) { |
|
int index = getIndex(uri, localName); |
|
return index != -1 ? fAttributes[index].augs : null; |
|
} |
|
/** |
|
* Look up an augmentation by XML 1.0 qualified name. |
|
* <p> |
|
* |
|
* @param qName The XML 1.0 qualified name. |
|
* |
|
* @return Augmentations |
|
* |
|
*/ |
|
public Augmentations getAugmentations(String qName){ |
|
int index = getIndex(qName); |
|
return index != -1 ? fAttributes[index].augs : null; |
|
} |
|
/** |
|
* Look up an augmentations by attributes index. |
|
* |
|
* @param attributeIndex The attribute index. |
|
* @return Augmentations |
|
*/ |
|
public Augmentations getAugmentations (int attributeIndex){ |
|
if (attributeIndex < 0 || attributeIndex >= fLength) { |
|
return null; |
|
} |
|
return fAttributes[attributeIndex].augs; |
|
} |
|
/** |
|
* Sets the augmentations of the attribute at the specified index. |
|
* |
|
* @param attrIndex The attribute index. |
|
* @param augs The augmentations. |
|
*/ |
|
public void setAugmentations(int attrIndex, Augmentations augs) { |
|
fAttributes[attrIndex].augs = augs; |
|
} |
|
/** |
|
* Sets the uri of the attribute at the specified index. |
|
* |
|
* @param attrIndex The attribute index. |
|
* @param uri Namespace uri |
|
*/ |
|
public void setURI(int attrIndex, String uri) { |
|
fAttributes[attrIndex].name.uri = uri; |
|
} // getURI(int,QName) |
|
// Implementation methods |
|
//XMLBufferListener methods |
|
/** |
|
* This method will be invoked by XMLEntityReader before ScannedEntities buffer |
|
* is reloaded. |
|
*/ |
|
public void refresh() { |
|
if(fLength > 0){ |
|
for(int i = 0 ; i < fLength ; i++){ |
|
getValue(i); |
|
} |
|
} |
|
} |
|
public void refresh(int pos) { |
|
} |
|
private void prepareAndPopulateTableViewNS(final int count) { |
|
prepareTableView(); |
|
// Need to populate the hash table with the attributes we've processed so far. |
|
Attribute attr; |
|
int bucket; |
|
for (int i = 0; i < count; ++i) { |
|
attr = fAttributes[i]; |
|
bucket = getTableViewBucket(attr.name.localpart, attr.name.uri); |
|
if (fAttributeTableViewChainState[bucket] != fLargeCount) { |
|
fAttributeTableViewChainState[bucket] = fLargeCount; |
|
attr.next = null; |
|
fAttributeTableView[bucket] = attr; |
|
} |
|
else { |
|
// Update table view |
|
attr.next = fAttributeTableView[bucket]; |
|
fAttributeTableView[bucket] = attr; |
|
} |
|
} |
|
} |
|
/** |
|
* Randomly selects a new hash function and reorganizes the table view |
|
* in order to more evenly distribute its entries. This method is called |
|
* automatically when the number of attributes in one bucket exceeds |
|
* MAX_HASH_COLLISIONS. |
|
*/ |
|
private void rebalanceTableView(final int count) { |
|
if (fHashMultipliers == null) { |
|
fHashMultipliers = new int[MULTIPLIERS_SIZE + 1]; |
|
} |
|
PrimeNumberSequenceGenerator.generateSequence(fHashMultipliers); |
|
prepareAndPopulateTableView(count); |
|
} |
|
/** |
|
* Randomly selects a new hash function and reorganizes the table view |
|
* in order to more evenly distribute its entries. This method is called |
|
* automatically when the number of attributes in one bucket exceeds |
|
* MAX_HASH_COLLISIONS. |
|
*/ |
|
private void rebalanceTableViewNS(final int count) { |
|
if (fHashMultipliers == null) { |
|
fHashMultipliers = new int[MULTIPLIERS_SIZE + 1]; |
|
} |
|
PrimeNumberSequenceGenerator.generateSequence(fHashMultipliers); |
|
prepareAndPopulateTableViewNS(count); |
|
} |
|
// |
|
// Classes |
|
// |
|
/** |
|
* Attribute information. |
|
* |
|
* @author Andy Clark, IBM |
|
*/ |
|
static class Attribute { |
|
// |
|
// Data |
|
// |
|
// basic info |
|
/** Name. */ |
|
public final QName name = new QName(); |
|
/** Type. */ |
|
public String type; |
|
/** Value. */ |
|
public String value; |
|
/** This will point to the ScannedEntities buffer.*/ |
|
public XMLString xmlValue; |
|
/** Non-normalized value. */ |
|
public String nonNormalizedValue; |
|
/** Specified. */ |
|
public boolean specified; |
|
/** |
|
* Augmentations information for this attribute. |
|
* XMLAttributes has no knowledge if any augmentations |
|
* were attached to Augmentations. |
|
*/ |
|
public Augmentations augs = new AugmentationsImpl(); |
|
// Additional data for attribute table view |
|
/** Pointer to the next attribute in the chain. **/ |
|
public Attribute next; |
|
} // class Attribute |
|
} // class XMLAttributesImpl |