All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ctc.wstx.sr.AttributeCollector Maven / Gradle / Ivy

There is a newer version: 0.10.0
Show newest version
/* Woodstox XML processor
 *
 * Copyright (c) 2004- Tatu Saloranta, [email protected]
 *
 * Licensed under the License specified in file LICENSE, included with
 * the source code.
 * You may not use this file except in compliance with the License.
 *
 * 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.ctc.wstx.sr;

import java.io.IOException;
import java.util.Arrays;

import javax.xml.XMLConstants;
import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import javax.xml.namespace.QName;

import org.codehaus.stax2.ri.typed.CharArrayBase64Decoder;
import org.codehaus.stax2.ri.typed.ValueDecoderFactory;
import org.codehaus.stax2.typed.Base64Variant;
import org.codehaus.stax2.typed.TypedArrayDecoder;
import org.codehaus.stax2.typed.TypedValueDecoder;
import org.codehaus.stax2.typed.TypedXMLStreamException;
import org.codehaus.stax2.validation.XMLValidator;

import com.ctc.wstx.api.ReaderConfig;
import com.ctc.wstx.cfg.ErrorConsts;
import com.ctc.wstx.sw.XmlWriter;
import com.ctc.wstx.util.*;

/**
 * Shared base class that defines API stream reader uses to communicate
 * with the attribute collector implementation, independent of whether it's
 * operating in namespace-aware or non-namespace modes.
 * Collector class is used to build up attribute lists; for the most part
 * will just hold references to few specialized {@link TextBuilder}s that
 * are used to create efficient semi-shared value Strings.
 */
public final class AttributeCollector
{
    final static int INT_SPACE = 0x0020;

    /**
     * Threshold value that indicates minimum length for lists instances
     * that need a Map structure, for fast attribute access by fully-qualified
     * name.
     */
    protected final static int LONG_ATTR_LIST_LEN = 4;

    /**
     * Expected typical maximum number of attributes for any element;
     * chosen to minimize need to resize, while trying not to waste space.
     * Dynamically grown; better not to set too high to avoid excessive
     * overhead for small attribute-less documents.
     */
    protected final static int EXP_ATTR_COUNT = 12;

    protected final static int EXP_NS_COUNT = 6;

    /**
     * This value is used to indicate that we shouldn't keep track
     * of index of xml:id attribute -- generally done when Xml:id
     * support is disabled
     */
    protected final static int XMLID_IX_DISABLED = -2;

    protected final static int XMLID_IX_NONE = -1;

    protected final static InternCache sInternCache = InternCache.getInstance();

    /*
    ///////////////////////////////////////////////////////////
    // Configuration
    ///////////////////////////////////////////////////////////
     */

    // // Settings for matching Xml:id attribute

    final String mXmlIdPrefix;
    final String mXmlIdLocalName;

    /*
    ///////////////////////////////////////////////////////////
    // Collected attribute (incl namespace attrs) information:
    ///////////////////////////////////////////////////////////
     */

    /**
     * Array of attributes collected for this element.
     */
    protected Attribute[] mAttributes;

    /**
     * Actual number of attributes collected, including attributes
     * added via default values.
     */
    protected int mAttrCount;

    /**
     * Number of attribute values actually parsed, not including
     * ones created via default value expansion. Equal to or less than
     * {@link #mAttrCount}.
     */
    protected int mNonDefCount;

    /**
     * Array of namespace declaration attributes collected for this element;
     * not used in non-namespace-aware mode
     */
    protected Attribute[] mNamespaces;

    /**
     * Number of valid namespace declarations in {@link #mNamespaces}.
     */
    protected int mNsCount;

    /**
     * Flag to indicate whether the default namespace has already been declared
     * for the current element.
     */
    protected boolean mDefaultNsDeclared = false;

    /**
     * Index of "xml:id" attribute, if one exists for the current
     * element; {@link #XMLID_IX_NONE} if none.
     */
    protected int mXmlIdAttrIndex;

    /*
    ///////////////////////////////////////////////////////////
    // Attribute (and ns) value builders
    ///////////////////////////////////////////////////////////
     */

    /**
     * TextBuilder into which values of all attributes are appended
     * to, including default valued ones (defaults are added after
     * explicit ones).
     * Constructed lazily, if and when needed (not needed
     * for short attribute-less docs)
     */
    protected TextBuilder mValueBuilder = null;

    /**
     * TextBuilder into which values of namespace URIs are added (including
     * URI for the default namespace, if one defined).
     */
    private final TextBuilder mNamespaceBuilder = new TextBuilder(EXP_NS_COUNT);

    /*
    //////////////////////////////////////////////////////////////
    // Information that defines "Map-like" data structure used for
    // quick access to attribute values by fully-qualified name
    //////////////////////////////////////////////////////////////
     */

    /**
     * Encoding of a data structure that contains mapping from
     * attribute names to attribute index in main attribute name arrays.
     *

* Data structure contains two separate areas; main hash area (with * size mAttrHashSize), and remaining spillover area * that follows hash area up until (but not including) * mAttrSpillEnd index. * Main hash area only contains indexes (index+1; 0 signifying empty slot) * to actual attributes; spillover area has both hash and index for * any spilled entry. Spilled entries are simply stored in order * added, and need to be searched using linear search. In case of both * primary hash hits and spills, eventual comparison with the local * name needs to be done with actual name array. */ protected int[] mAttrMap = null; /** * Size of hash area in mAttrMap; generally at least 20% * more than number of attributes (mAttrCount). */ protected int mAttrHashSize; /** * Pointer to int slot right after last spill entr, in * mAttrMap array. */ protected int mAttrSpillEnd; protected int mMaxAttributesPerElement; protected int mMaxAttributeSize; /* /////////////////////////////////////////////// // Life-cycle: /////////////////////////////////////////////// */ protected AttributeCollector(ReaderConfig cfg, boolean nsAware) { mXmlIdAttrIndex = cfg.willDoXmlIdTyping() ? XMLID_IX_NONE : XMLID_IX_DISABLED; if (nsAware) { mXmlIdPrefix = "xml"; mXmlIdLocalName = "id"; } else { mXmlIdPrefix = null; mXmlIdLocalName = "xml:id"; } mMaxAttributesPerElement = cfg.getMaxAttributesPerElement(); mMaxAttributeSize = cfg.getMaxAttributeSize(); } /** * Method called to allow reusing of collector, usually right before * starting collecting attributes for a new start tag. */ /** * Method called to allow reusing of collector, usually right before * starting collecting attributes for a new start tag. *

* Note: public only so that it can be called by unit tests. */ public void reset() { if (mNsCount > 0) { mNamespaceBuilder.reset(); mDefaultNsDeclared = false; mNsCount = 0; } /* No need to clear attr name, or NS prefix Strings; they are * canonicalized and will be referenced by symbol table in any * case... so we can save trouble of cleaning them up. This Object * will get GC'ed soon enough, after parser itself gets disposed of. */ if (mAttrCount > 0) { mValueBuilder.reset(); mAttrCount = 0; if (mXmlIdAttrIndex >= 0) { mXmlIdAttrIndex = XMLID_IX_NONE; } } /* Note: attribute values will be cleared later on, when validating * namespaces. This so that we know how much to clean up; and * occasionally can also just avoid clean up (when resizing) */ } /** * Method that can be called to force space normalization (remove * leading/trailing spaces, replace non-spaces white space with * spaces, collapse spaces to one) on specified attribute. * Currently called by {@link InputElementStack} to force * normalization of Xml:id attribute */ public void normalizeSpacesInValue(int index) { // StringUtil has a method, but it works on char arrays... char[] attrCB = mValueBuilder.getCharBuffer(); String normValue = StringUtil.normalizeSpaces (attrCB, getValueStartOffset(index), getValueStartOffset(index+1)); if (normValue != null) { mAttributes[index].setValue(normValue); } } /* /////////////////////////////////////////////// // Public accesors (for stream reader) /////////////////////////////////////////////// */ /** * @return Number of namespace declarations collected, including * possible default namespace declaration */ protected int getNsCount() { return mNsCount; } public boolean hasDefaultNs() { return mDefaultNsDeclared; } // // // Direct access to attribute/NS prefixes/localnames/URI public final int getCount() { return mAttrCount; } /** * @return Number of attributes that were explicitly specified; may * be less than the total count due to attributes created using * attribute default values */ public int getSpecifiedCount() { return mNonDefCount; } public String getNsPrefix(int index) { if (index < 0 || index >= mNsCount) { throwIndex(index); } // for NS decls, local name is stored in prefix return mNamespaces[index].mLocalName; } public String getNsURI(int index) { if (index < 0 || index >= mNsCount) { throwIndex(index); } return mNamespaces[index].mNamespaceURI; } // // // Direct access to attribute/NS prefixes/localnames/URI public String getPrefix(int index) { if (index < 0 || index >= mAttrCount) { throwIndex(index); } return mAttributes[index].mPrefix; } public String getLocalName(int index) { if (index < 0 || index >= mAttrCount) { throwIndex(index); } return mAttributes[index].mLocalName; } public String getURI(int index) { if (index < 0 || index >= mAttrCount) { throwIndex(index); } return mAttributes[index].mNamespaceURI; } public QName getQName(int index) { if (index < 0 || index >= mAttrCount) { throwIndex(index); } return mAttributes[index].getQName(); } /** *

* Note: the main reason this method is defined at this level, and * made final, is performance. JIT may be able to fully inline this * method, even when reference is via this base class. This is important * since this is likely to be the most often called method of the * collector instances. */ public final String getValue(int index) { if (index < 0 || index >= mAttrCount) { throwIndex(index); } String full = mValueBuilder.getAllValues(); Attribute attr = mAttributes[index]; ++index; if (index < mAttrCount) { // not last int endOffset = mAttributes[index].mValueStartOffset; return attr.getValue(full, endOffset); } // last can be optimized bit more: return attr.getValue(full); } public String getValue(String nsURI, String localName) { // Primary hit? int hashSize = mAttrHashSize; if (hashSize == 0) { // sanity check, for 'no attributes' return null; } int hash = localName.hashCode(); if (nsURI != null) { if (nsURI.length() == 0) { nsURI = null; } else { hash ^= nsURI.hashCode(); } } int ix = mAttrMap[hash & (hashSize-1)]; if (ix == 0) { // nothing in here; no spills either return null; } --ix; // Is primary candidate match? if (mAttributes[ix].hasQName(nsURI, localName)) { return getValue(ix); } /* Nope, need to traverse spill list, which has 2 entries for * each spilled attribute id; first for hash value, second index. */ for (int i = hashSize, len = mAttrSpillEnd; i < len; i += 2) { if (mAttrMap[i] != hash) { continue; } /* Note: spill indexes are not off-by-one, since there's no need * to mask 0 */ ix = mAttrMap[i+1]; if (mAttributes[ix].hasQName(nsURI, localName)) { return getValue(ix); } } return null; } public int getMaxAttributesPerElement() { return mMaxAttributesPerElement; } public void setMaxAttributesPerElement(int maxAttributesPerElement) { this.mMaxAttributesPerElement = maxAttributesPerElement; } public int findIndex(String localName) { return findIndex(null, localName); } public int findIndex(String nsURI, String localName) { /* Note: most of the code is from getValue().. could refactor * code, performance is bit of concern (one more method call * if index access was separate). * See comments on that method, for logics. */ // Primary hit? int hashSize = mAttrHashSize; if (hashSize == 0) { // sanity check, for 'no attributes' return -1; } int hash = localName.hashCode(); if (nsURI != null) { if (nsURI.length() == 0) { nsURI = null; } else { hash ^= nsURI.hashCode(); } } int ix = mAttrMap[hash & (hashSize-1)]; if (ix == 0) { // nothing in here; no spills either return -1; } --ix; // Is primary candidate match? if (mAttributes[ix].hasQName(nsURI, localName)) { return ix; } /* Nope, need to traverse spill list, which has 2 entries for * each spilled attribute id; first for hash value, second index. */ for (int i = hashSize, len = mAttrSpillEnd; i < len; i += 2) { if (mAttrMap[i] != hash) { continue; } /* Note: spill indexes are not off-by-one, since there's no need * to mask 0 */ ix = mAttrMap[i+1]; if (mAttributes[ix].hasQName(nsURI, localName)) { return ix; } } return -1; } public final boolean isSpecified(int index) { return (index < mNonDefCount); } public final int getXmlIdAttrIndex() { return mXmlIdAttrIndex; } /* ////////////////////////////////////////////////////// // Type-safe accessors to support TypedXMLStreamReader ////////////////////////////////////////////////////// */ /** * Method called to decode the whole attribute value as a single * typed value. * Decoding is done using the decoder provided. */ public final void decodeValue(int index, TypedValueDecoder tvd) throws IllegalArgumentException { if (index < 0 || index >= mAttrCount) { throwIndex(index); } /* Should be faster to pass the char array even if we might * have a String */ // Either way, need to trim before passing: char[] buf = mValueBuilder.getCharBuffer(); int start = mAttributes[index].mValueStartOffset; int end = getValueStartOffset(index+1); while (true) { if (start >= end) { tvd.handleEmptyValue(); return; } if (!StringUtil.isSpace(buf[start])) { break; } ++start; } // Trailing space? while (--end > start && StringUtil.isSpace(buf[end])) { } tvd.decode(buf, start, end+1); } /** * Method called to decode the attribute value that consists of * zero or more space-separated tokens. * Decoding is done using the decoder provided. * @return Number of tokens decoded */ public final int decodeValues(int index, TypedArrayDecoder tad, InputProblemReporter rep) throws XMLStreamException { if (index < 0 || index >= mAttrCount) { throwIndex(index); } // Char[] faster than String... and no need to trim here: return decodeValues(tad, rep, mValueBuilder.getCharBuffer(), mAttributes[index].mValueStartOffset, getValueStartOffset(index+1)); } public final byte[] decodeBinary(int index, Base64Variant v, CharArrayBase64Decoder dec, InputProblemReporter rep) throws XMLStreamException { if (index < 0 || index >= mAttrCount) { throwIndex(index); } /* No point in trying to use String representation, even if one * available, faster to process from char[] */ Attribute attr = mAttributes[index]; char[] cbuf = mValueBuilder.getCharBuffer(); int start = attr.mValueStartOffset; int end = getValueStartOffset(index+1); int len = end-start; dec.init(v, true, cbuf, start, len, null); try { return dec.decodeCompletely(); } catch (IllegalArgumentException iae) { // Need to convert to a checked stream exception String lexical = new String(cbuf, start, len); throw new TypedXMLStreamException(lexical, iae.getMessage(), rep.getLocation(), iae); } } private final static int decodeValues(TypedArrayDecoder tad, InputProblemReporter rep, final char[] buf, int ptr, final int end) throws XMLStreamException { int start = ptr; int count = 0; try { decode_loop: while (ptr < end) { // First, any space to skip? while (buf[ptr] <= INT_SPACE) { if (++ptr >= end) { break decode_loop; } } // Then let's figure out non-space char (token) start = ptr; ++ptr; while (ptr < end && buf[ptr] > INT_SPACE) { ++ptr; } int tokenEnd = ptr; ++ptr; // to skip trailing space (or, beyond end) // Ok, decode... any more room? ++count; if (tad.decodeValue(buf, start, tokenEnd)) { if (!checkExpand(tad)) { break; } } } } catch (IllegalArgumentException iae) { // Need to convert to a checked stream exception Location loc = rep.getLocation(); String lexical = new String(buf, start, (ptr-start)); throw new TypedXMLStreamException(lexical, iae.getMessage(), loc, iae); } return count; } /** * Internal method used to see if we can expand the buffer that * the array decoder has. Bit messy, but simpler than having * separately typed instances; and called rarely so that performance * downside of instanceof is irrelevant. */ private final static boolean checkExpand(TypedArrayDecoder tad) { if (tad instanceof ValueDecoderFactory.BaseArrayDecoder) { ((ValueDecoderFactory.BaseArrayDecoder) tad).expand(); return true; } return false; } /* /////////////////////////////////////////////// // Accessors for accessing helper objects /////////////////////////////////////////////// */ /** * Method for getting start pointer within shared value buffer * for given attribute. It is also the same as end pointer * for preceding attribute, if any. */ protected int getValueStartOffset(int index) { if (index < mAttrCount) { return mAttributes[index].mValueStartOffset; } return mValueBuilder.getCharSize(); } protected char[] getSharedValueBuffer() { return mValueBuilder.getCharBuffer(); } /** * Method called to resolve and initialize specified collected * namespace declaration * * @return Attribute that contains specified namespace declaration */ protected Attribute resolveNamespaceDecl(int index, boolean internURI) { Attribute ns = mNamespaces[index]; String full = mNamespaceBuilder.getAllValues(); String uri; if (mNsCount == 0) { uri = full; } else { ++index; if (index < mNsCount) { // not last int endOffset = mNamespaces[index].mValueStartOffset; uri = ns.getValue(full, endOffset); } else { // is last uri = ns.getValue(full); } } if (internURI && uri.length() > 0) { uri = sInternCache.intern(uri); } ns.mNamespaceURI = uri; return ns; } /** * Method needed by event creating code, to build a non-transient * attribute container, to use with XMLEvent objects (specifically * implementation of StartElement event). */ public ElemAttrs buildAttrOb() { int count = mAttrCount; if (count == 0) { return null; } /* If we have actual attributes, let's first just create the * raw array that has all attribute information: */ String[] raw = new String[count << 2]; for (int i = 0; i < count; ++i) { Attribute attr = mAttributes[i]; int ix = (i << 2); raw[ix] = attr.mLocalName; raw[ix+1] = attr.mNamespaceURI; raw[ix+2] = attr.mPrefix; raw[ix+3] = getValue(i); } // Do we have a "short" list? if (count < LONG_ATTR_LIST_LEN) { return new ElemAttrs(raw, mNonDefCount); } // Ok, nope; we need to also pass the Map information... /* 02-Feb-2009, TSa: Must make a copy of the Map array now, * otherwise could get overwritten. */ int amapLen = mAttrMap.length; int[] amap = new int[amapLen]; // TODO: JDK 1.6 has Arrays.copyOf(), should use with Woodstox 6 System.arraycopy(mAttrMap, 0, amap, 0, amapLen); return new ElemAttrs(raw, mNonDefCount, amap, mAttrHashSize, mAttrSpillEnd); } protected void validateAttribute(int index, XMLValidator vld) throws XMLStreamException { Attribute attr = mAttributes[index]; String normValue = vld.validateAttribute (attr.mLocalName, attr.mNamespaceURI, attr.mPrefix, mValueBuilder.getCharBuffer(), getValueStartOffset(index), getValueStartOffset(index+1)); if (normValue != null) { attr.setValue(normValue); } } /* /////////////////////////////////////////////// // Attribute, namespace decl building /////////////////////////////////////////////// */ /** * Low-level accessor method that attribute validation code may call * for certain types of attributes; generally only for id and idref/idrefs * attributes. It returns the underlying 'raw' attribute value buffer * for direct access. */ public final TextBuilder getAttrBuilder(String attrPrefix, String attrLocalName) throws XMLStreamException { /* Ok: we have parsed prefixed-name of a regular * attribute. So let's initialize the instance... */ if (mAttrCount == 0) { if (mAttributes == null) { allocBuffers(); } mAttributes[0] = new Attribute(attrPrefix, attrLocalName, 0); } else { int valueStart = mValueBuilder.getCharSize(); if (mAttrCount >= mAttributes.length) { if ((mAttrCount + mNsCount) >= mMaxAttributesPerElement) { throw new XMLStreamException("Attribute limit ("+mMaxAttributesPerElement+") exceeded"); } mAttributes = (Attribute[]) DataUtil.growArrayBy50Pct(mAttributes); } Attribute curr = mAttributes[mAttrCount]; if (curr == null) { mAttributes[mAttrCount] = new Attribute(attrPrefix, attrLocalName, valueStart); } else { curr.reset(attrPrefix, attrLocalName, valueStart); } } ++mAttrCount; // 25-Sep-2006, TSa: Need to keep track of xml:id attribute? if (attrLocalName == mXmlIdLocalName) { if (attrPrefix == mXmlIdPrefix) { if (mXmlIdAttrIndex != XMLID_IX_DISABLED) { mXmlIdAttrIndex = mAttrCount - 1; } } } /* Can't yet create attribute map by name, since we only know * name prefix, not necessarily matching URI. */ return mValueBuilder; } /** * Method called by validator to insert an attribute that has a default * value and wasn't yet included in collector's attribute set. * * @return Index of the newly added attribute, if added; -1 to indicate * this was a duplicate */ public int addDefaultAttribute(String localName, String uri, String prefix, String value) throws XMLStreamException { int attrIndex = mAttrCount; if (attrIndex < 1) { /* had no explicit attributes... better initialize now, then. * Let's just use hash area of 4, and */ initHashArea(); } /* Ok, first, since we do want to verify that we can not accidentally * add duplicates, let's first try to add entry to Map, since that * will catch dups. */ int hash = localName.hashCode(); if (uri != null && uri.length() > 0) { hash ^= uri.hashCode(); } int index = hash & (mAttrHashSize - 1); int[] map = mAttrMap; if (map[index] == 0) { // whoa, have room... map[index] = attrIndex+1; // add 1 to get 1-based index (0 is empty marker) } else { // nah, collision... int currIndex = map[index]-1; // Index of primary collision entry int spillIndex = mAttrSpillEnd; map = spillAttr(uri, localName, map, currIndex, spillIndex, attrIndex, hash, mAttrHashSize); if (map == null) { // dup! return -1; // could return negation (-(index+1)) of the prev index? } map[++spillIndex] = attrIndex; // no need to specifically avoid 0 mAttrMap = map; mAttrSpillEnd = ++spillIndex; } /* Can reuse code; while we don't really need the builder, * we need to add/reset attribute */ getAttrBuilder(prefix, localName); Attribute attr = mAttributes[mAttrCount-1]; attr.mNamespaceURI = uri; attr.setValue(value); // attribute count has been updated; index is one less than count return (mAttrCount-1); } /** * Low-level mutator method that attribute validation code may call * for certain types of attributes, when it wants to handle the whole * validation and normalization process by itself. It is generally * only called for id and idref/idrefs attributes, as those values * are usually normalized. */ public final void setNormalizedValue(int index, String value) { mAttributes[index].setValue(value); } /** * @return null if the default namespace URI has been already declared * for the current element; TextBuilder to add URI to if not. */ public TextBuilder getDefaultNsBuilder() throws XMLStreamException { if (mDefaultNsDeclared) { return null; } mDefaultNsDeclared = true; return getNsBuilder(null); } /** * @return null if prefix has been already declared; TextBuilder to * add value to if not. */ public TextBuilder getNsBuilder(String prefix) throws XMLStreamException { // first: must verify that it's not a dup if (mNsCount == 0) { if (mNamespaces == null) { mNamespaces = new Attribute[EXP_NS_COUNT]; } mNamespaces[0] = new Attribute(null, prefix, 0); } else { int len = mNsCount; /* Ok: must ensure that there are no duplicate namespace * declarations (ie. decls with same prefix being bound) */ if (prefix != null) { // null == default ns for (int i = 0; i < len; ++i) { // note: for ns decls, bound prefix is in 'local name' if (prefix == mNamespaces[i].mLocalName) { return null; } } } if (len >= mNamespaces.length) { if ((mAttrCount + mNsCount) >= mMaxAttributesPerElement) { throw new XMLStreamException("Attribute limit ("+mMaxAttributesPerElement+") exceeded"); } mNamespaces = (Attribute[]) DataUtil.growArrayBy50Pct(mNamespaces); } int uriStart = mNamespaceBuilder.getCharSize(); Attribute curr = mNamespaces[len]; if (curr == null) { mNamespaces[len] = new Attribute(null, prefix, uriStart); } else { curr.reset(null, prefix, uriStart); } } ++mNsCount; return mNamespaceBuilder; } /** * Method called to resolve namespace URIs from attribute prefixes. *

* Note: public only so that it can be called by unit tests. * * @param rep Reporter to use for reporting well-formedness problems * @param ns Namespace prefix/URI mappings active for this element * * @return Index of xml:id attribute, if any, -1 if not */ public int resolveNamespaces(InputProblemReporter rep, StringVector ns) throws XMLStreamException { int attrCount = mAttrCount; /* Let's now set number of 'real' attributes, to allow figuring * out number of attributes created via default value expansion */ mNonDefCount = attrCount; if (attrCount < 1) { // Checked if doing access by FQN: mAttrHashSize = mAttrSpillEnd = 0; // And let's just bail out, too... return mXmlIdAttrIndex; } for (int i = 0; i < attrCount; ++i) { Attribute attr = mAttributes[i]; String prefix = attr.mPrefix; // Attributes' ns URI is null after reset, so can skip setting "no namespace" if (prefix != null) { if (prefix == "xml") { attr.mNamespaceURI = XMLConstants.XML_NS_URI; } else { String uri = ns.findLastFromMap(prefix); if (uri == null) { rep.throwParseError(ErrorConsts.ERR_NS_UNDECLARED_FOR_ATTR, prefix, attr.mLocalName); } attr.mNamespaceURI = uri; } } } /* Ok, finally, let's create attribute map, to allow efficient * access by prefix+localname combination. Could do it on-demand, * but this way we can check for duplicates right away. */ int[] map = mAttrMap; /* What's minimum size to contain at most 80% full hash area, * plus 1/8 spill area (12.5% spilled entries, two ints each)? */ int hashCount = 4; { int min = attrCount + (attrCount >> 2); // == 80% fill rate /* Need to get 2^N size that can contain all elements, with * 80% fill rate */ while (hashCount < min) { hashCount += hashCount; // 2x } // And then add the spill area mAttrHashSize = hashCount; min = hashCount + (hashCount >> 4); // 12.5 x 2 ints if (map == null || map.length < min) { map = new int[min]; } else { /* Need to clear old hash entries (if any). But note that * spilled entries we can leave alone -- they are just ints, * and get overwritten if and as needed */ Arrays.fill(map, 0, hashCount, 0); } } { int mask = hashCount-1; int spillIndex = hashCount; // Ok, array's fine, let's hash 'em in! for (int i = 0; i < attrCount; ++i) { Attribute attr = mAttributes[i]; String name = attr.mLocalName; int hash = name.hashCode(); String uri = attr.mNamespaceURI; if (uri != null) { hash ^= uri.hashCode(); } int index = hash & mask; // Hash slot available? if (map[index] == 0) { map[index] = i+1; // since 0 is marker } else { int currIndex = map[index]-1; /* nope, need to spill; let's extract most of that code to * a separate method for clarity (and maybe it'll be * easier to inline by JVM too) */ map = spillAttr(uri, name, map, currIndex, spillIndex, attrCount, hash, hashCount); if (map == null) { throwDupAttr(rep, currIndex); // never returns here... } else { // let's use else to keep FindBugs happy map[++spillIndex] = i; // no need to specifically avoid 0 ++spillIndex; } } } mAttrSpillEnd = spillIndex; } mAttrMap = map; return mXmlIdAttrIndex; } /* /////////////////////////////////////////////// // Package/core methods: /////////////////////////////////////////////// */ protected void throwIndex(int index) { throw new IllegalArgumentException("Invalid index "+index+"; current element has only "+getCount()+" attributes"); } /** * Method that basically serializes the specified (read-in) attribute * using Writers provided. Serialization is done by * writing out (fully-qualified) name * of the attribute, followed by the equals sign and quoted value. */ public void writeAttribute(int index, XmlWriter xw) throws IOException, XMLStreamException { // Note: here we assume index checks have been done by caller Attribute attr = mAttributes[index]; String ln = attr.mLocalName; String prefix = attr.mPrefix; if (prefix == null || prefix.length() == 0) { xw.writeAttribute(ln, getValue(index)); } else { xw.writeAttribute(prefix, ln, getValue(index)); } } /** * Method called to initialize buffers that need not be immediately * initialized */ protected final void allocBuffers() { if (mAttributes == null) { mAttributes = new Attribute[8]; } if (mValueBuilder == null) { mValueBuilder = new TextBuilder(EXP_ATTR_COUNT); } } /* /////////////////////////////////////////////// // Internal methods: /////////////////////////////////////////////// */ /** * @return Null, if attribute is a duplicate (to indicate error); * map itself, or resized version, otherwise. */ private int[] spillAttr(String uri, String name, int[] map, int currIndex, int spillIndex, int attrCount, int hash, int hashCount) { // Do we have a dup with primary entry? /* Can do equality comp for local name, as they * are always canonicalized: */ Attribute oldAttr = mAttributes[currIndex]; if (oldAttr.mLocalName == name) { // URIs may or may not be interned though: String currURI = oldAttr.mNamespaceURI; if (currURI == uri || (currURI != null && currURI.equals(uri))) { return null; } } /* Is there room to spill into? (need to 2 int spaces; one for hash, * the other for index) */ if ((spillIndex + 1)>= map.length) { // Let's just add room for 4 spills... map = DataUtil.growArrayBy(map, 8); } // Let's first ensure we aren't adding a dup: for (int j = hashCount; j < spillIndex; j += 2) { if (map[j] == hash) { currIndex = map[j+1]; Attribute attr = mAttributes[currIndex]; if (attr.mLocalName == name) { String currURI = attr.mNamespaceURI; if (currURI == uri || (currURI != null && currURI.equals(uri))) { return null; } } } } map[spillIndex] = hash; return map; } /** * Method called to ensure hash area will be properly set up in * cases where initially no room was needed, but default attribute(s) * is being added. */ private void initHashArea() { /* Let's use small hash area of size 4, and one spill; don't * want too big (need to clear up room), nor too small (only * collisions) */ mAttrHashSize = mAttrSpillEnd = 4; if (mAttrMap == null || mAttrMap.length < mAttrHashSize) { mAttrMap = new int[mAttrHashSize+1]; } mAttrMap[0] = mAttrMap[1] = mAttrMap[2] = mAttrMap[3] = 0; allocBuffers(); } /** * Method that can be used to get the specified attribute value, * by getting it written using Writer passed in. Can potentially * save one String allocation, since no (temporary) Strings need * to be created. */ /* protected final void writeValue(int index, Writer w) throws IOException { mValueBuilder.getEntry(index, w); } */ protected void throwDupAttr(InputProblemReporter rep, int index) throws XMLStreamException { rep.throwParseError("Duplicate attribute '"+getQName(index)+"'."); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy