net.sf.saxon.tree.tiny.TinyTree Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of saxon-he Show documentation
Show all versions of saxon-he Show documentation
An OSGi bundle for Saxon-HE
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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