package client.net.sf.saxon.ce.pattern;
import client.net.sf.saxon.ce.expr.Token;
import client.net.sf.saxon.ce.expr.sort.SetUtils;
import client.net.sf.saxon.ce.om.NamePool;
import client.net.sf.saxon.ce.om.NodeInfo;
import client.net.sf.saxon.ce.type.*;
import java.util.HashSet;
/**
* A CombinedNodeTest combines two nodetests using one of the operators
* union (=or), intersect (=and), difference (= "and not"). This arises
* when optimizing a union (etc) of two path expressions using the same axis.
* A CombinedNodeTest is also used to support constructs such as element(N,T),
* which can be expressed as (element(N,*) AND element(*,T))
*
* @author Michael H. Kay
*/
public class CombinedNodeTest extends NodeTest {
private NodeTest nodetest1;
private NodeTest nodetest2;
private int operator;
/**
* Create a NodeTest that combines two other node tests
* @param nt1 the first operand. Note that if the defaultPriority of the pattern
* is required, it will be taken from that of the first operand.
* @param operator one of Token.UNION, Token.INTERSECT, Token.EXCEPT
* @param nt2 the second operand
*/
public CombinedNodeTest(NodeTest nt1, int operator, NodeTest nt2) {
nodetest1 = nt1;
this.operator = operator;
nodetest2 = nt2;
}
/**
* Test whether this node test is satisfied by a given node.
* @param nodeType The type of node to be matched
@param fingerprint identifies the expanded name of the node to be matched.
*/
public boolean matches(int nodeType, int fingerprint, int annotation) {
switch (operator) {
case Token.UNION:
return nodetest1==null ||
nodetest2==null ||
nodetest1.matches(nodeType, fingerprint, annotation) ||
nodetest2.matches(nodeType, fingerprint, annotation);
case Token.INTERSECT:
return (nodetest1==null || nodetest1.matches(nodeType, fingerprint, annotation)) &&
(nodetest2==null || nodetest2.matches(nodeType, fingerprint, annotation));
case Token.EXCEPT:
return (nodetest1==null || nodetest1.matches(nodeType, fingerprint, annotation)) &&
!(nodetest2==null || nodetest2.matches(nodeType, fingerprint, annotation));
default:
throw new IllegalArgumentException("Unknown operator in Combined Node Test");
}
}
/**
* Test whether this node test is satisfied by a given node. This alternative
* method is used in the case of nodes where calculating the fingerprint is expensive,
* for example DOM or JDOM nodes.
* @param node the node to be matched
*/
public boolean matches(NodeInfo node) {
switch (operator) {
case Token.UNION:
return nodetest1==null ||
nodetest2==null ||
nodetest1.matches(node) ||
nodetest2.matches(node);
case Token.INTERSECT:
return (nodetest1==null || nodetest1.matches(node)) &&
(nodetest2==null || nodetest2.matches(node));
case Token.EXCEPT:
return (nodetest1==null || nodetest1.matches(node)) &&
!(nodetest2==null || nodetest2.matches(node));
default:
throw new IllegalArgumentException("Unknown operator in Combined Node Test");
}
}
public String toString(NamePool pool) {
if (nodetest1 instanceof NameTest && operator==Token.INTERSECT) {
int kind = nodetest1.getPrimitiveType();
String skind = (kind == Type.ELEMENT ? "element(" : "attribute(");
String content = "";
if (nodetest2 instanceof ContentTypeTest) {
final SchemaType schemaType = ((ContentTypeTest)nodetest2).getSchemaType();
content = ", " + pool.getClarkName(schemaType.getFingerprint());
}
String name = pool.getClarkName(nodetest1.getFingerprint());
return skind + name + content + ')';
} else {
String nt1 = (nodetest1==null ? "true()" : nodetest1.toString(pool));
String nt2 = (nodetest2==null ? "true()" : nodetest2.toString(pool));
return '(' + nt1 + ' ' + Token.tokens[operator] + ' ' + nt2 + ')';
}
}
public String toString() {
if (nodetest1 instanceof NameTest && operator==Token.INTERSECT) {
int kind = nodetest1.getPrimitiveType();
String skind = (kind == Type.ELEMENT ? "element(" : "attribute(");
String content = "";
if (nodetest2 instanceof ContentTypeTest) {
final SchemaType schemaType = ((ContentTypeTest)nodetest2).getSchemaType();
content = ", " + schemaType.getFingerprint();
}
String name = nodetest1.toString();
return skind + name + content + ')';
} else {
String nt1 = (nodetest1==null ? "true()" : nodetest1.toString());
String nt2 = (nodetest2==null ? "true()" : nodetest2.toString());
return '(' + nt1 + ' ' + Token.tokens[operator] + ' ' + nt2 + ')';
}
}
/**
* Get the supertype of this type. This isn't actually a well-defined concept: the types
* form a lattice rather than a strict hierarchy.
* @param th the type hierarchy cache
*/
public ItemType getSuperType(TypeHierarchy th) {
switch (operator) {
case Token.UNION:
return Type.getCommonSuperType(nodetest1, nodetest2, th);
case Token.INTERSECT:
return nodetest1;
case Token.EXCEPT:
return nodetest1;
default:
throw new IllegalArgumentException("Unknown operator in Combined Node Test");
}
}
/**
* Get a mask indicating which kinds of nodes this NodeTest can match. This is a combination
* of bits: 1<<Type.ELEMENT for element nodes, 1<<Type.TEXT for text nodes, and so on.
*/
public int getNodeKindMask() {
switch (operator) {
case Token.UNION:
return nodetest1.getNodeKindMask() | nodetest2.getNodeKindMask();
case Token.INTERSECT:
return nodetest1.getNodeKindMask() & nodetest2.getNodeKindMask();
case Token.EXCEPT:
return nodetest1.getNodeKindMask();
default:
return 0;
}
}
/**
* Get the basic kind of object that this ItemType matches: for a NodeTest, this is the kind of node,
* or Type.Node if it matches different kinds of nodes.
*
* @return the node kind matched by this node test
*/
public int getPrimitiveType() {
int mask = getNodeKindMask();
if (mask == (1<<Type.ELEMENT)) {
return Type.ELEMENT;
}
if (mask == (1<<Type.ATTRIBUTE)) {
return Type.ATTRIBUTE;
}
if (mask == (1<<Type.DOCUMENT)) {
return Type.DOCUMENT;
}
return Type.NODE;
}
/**
* Get the set of node names allowed by this NodeTest. This is returned as a set of Integer fingerprints.
* A null value indicates that all names are permitted (i.e. that there are no constraints on the node name).
* The default implementation returns null.
*/
public HashSet<Integer> getRequiredNodeNames() {
HashSet<Integer> s1 = nodetest1.getRequiredNodeNames();
HashSet<Integer> s2 = nodetest2.getRequiredNodeNames();
if (s2 == null) {
return s1;
}
if (s1 == null) {
return s2;
}
switch (operator) {
case Token.UNION: {
return (HashSet<Integer>) SetUtils.union(s1, s2);
}
case Token.INTERSECT: {
return (HashSet<Integer>) SetUtils.intersect(s1, s2);
}
case Token.EXCEPT: {
return (HashSet<Integer>) SetUtils.except(s1, s2);
}
default:
throw new UnsupportedOperationException();
}
}
/**
* Get the content type allowed by this NodeTest (that is, the type annotation of the matched nodes).
* Return AnyType if there are no restrictions. The default implementation returns AnyType.
*/
public SchemaType getContentType() {
SchemaType type1 = nodetest1.getContentType();
SchemaType type2 = nodetest2.getContentType();
if (type1.isSameType(type2)) return type1;
if (operator == Token.INTERSECT) {
if (type2 instanceof AnyType) {
return type1;
}
if (type1 instanceof AnyType) {
return type2;
}
}
return AnyType.getInstance();
}
/**
* Get the item type of the atomic values that will be produced when an item
* of this type is atomized (assuming that atomization succeeds)
*/
public AtomicType getAtomizedItemType() {
AtomicType type1 = nodetest1.getAtomizedItemType();
AtomicType type2 = nodetest2.getAtomizedItemType();
if (type1.isSameType(type2)) return type1;
if (operator == Token.INTERSECT) {
if (type2.equals(BuiltInAtomicType.ANY_ATOMIC)) {
return type1;
}
if (type1.equals(BuiltInAtomicType.ANY_ATOMIC)) {
return type2;
}
}
return BuiltInAtomicType.ANY_ATOMIC;
}
/**
* Get the name of the nodes matched by this nodetest, if it matches a specific name.
* Return -1 if the node test matches nodes of more than one name
*/
public int getFingerprint() {
int fp1 = nodetest1.getFingerprint();
int fp2 = nodetest2.getFingerprint();
if (fp1 == fp2) return fp1;
if (fp2 == -1 && operator==Token.INTERSECT) return fp1;
if (fp1 == -1 && operator==Token.INTERSECT) return fp2;
return -1;
}
/**
* Determine whether the content type (if present) is nillable
* @return true if the content test (when present) can match nodes that are nilled
*/
public boolean isNillable() {
// this should err on the safe side
return nodetest1.isNillable() || nodetest2.isNillable();
}
/**
* Returns a hash code value for the object.
*/
public int hashCode() {
return nodetest1.hashCode() ^ nodetest2.hashCode();
}
/**
* Indicates whether some other object is "equal to" this one.
*/
public boolean equals(Object other) {
return other instanceof CombinedNodeTest &&
((CombinedNodeTest)other).nodetest1.equals(nodetest1) &&
((CombinedNodeTest)other).nodetest2.equals(nodetest2) &&
((CombinedNodeTest)other).operator == operator;
}
/**
* Get the default priority of this nodeTest when used as a pattern. In the case of a union, this will always
* be (arbitrarily) the default priority of the first operand. In other cases, again somewhat arbitrarily, it
* is 0.25, reflecting the common usage of an intersection to represent the pattern element(E, T).
*/
public double getDefaultPriority() {
if (operator == Token.UNION) {
return nodetest1.getDefaultPriority();
} else {
// typically it's element(E, T)
return 0.25;
}
}
/**
* Get the two parts of the combined node test
* @return the two operands
*/
public NodeTest[] getComponentNodeTests() {
return new NodeTest[] {nodetest1, nodetest2};
}
/**
* Get the operator used to combine the two node tests: one of {@link Token#UNION},
* {@link Token#INTERSECT}, {@link Token#EXCEPT},
* @return the operator
*/
public int getOperator() {
return operator;
}
}
// 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.