blob: a02d54a861332d51b2851d79d899f1e226e3ed48 [file] [log] [blame]
/*
* 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.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;
import com.sun.xml.internal.stream.XMLBufferListener;
/**
* 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
*
* @version $Id: XMLAttributesImpl.java,v 1.7 2010/05/07 20:13:09 joehw Exp $
*/
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.equals("")
? 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
public void setSchemaId(int attrIndex, boolean schemaId) {
fAttributes[attrIndex].schemaId = schemaId;
}
public boolean getSchemaId(int index) {
if (index < 0 || index >= fLength) {
return false;
}
return fAttributes[index].schemaId;
}
public boolean getSchemaId(String qname) {
int index = getIndex(qname);
return index != -1 ? fAttributes[index].schemaId : false;
} // getType(String):String
public boolean getSchemaId(String uri, String localName) {
if (!fNamespaces) {
return false;
}
int index = getIndex(uri, localName);
return index != -1 ? fAttributes[index].schemaId : false;
} // getType(String,String):String
//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 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;
/** Schema ID type. */
public boolean schemaId;
/**
* 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