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

net.sf.saxon.tree.tiny.TinyTree Maven / Gradle / Ivy

There is a newer version: 10.5
Show newest version
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.tree.tiny;

import net.sf.saxon.Configuration;
import net.sf.saxon.event.ReceiverOptions;
import net.sf.saxon.lib.FeatureKeys;
import net.sf.saxon.om.*;
import net.sf.saxon.trans.Err;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.linked.SystemIdMap;
import net.sf.saxon.tree.util.FastStringBuffer;
import net.sf.saxon.type.*;
import net.sf.saxon.value.*;
import net.sf.saxon.value.StringValue;
import net.sf.saxon.z.IntArraySet;

import java.util.ArrayList;
import java.util.Arrays;


/**
 * A data structure to hold the contents of a tree. As the name implies, this implementation
 * of the data model is optimized for size, and for speed of creation: it minimizes the number
 * of Java objects used.
 *
 * 

It can be used to represent a tree that is rooted at a document node, or one that is rooted * at an element node.

*/ public final class TinyTree { /*@NotNull*/ private static final String[] EMPTY_STRING_ARRAY = new String[0]; /*@NotNull*/ private Configuration config; // List of top-level document nodes. /*@NotNull*/ private ArrayList documentList = new ArrayList(5); // The document number (really a tree number: it can identify a non-document root node protected long documentNumber; // the contents of the document protected AppendableCharSequence charBuffer; /*@Nullable*/ protected FastStringBuffer commentBuffer = null; // created when needed protected int numberOfNodes = 0; // excluding attributes and namespaces // The following arrays contain one entry for each node other than attribute // and namespace nodes, arranged in document order. // nodeKind indicates the kind of node, e.g. element, text, or comment public byte[] nodeKind; // depth is the depth of the node in the hierarchy, i.e. the number of ancestors protected short[] depth; // next is the node number of the next sibling // - unless it points backwards, in which case it is the node number of the parent protected int[] next; // alpha holds a value that depends on the node kind. For text nodes, it is the offset // into the text buffer. For comments and processing instructions, it is the offset into // the comment buffer. For elements, it is the index of the first attribute node, or -1 // if this element has no attributes. protected int[] alpha; // beta holds a value that depends on the node kind. For text nodes, it is the length // of the text. For comments and processing instructions, it is the length of the text. // For elements, it is the index of the first namespace node, or -1 // if this element has no namespaces. protected int[] beta; // nameCode holds the name of the node, as an identifier resolved using the name pool protected int[] nameCode; // the prior array indexes preceding-siblings; it is constructed only when required /*@Nullable*/ protected int[] prior = null; // the typeCode array holds type codes for element nodes; it is constructed only // if at least one element has a type other than untyped, or has an IDREF property. // The array holds the type fingerprint, with bit TYPECODE_IDREF set if the value is an IDREF /*@Nullable*/ protected int[] typeCodeArray = null; // the typedValue array holds the typed values of element nodes if the typed value is anything // other than string, untypedAtomic, or anyURI. This means it is only used for schema-validated // documents. It is created lazily when the typed value of a node is first accessed. /*@Nullable*/ protected AtomicSequence[] typedValueArray = null; // boolean switch to disable the typed value caching private boolean allowTypedValueCache = true; public static final int TYPECODE_IDREF = 1<<29; // the owner array gives fast access from a node to its parent; it is constructed // only when required // protected int[] parentIndex = null; // the following arrays have one entry for each attribute. protected int numberOfAttributes = 0; // attParent is the index of the parent element node protected int[] attParent; // attCode is the nameCode representing the attribute name protected int[] attCode; // attValue is the string value of the attribute protected CharSequence[] attValue; // attTypedValue is the typed vlaue of the attribute, maintained only if the attribute type is // something other than string, untypedAtomic, or anyURI. It is maintained lazily on first reference // to the typed value protected AtomicSequence[] attTypedValue; // attTypeCode holds type annotations. The array is created only if any nodes have a type annotation // or are marked as IDREF/IDREFS attributes. The bit TYPECODE_IDREF represents the is-idref property, // while IS_DTD_TYPE is set if the type is DTD-derived. /*@Nullable*/ protected int[] attTypeCode; // The following arrays have one entry for each namespace declaration protected int numberOfNamespaces = 0; // namespaceParent is the index of the element node owning the namespace declaration protected int[] namespaceParent; // namespaceCode is the namespace binding, holding the prefix and URI protected NamespaceBinding[] namespaceBinding; // an array holding the offsets of all the level-0 (root) nodes, so that the root of a given // node can be found efficiently /*@NotNull*/ private int[] rootIndex = new int[5]; protected int rootIndexUsed = 0; /*@Nullable*/ private int[] lineNumbers = null; /*@Nullable*/ private int[] columnNumbers = null; /*@Nullable*/ private SystemIdMap systemIdMap = null; // a boolean that is set to true if the document declares a namespace other than the XML namespace protected boolean usesNamespaces = false; // We maintain statistics in static data, recording how large the trees created under this Java VM // turned out to be. These figures are then used when allocating space for new trees, on the assumption // that there is likely to be some uniformity. The statistics are initialized to an arbitrary value // so that they can be used every time including the first time. The count of how many trees have been // created so far is initialized artificially to 5, to provide some smoothing if the first real tree is // atypically large or small. private static int treesCreated = 5; private static double averageNodes = 4000.0; private static double averageAttributes = 100.0; private static double averageNamespaces = 20.0; private static double averageCharacters = 4000.0; /** * Create a TinyTree. The initial size is based on the average size of * trees previously built in this session * @param config the Saxon Configuration */ public TinyTree(/*@NotNull*/ Configuration config) { this(config, (int)(averageNodes + 1), (int)(averageAttributes + 1), (int)(averageNamespaces + 1), (int)(averageCharacters + 1)); } /** * Create a tree with a specified initial size * @param config the Saxon configuration * @param nodes the expected number of (non attribute or namespace) nodes * @param attributes the expected number of attributes * @param namespaces the expected number of namespace declarations * @param characters the expected number of characters in the document (in text nodes) */ public TinyTree(/*@NotNull*/ Configuration config, int nodes, int attributes, int namespaces, int characters) { //System.err.println("TinyTree.new() (initial size " + nodes + ", treesCreated = " + treesCreated + ")"); nodeKind = new byte[nodes]; depth = new short[nodes]; next = new int[nodes]; alpha = new int[nodes]; beta = new int[nodes]; nameCode = new int[nodes]; numberOfAttributes = 0; attParent = new int[attributes]; attCode = new int[attributes]; attValue = new String[attributes]; numberOfNamespaces = 0; namespaceParent = new int[namespaces]; namespaceBinding = new NamespaceBinding[namespaces]; //charBuffer = new LargeStringBuffer(characters, 64000); charBuffer = (characters > 65000 ? new LargeStringBuffer() : new FastStringBuffer(characters)); setConfiguration(config); } /** * Set the Configuration that contains this document * @param config the Saxon configuration */ public void setConfiguration(/*@NotNull*/ Configuration config) { this.config = config; allowTypedValueCache = config.isLicensedFeature(Configuration.LicenseFeature.SCHEMA_VALIDATION) && config.getBooleanProperty(FeatureKeys.USE_TYPED_VALUE_CACHE); addNamespace(0, NamespaceBinding.XML); } /** * Get the configuration previously set using setConfiguration * @return the Saxon configuration */ public Configuration getConfiguration() { return config; } /** * Get the name pool used for the names in this document * @return the name pool */ public NamePool getNamePool() { return config.getNamePool(); } private void ensureNodeCapacity(short kind) { if (nodeKind.length < numberOfNodes+1) { //System.err.println("Number of nodes = " + numberOfNodes); int k = (kind == Type.STOPPER ? numberOfNodes+1 : numberOfNodes*2); byte[] nodeKind2 = new byte[k]; int[] next2 = new int[k]; short[] depth2 = new short[k]; int[] alpha2 = new int[k]; int[] beta2 = new int[k]; int[] nameCode2 = new int[k]; System.arraycopy(nodeKind, 0, nodeKind2, 0, numberOfNodes); System.arraycopy(next, 0, next2, 0, numberOfNodes); System.arraycopy(depth, 0, depth2, 0, numberOfNodes); System.arraycopy(alpha, 0, alpha2, 0, numberOfNodes); System.arraycopy(beta, 0, beta2, 0, numberOfNodes); System.arraycopy(nameCode, 0, nameCode2, 0, numberOfNodes); nodeKind = nodeKind2; next = next2; depth = depth2; alpha = alpha2; beta = beta2; nameCode = nameCode2; if (typeCodeArray != null) { int[] typeCodeArray2 = new int[k]; System.arraycopy(typeCodeArray, 0, typeCodeArray2, 0, numberOfNodes); typeCodeArray = typeCodeArray2; } if (typedValueArray != null) { @SuppressWarnings({"unchecked"}) AtomicSequence[] typedValueArray2 = new AtomicSequence[k]; //noinspection SuspiciousSystemArraycopy System.arraycopy(typedValueArray, 0, typedValueArray2, 0, numberOfNodes); typedValueArray = typedValueArray2; } if (lineNumbers != null) { int[] lines2 = new int[k]; System.arraycopy(lineNumbers, 0, lines2, 0, numberOfNodes); lineNumbers = lines2; int[] columns2 = new int[k]; System.arraycopy(columnNumbers, 0, columns2, 0, numberOfNodes); columnNumbers = columns2; } } } private void ensureAttributeCapacity() { if (attParent.length < numberOfAttributes+1) { int k = numberOfAttributes*2; if (k==0) { k = 10; } int[] attParent2 = new int[k]; int[] attCode2 = new int[k]; CharSequence[] attValue2 = new String[k]; System.arraycopy(attParent, 0, attParent2, 0, numberOfAttributes); System.arraycopy(attCode, 0, attCode2, 0, numberOfAttributes); System.arraycopy(attValue, 0, attValue2, 0, numberOfAttributes); attParent = attParent2; attCode = attCode2; attValue = attValue2; if (attTypeCode != null) { int[] attTypeCode2 = new int[k]; System.arraycopy(attTypeCode, 0, attTypeCode2, 0, numberOfAttributes); attTypeCode = attTypeCode2; } if (attTypedValue != null) { @SuppressWarnings({"unchecked"}) AtomicSequence[] attTypedValue2 = new AtomicSequence[k]; System.arraycopy(attTypedValue, 0, attTypedValue2, 0, numberOfAttributes); attTypedValue = attTypedValue2; } } } private void ensureNamespaceCapacity() { if (namespaceParent.length < numberOfNamespaces+1) { int k = numberOfNamespaces*2; if (k==0) { k = 10; } int[] namespaceParent2 = new int[k]; NamespaceBinding[] namespaceCode2 = new NamespaceBinding[k]; System.arraycopy(namespaceParent, 0, namespaceParent2, 0, numberOfNamespaces); System.arraycopy(namespaceBinding, 0, namespaceCode2, 0, numberOfNamespaces); namespaceParent = namespaceParent2; namespaceBinding = namespaceCode2; } } /** * Add a document node to the tree. The data structure can contain any number of document (or element) nodes * as top-level nodes. The document node is retained in the documentList list, and its offset in that list * is held in the alpha array for the relevant node number. * @param doc the document node to be added * @return the number of the node that was added */ int addDocumentNode(TinyDocumentImpl doc) { documentList.add(doc); return addNode(Type.DOCUMENT, 0, documentList.size()-1, 0, -1); } /** * Add a node to the tree * @param kind The kind of the node. This must be a document, element, text, comment, * or processing-instruction node (not an attribute or namespace) * @param depth The depth in the tree * @param alpha Pointer to attributes or text * @param beta Pointer to namespaces or text * @param nameCode The name of the node * @return the node number of the node that was added */ int addNode(short kind, int depth, int alpha, int beta, int nameCode) { ensureNodeCapacity(kind); nodeKind[numberOfNodes] = (byte)kind; this.depth[numberOfNodes] = (short)depth; this.alpha[numberOfNodes] = alpha; this.beta[numberOfNodes] = beta; this.nameCode[numberOfNodes] = nameCode; next[numberOfNodes] = -1; // safety precaution if (typeCodeArray != null) { typeCodeArray[numberOfNodes] = StandardNames.XS_UNTYPED; } if (numberOfNodes == 0) { documentNumber = config.getDocumentNumberAllocator().allocateDocumentNumber(); } if (depth == 0 && kind != Type.STOPPER) { if (rootIndexUsed == rootIndex.length) { int[] r2 = new int[rootIndexUsed * 2]; System.arraycopy(rootIndex, 0, r2, 0, rootIndexUsed); rootIndex = r2; } rootIndex[rootIndexUsed++] = numberOfNodes; } return numberOfNodes++; } /** * Append character data to the current text node * @param chars the character data to be appended */ void appendChars(CharSequence chars) { if (charBuffer instanceof FastStringBuffer && charBuffer.length() > 65000) { LargeStringBuffer lsb = new LargeStringBuffer(); lsb.append(charBuffer); charBuffer = lsb; } charBuffer.append(chars); } /** * Create a new text node that is a copy of an existing text node * @param depth the depth of the new node * @param existingNodeNr the node to be copied * @return the node number of the new node */ public int addTextNodeCopy(int depth, int existingNodeNr) { return addNode(Type.TEXT, depth, alpha[existingNodeNr], beta[existingNodeNr], -1); } /** * Condense the tree: release unused memory. This is done after the full tree has been built. * The method makes a pragmatic judgement as to whether it is worth reclaiming space; this is * only done when the constructed tree is very small compared with the space allocated. */ void condense() { //System.err.println("TinyTree.condense() " + this + " roots " + rootIndexUsed + " nodes " + numberOfNodes + " capacity " + nodeKind.length); // If there are already two trees in this forest, the chances are that more will be added. In this // case we don't want to condense the arrays because we will only have to expand them again, which gets // increasingly expensive as they grow larger. if (rootIndexUsed > 1) { return; } if (numberOfNodes * 3 < nodeKind.length || (nodeKind.length - numberOfNodes > 20000)) { //System.err.println("-- copying node arrays"); int k = numberOfNodes + 1; byte[] nodeKind2 = new byte[k]; int[] next2 = new int[k]; short[] depth2 = new short[k]; int[] alpha2 = new int[k]; int[] beta2 = new int[k]; int[] nameCode2 = new int[k]; System.arraycopy(nodeKind, 0, nodeKind2, 0, numberOfNodes); System.arraycopy(next, 0, next2, 0, numberOfNodes); System.arraycopy(depth, 0, depth2, 0, numberOfNodes); System.arraycopy(alpha, 0, alpha2, 0, numberOfNodes); System.arraycopy(beta, 0, beta2, 0, numberOfNodes); System.arraycopy(nameCode, 0, nameCode2, 0, numberOfNodes); if (typeCodeArray != null) { int[] type2 = new int[k]; System.arraycopy(typeCodeArray, 0, type2, 0, numberOfNodes); typeCodeArray = type2; } if (lineNumbers != null) { int[] lines2 = new int[k]; System.arraycopy(lineNumbers, 0, lines2, 0, numberOfNodes); lineNumbers = lines2; int[] columns2 = new int[k]; System.arraycopy(columnNumbers, 0, columns2, 0, numberOfNodes); columnNumbers = columns2; } nodeKind = nodeKind2; next = next2; depth = depth2; alpha = alpha2; beta = beta2; nameCode = nameCode2; } if ((numberOfAttributes * 3 < attParent.length) || (attParent.length - numberOfAttributes > 1000)) { int k = numberOfAttributes; //System.err.println("-- copying attribute arrays"); if (k==0) { attParent = IntArraySet.EMPTY_INT_ARRAY; attCode = IntArraySet.EMPTY_INT_ARRAY; attValue = EMPTY_STRING_ARRAY; attTypeCode = null; } int[] attParent2 = new int[k]; int[] attCode2 = new int[k]; CharSequence[] attValue2 = new String[k]; System.arraycopy(attParent, 0, attParent2, 0, numberOfAttributes); System.arraycopy(attCode, 0, attCode2, 0, numberOfAttributes); System.arraycopy(attValue, 0, attValue2, 0, numberOfAttributes); attParent = attParent2; attCode = attCode2; attValue = attValue2; if (attTypeCode != null) { int[] attTypeCode2 = new int[k]; System.arraycopy(attTypeCode, 0, attTypeCode2, 0, numberOfAttributes); attTypeCode = attTypeCode2; } } if (numberOfNamespaces * 3 < namespaceParent.length) { int k = numberOfNamespaces; int[] namespaceParent2 = new int[k]; NamespaceBinding[] namespaceCode2 = new NamespaceBinding[k]; //System.err.println("-- copying namespace arrays"); System.arraycopy(namespaceParent, 0, namespaceParent2, 0, numberOfNamespaces); System.arraycopy(namespaceBinding, 0, namespaceCode2, 0, numberOfNamespaces); namespaceParent = namespaceParent2; namespaceBinding = namespaceCode2; } updateStatistics(); // System.err.println("STATS: " + averageNodes + ", " + averageAttributes + ", " // + averageNamespaces + ", " + averageCharacters); // if (charBufferLength * 3 < charBuffer.length || // charBuffer.length - charBufferLength > 10000) { // char[] c2 = new char[charBufferLength]; // System.arraycopy(charBuffer, 0, c2, 0, charBufferLength); // charBuffer = c2; // } } /** * Set the type annotation of an element node * @param nodeNr the node whose type annotation is to be set * @param typeCode the type annotation */ void setElementAnnotation(int nodeNr, int typeCode) { if (typeCode != StandardNames.XS_UNTYPED) { if (typeCodeArray == null) { typeCodeArray = new int[nodeKind.length]; Arrays.fill(typeCodeArray, 0, nodeKind.length, StandardNames.XS_UNTYPED); } assert typeCodeArray != null; typeCodeArray[nodeNr] = typeCode; } } /** * Get the type annotation of a node. Applies only to document, element, text, * processing instruction, and comment nodes. * @param nodeNr the node whose type annotation is required * @return the fingerprint of the type annotation for elements and attributes, otherwise undefined. */ public int getTypeAnnotation(int nodeNr) { if (typeCodeArray == null) { return StandardNames.XS_UNTYPED; } return typeCodeArray[nodeNr] & NamePool.FP_MASK; } /** * Get the typed value of an element node. * @param element the element node * @return the typed value of the node (a Value whose items are AtomicValue instances) * @throws net.sf.saxon.trans.XPathException if a dynamic error occurs, for example if the node is * an element annotated with a type that has element-only content */ /*@Nullable*/ public AtomicSequence getTypedValueOfElement(/*@NotNull*/ TinyElementImpl element) throws XPathException { int nodeNr = element.nodeNr; if (typedValueArray == null || typedValueArray[nodeNr] == null) { int annotation = getTypeAnnotation(nodeNr); if (annotation == StandardNames.XS_UNTYPED || annotation == StandardNames.XS_UNTYPED_ATOMIC || annotation == StandardNames.XS_ANY_TYPE) { CharSequence stringValue = TinyParentNodeImpl.getStringValueCS(this, nodeNr); return new UntypedAtomicValue(stringValue); } else if (annotation == StandardNames.XS_STRING) { CharSequence stringValue = TinyParentNodeImpl.getStringValueCS(this, nodeNr); return new StringValue(stringValue); } else if (annotation == StandardNames.XS_ANY_URI) { CharSequence stringValue = TinyParentNodeImpl.getStringValueCS(this, nodeNr); return new AnyURIValue(stringValue); } else { SchemaType stype = getConfiguration().getSchemaType(annotation); if (stype == null) { String typeName; try { typeName = getNamePool().getDisplayName(annotation); } catch (Exception err) { typeName = annotation + ""; } throw new XPathException("Unknown type annotation " + Err.wrap(typeName) + " in document instance"); } else { AtomicSequence value = stype.atomize(element); if (allowTypedValueCache) { if (typedValueArray == null) { //noinspection unchecked typedValueArray = new AtomicSequence[nodeKind.length]; } typedValueArray[nodeNr] = value; } return value; } } } else { return typedValueArray[nodeNr]; } } /** * Get the type value of an element node, given only the node number * @param nodeNr the node number of the element node * @return the typed value of the node * @throws net.sf.saxon.trans.XPathException if the eement has no typed value */ /*@Nullable*/ public AtomicSequence getTypedValueOfElement(int nodeNr) throws XPathException { if (typedValueArray == null || typedValueArray[nodeNr] == null) { int annotation = getTypeAnnotation(nodeNr); if (annotation == StandardNames.XS_UNTYPED_ATOMIC || annotation == StandardNames.XS_UNTYPED) { CharSequence stringValue = TinyParentNodeImpl.getStringValueCS(this, nodeNr); return new UntypedAtomicValue(stringValue); } else if (annotation == StandardNames.XS_STRING) { CharSequence stringValue = TinyParentNodeImpl.getStringValueCS(this, nodeNr); return new StringValue(stringValue); } else if (annotation == StandardNames.XS_ANY_URI) { CharSequence stringValue = TinyParentNodeImpl.getStringValueCS(this, nodeNr); return new AnyURIValue(stringValue); } else { SchemaType stype = getConfiguration().getSchemaType(annotation); if (stype == null) { String typeName; try { typeName = getNamePool().getDisplayName(annotation); } catch (Exception err) { typeName = annotation + ""; } throw new XPathException("Unknown type annotation " + Err.wrap(typeName) + " in document instance"); } else { TinyElementImpl element = new TinyElementImpl(this, nodeNr); AtomicSequence value = stype.atomize(element); if (allowTypedValueCache) { if (typedValueArray == null) { //noinspection unchecked typedValueArray = new AtomicSequence[nodeKind.length]; } typedValueArray[nodeNr] = value; } return value; } } } else { return typedValueArray[nodeNr]; } } /** * Get the typed value of an attribute node. This method avoids * materializing the attribute node if possible, but uses the attribute node * supplied if it already exists. * @param att the attribute node if available. If null is supplied, the attribute node * will be materialized only if it is needed. * @param nodeNr the node number of the attribute node * @return the typed value of the node * @throws net.sf.saxon.trans.XPathException if an error is found */ public AtomicSequence getTypedValueOfAttribute(/*@Nullable*/ TinyAttributeImpl att, int nodeNr) throws XPathException { if (attTypeCode == null) { // it's an untyped tree return new UntypedAtomicValue(attValue[nodeNr]); } if (attTypedValue == null || attTypedValue[nodeNr] == null) { int annotation = getAttributeAnnotation(nodeNr); if (annotation == StandardNames.XS_UNTYPED_ATOMIC) { return new UntypedAtomicValue(attValue[nodeNr]); } else if (annotation == StandardNames.XS_STRING) { return new StringValue(attValue[nodeNr]); } else if (annotation == StandardNames.XS_ANY_URI) { return new AnyURIValue(attValue[nodeNr]); } else { SchemaType stype = getConfiguration().getSchemaType(annotation); if (stype == null) { String typeName; try { typeName = getNamePool().getDisplayName(annotation); } catch (Exception err) { typeName = annotation + ""; } throw new XPathException("Unknown attribute type annotation " + Err.wrap(typeName) + " in document instance"); } else { if (att == null) { att = new TinyAttributeImpl(this, nodeNr); } AtomicSequence value = stype.atomize(att); if (allowTypedValueCache) { if (attTypedValue == null) { //noinspection unchecked attTypedValue = new AtomicSequence[attParent.length]; } attTypedValue[nodeNr] = value; } return value; } } } else { return attTypedValue[nodeNr]; } } /** * Get the node kind of a given node, which must be a document, element, * text, comment, or processing instruction node * @param nodeNr the node number * @return the node kind */ public int getNodeKind(int nodeNr) { int kind = nodeKind[nodeNr]; return (kind == Type.WHITESPACE_TEXT ? Type.TEXT : kind); } /** * Get the nameCode for a given node, which must be a document, element, * text, comment, or processing instruction node * @param nodeNr the node number * @return the name code */ public int getNameCode(int nodeNr) { return nameCode[nodeNr]; } /** * On demand, make an index for quick access to preceding-sibling nodes */ void ensurePriorIndex() { // TODO: avoid rebuilding the whole index in the second case, i.e. with a forest if (prior==null || prior.length < numberOfNodes) { makePriorIndex(); } } private synchronized void makePriorIndex() { int[] p = new int[numberOfNodes]; Arrays.fill(p, 0, numberOfNodes, -1); for (int i=0; i i) { p[nextNode] = i; } } prior = p; } /** * Add an attribute node to the tree * @param root the root of the tree to contain the attribute * @param parent the parent element of the new attribute * @param nameCode the name code of the attribute * @param typeCode the type annotation of the attribute * @param attValue the string value of the attribute * @param properties any special properties of the attribute (bit-significant) */ void addAttribute(/*@NotNull*/ NodeInfo root, int parent, int nameCode, int typeCode, CharSequence attValue, int properties) { ensureAttributeCapacity(); attParent[numberOfAttributes] = parent; attCode[numberOfAttributes] = nameCode; this.attValue[numberOfAttributes] = attValue; if (typeCode == -1) { // this shouldn't happen any more typeCode = StandardNames.XS_UNTYPED_ATOMIC; } if (typeCode != StandardNames.XS_UNTYPED_ATOMIC) { initializeAttributeTypeCodes(); } if (attTypeCode != null) { attTypeCode[numberOfAttributes] = typeCode; } if (alpha[parent] == -1) { alpha[parent] = numberOfAttributes; } if (root instanceof TinyDocumentImpl) { boolean isID = false; if ((properties & ReceiverOptions.IS_ID) != 0) { isID = true; } else if ((nameCode & NamePool.FP_MASK) == StandardNames.XML_ID) { isID = true; } else if (config.getTypeHierarchy().isIdCode(typeCode)) { isID = true; } if (isID) { // The attribute is marked as being an ID. But we don't trust it - it // might come from a non-validating parser. Before adding it to the index, we // check that it really is an ID. String id = Whitespace.trim(attValue); // Make an exception to our usual policy of storing the original string value. // This is because xml:id processing applies whitespace trimming at an earlier stage this.attValue[numberOfAttributes] = id; if (root.getConfiguration().getNameChecker().isValidNCName(id)) { NodeInfo e = getNode(parent); ((TinyDocumentImpl)root).registerID(e, id); } else if (attTypeCode != null) { attTypeCode[numberOfAttributes] = StandardNames.XS_UNTYPED_ATOMIC; } } if ((properties & ReceiverOptions.IS_IDREF) != 0) { initializeAttributeTypeCodes(); assert attTypeCode != null; attTypeCode[numberOfAttributes] = typeCode | TYPECODE_IDREF; } } // Note: IDREF attributes are not indexed at this stage; that happens only if and when // the idref() function is called. // Note that an attTypes array will be created for all attributes if any IDREF value is reported. numberOfAttributes++; } private void initializeAttributeTypeCodes() { if (attTypeCode==null) { // this is the first typed attribute; // create an array for the types, and set all previous attributes to untyped attTypeCode = new int[attParent.length]; Arrays.fill(attTypeCode, 0, numberOfAttributes, StandardNames.XS_UNTYPED_ATOMIC); // for (int i=0; i level) { if (nodeKind[next]==Type.TEXT) { if (sb==null) { sb = new FastStringBuffer(FastStringBuffer.MEDIUM); } sb.append(TinyTextImpl.getStringValue(this, next)); } else if (nodeKind[next]==Type.WHITESPACE_TEXT) { if (sb==null) { sb = new FastStringBuffer(FastStringBuffer.MEDIUM); } WhitespaceTextImpl.appendStringValue(this, next, sb); } next++; } if (sb==null) { return UntypedAtomicValue.ZERO_LENGTH_UNTYPED; } else { return new UntypedAtomicValue(sb.condense()); } case Type.TEXT: return new UntypedAtomicValue(TinyTextImpl.getStringValue(this, nodeNr)); case Type.WHITESPACE_TEXT: return new UntypedAtomicValue(WhitespaceTextImpl.getStringValueCS(this, nodeNr)); case Type.COMMENT: case Type.PROCESSING_INSTRUCTION: int start2 = alpha[nodeNr]; int len2 = beta[nodeNr]; if (len2==0) return UntypedAtomicValue.ZERO_LENGTH_UNTYPED; char[] dest = new char[len2]; assert commentBuffer != null; commentBuffer.getChars(start2, start2+len2, dest, 0); return new StringValue(new CharSlice(dest, 0, len2)); default: throw new IllegalStateException("Unknown node kind"); } } /** * Make a (transient) attribute node from the array of attributes * @param nr the node number of the attribute * @return an attribute node */ /*@NotNull*/ TinyAttributeImpl getAttributeNode(int nr) { return new TinyAttributeImpl(this, nr); } /** * Get the type annotation of an attribute node. * @param nr the node number of the attribute * @return the fingerprint of the type annotation, or Type.UNTYPED_ATOMIC if there is no annotation */ int getAttributeAnnotation(int nr) { if (attTypeCode == null) { return StandardNames.XS_UNTYPED_ATOMIC; } else { return attTypeCode[nr] & ~TYPECODE_IDREF; } } /** * Determine whether an attribute is an IDREF/IDREFS attribute. (The represents the * is-idref property in the data model) * @param nr the node number of the attribute * @return true if this is an IDREF/IDREFS attribute */ public boolean isIdAttribute(int nr) { if (attTypeCode == null) { return false; } int tc = getAttributeAnnotation(nr); if (tc == StandardNames.XS_UNTYPED_ATOMIC) { return false; } else if (tc == StandardNames.XS_ID) { return true; } else if (tc < 1024) { return false; } else { final SchemaType type = getConfiguration().getSchemaType(tc); assert type != null; final TypeHierarchy th = getConfiguration().getTypeHierarchy(); if (type.isAtomicType()) { return th.isSubType((AtomicType)type, BuiltInAtomicType.ID); } else if (type instanceof ListType) { // We are using the XSD 1.1 rules, which allow ID's in list and union types SimpleType itemType = ((ListType)type).getItemType(); return itemType.isAtomicType() && th.isSubType((AtomicType)itemType, BuiltInAtomicType.ID); } } return false; } /** * Determine whether an attribute is an IDREF/IDREFS attribute. (The represents the * is-idref property in the data model) * @param nr the node number of the attribute * @return true if this is an IDREF/IDREFS attribute */ public boolean isIdrefAttribute(int nr) { if (attTypeCode == null) { return false; } int tc = attTypeCode[nr]; if ((tc & TYPECODE_IDREF) != 0) { return true; } if (tc == StandardNames.XS_UNTYPED_ATOMIC) { return false; } else if (tc == StandardNames.XS_IDREF) { return true; } else if (tc == StandardNames.XS_IDREFS) { return true; } else if (tc < 1024) { return false; } else { SchemaType type = getConfiguration().getSchemaType(tc); assert type != null; return type.isIdRefType(); } } /** * Ask whether an element is an ID element. (The represents the * is-id property in the data model) * @param nr the element node whose is-idref property is required * @return true if the node has the is-idref property */ public boolean isIdElement(int nr) { if (typeCodeArray == null) { return false; } int tc = typeCodeArray[nr]; return (tc & TYPECODE_IDREF) != 0 || getConfiguration().getTypeHierarchy().isIdCode(tc & NamePool.FP_MASK); // TODO: there is an additional condition, which is that the typed value must be a singleton } /** * Ask whether an element is an IDREF/IDREFS element. (The represents the * is-idref property in the data model) * @param nr the element node whose is-idref property is required * @return true if the node has the is-idref property */ public boolean isIdrefElement(int nr) { if (typeCodeArray == null) { return false; } int tc = typeCodeArray[nr]; return (tc & TYPECODE_IDREF) != 0 || getConfiguration().getTypeHierarchy().isIdrefsCode(tc & NamePool.FP_MASK); } /** * Set the system id of an element in the document. This identifies the external entity containing * the node - this is not necessarily the same as the base URI. * @param seq the node number * @param uri the system ID */ void setSystemId(int seq, /*@Nullable*/ String uri) { if (uri==null) { uri = ""; } if (systemIdMap==null) { systemIdMap = new SystemIdMap(); } systemIdMap.setSystemId(seq, uri); } /** * Get the system id of an element in the document * @param seq the node number of the element node * @return the system id (base URI) of the element */ /*@Nullable*/ public String getSystemId(int seq) { if (systemIdMap==null) { return null; } return systemIdMap.getSystemId(seq); } /** * Get the root node for a given node * @param nodeNr the node number of the given node * @return the node number of the root of the tree containing the given node */ int getRootNode(int nodeNr) { for (int i=rootIndexUsed-1; i>=0; i--) { if (rootIndex[i] <= nodeNr) { return rootIndex[i]; } } return 0; } /** * Set line numbering on */ public void setLineNumbering() { lineNumbers = new int[nodeKind.length]; Arrays.fill(lineNumbers, -1); columnNumbers = new int[nodeKind.length]; Arrays.fill(columnNumbers, -1); } /** * Set the line number for a node. Ignored if line numbering is off. * @param sequence the node number * @param line the line number to be set for the node * @param column the column number for the node */ void setLineNumber(int sequence, int line, int column) { if (lineNumbers != null) { assert columnNumbers != null; lineNumbers[sequence] = line; columnNumbers[sequence] = column; } } /** * Get the line number for a node. * @param sequence the node number * @return the line number of the node. Return -1 if line numbering is off. */ public int getLineNumber(int sequence) { if (lineNumbers != null) { // find the nearest preceding node that has a known line number, and return it for (int i=sequence; i>=0; i--) { int c = lineNumbers[i]; if (c > 0) { return c; } } } return -1; } /** * Get the column number for a node. * @param sequence the node number * @return the line number of the node. Return -1 if line numbering is off. */ int getColumnNumber(int sequence) { if (columnNumbers != null) { // find the nearest preceding node that has a known column number, and return it for (int i=sequence; i>=0; i--) { int c = columnNumbers[sequence]; if (c > 0) { return c; } } } return -1; } /** * Get the document number (actually, the tree number) * @return the unique number of this TinyTree structure */ public long getDocumentNumber() { return documentNumber; } /** * Ask whether a given node is nilled * @param nodeNr the node in question (which must be an element node) * @return true if the node has the nilled property */ public boolean isNilled(int nodeNr) { return (typeCodeArray != null && (typeCodeArray[nodeNr] & NodeInfo.IS_NILLED) != 0); } /** * Produce diagnostic print of main tree arrays */ public void diagnosticDump() { NamePool pool = config.getNamePool(); System.err.println(" node kind depth next alpha beta name type"); for (int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy