001: package net.sf.saxon.pattern;
002:
003: import net.sf.saxon.expr.Token;
004: import net.sf.saxon.om.NamePool;
005: import net.sf.saxon.om.NodeInfo;
006: import net.sf.saxon.sort.IntHashSet;
007: import net.sf.saxon.tinytree.TinyTree;
008: import net.sf.saxon.type.*;
009:
010: /**
011: * A CombinedNodeTest combines two nodetests using one of the operators
012: * union (=or), intersect (=and), difference (= "and not"). This arises
013: * when optimizing a union (etc) of two path expressions using the same axis.
014: * A CombinedNodeTest is also used to support constructs such as element(N,T),
015: * which can be expressed as (element(N,*) AND element(*,T))
016: *
017: * @author Michael H. Kay
018: */
019:
020: public class CombinedNodeTest extends NodeTest {
021:
022: private NodeTest nodetest1;
023: private NodeTest nodetest2;
024: private int operator;
025:
026: public CombinedNodeTest(NodeTest nt1, int operator, NodeTest nt2) {
027: nodetest1 = nt1;
028: this .operator = operator;
029: nodetest2 = nt2;
030: }
031:
032: /**
033: * Test whether this node test is satisfied by a given node.
034: * @param nodeType The type of node to be matched
035: @param fingerprint identifies the expanded name of the node to be matched.
036:
037: */
038:
039: public boolean matches(int nodeType, int fingerprint, int annotation) {
040: switch (operator) {
041: case Token.UNION:
042: return nodetest1 == null
043: || nodetest2 == null
044: || nodetest1.matches(nodeType, fingerprint,
045: annotation)
046: || nodetest2.matches(nodeType, fingerprint,
047: annotation);
048: case Token.INTERSECT:
049: return (nodetest1 == null || nodetest1.matches(nodeType,
050: fingerprint, annotation))
051: && (nodetest2 == null || nodetest2.matches(
052: nodeType, fingerprint, annotation));
053: case Token.EXCEPT:
054: return (nodetest1 == null || nodetest1.matches(nodeType,
055: fingerprint, annotation))
056: && !(nodetest2 == null || nodetest2.matches(
057: nodeType, fingerprint, annotation));
058: default:
059: throw new IllegalArgumentException(
060: "Unknown operator in Combined Node Test");
061: }
062: }
063:
064: /**
065: * Test whether this node test is satisfied by a given node on a TinyTree. The node
066: * must be a document, element, text, comment, or processing instruction node.
067: * This method is provided
068: * so that when navigating a TinyTree a node can be rejected without
069: * actually instantiating a NodeInfo object.
070: *
071: * @param tree the TinyTree containing the node
072: * @param nodeNr the number of the node within the TinyTree
073: * @return true if the node matches the NodeTest, otherwise false
074: */
075:
076: public boolean matches(TinyTree tree, int nodeNr) {
077: switch (operator) {
078: case Token.UNION:
079: return nodetest1 == null || nodetest2 == null
080: || nodetest1.matches(tree, nodeNr)
081: || nodetest2.matches(tree, nodeNr);
082: case Token.INTERSECT:
083: return (nodetest1 == null || nodetest1
084: .matches(tree, nodeNr))
085: && (nodetest2 == null || nodetest2.matches(tree,
086: nodeNr));
087: case Token.EXCEPT:
088: return (nodetest1 == null || nodetest1
089: .matches(tree, nodeNr))
090: && !(nodetest2 == null || nodetest2.matches(tree,
091: nodeNr));
092: default:
093: throw new IllegalArgumentException(
094: "Unknown operator in Combined Node Test");
095: }
096: }
097:
098: /**
099: * Test whether this node test is satisfied by a given node. This alternative
100: * method is used in the case of nodes where calculating the fingerprint is expensive,
101: * for example DOM or JDOM nodes.
102: * @param node the node to be matched
103: */
104:
105: public boolean matches(NodeInfo node) {
106: switch (operator) {
107: case Token.UNION:
108: return nodetest1 == null || nodetest2 == null
109: || nodetest1.matches(node)
110: || nodetest2.matches(node);
111: case Token.INTERSECT:
112: return (nodetest1 == null || nodetest1.matches(node))
113: && (nodetest2 == null || nodetest2.matches(node));
114: case Token.EXCEPT:
115: return (nodetest1 == null || nodetest1.matches(node))
116: && !(nodetest2 == null || nodetest2.matches(node));
117: default:
118: throw new IllegalArgumentException(
119: "Unknown operator in Combined Node Test");
120: }
121: }
122:
123: public String toString(NamePool pool) {
124: if (nodetest1 instanceof NameTest
125: && operator == Token.INTERSECT) {
126: int kind = nodetest1.getPrimitiveType();
127: String content = "";
128: if (nodetest2 instanceof ContentTypeTest) {
129: content = ", "
130: + pool
131: .getClarkName(((ContentTypeTest) nodetest2)
132: .getSchemaType()
133: .getFingerprint());
134: }
135: String name = pool.getClarkName(nodetest1.getFingerprint());
136: if (kind == Type.ELEMENT) {
137: return "element(" + name + content + ')';
138: } else if (kind == Type.ATTRIBUTE) {
139: return "attribute(" + name + content + ')';
140: }
141: }
142: String nt1 = (nodetest1 == null ? "true()" : nodetest1
143: .toString(pool));
144: String nt2 = (nodetest2 == null ? "true()" : nodetest2
145: .toString(pool));
146: return '(' + nt1 + ' ' + Token.tokens[operator] + ' ' + nt2
147: + ')';
148: }
149:
150: /**
151: * Get the supertype of this type. This isn't actually a well-defined concept: the types
152: * form a lattice rather than a strict hierarchy.
153: * @param th
154: */
155:
156: public ItemType getSuperType(TypeHierarchy th) {
157: switch (operator) {
158: case Token.UNION:
159: return Type.getCommonSuperType(nodetest1, nodetest2, th);
160: case Token.INTERSECT:
161: return nodetest1;
162: case Token.EXCEPT:
163: return nodetest1;
164: default:
165: throw new IllegalArgumentException(
166: "Unknown operator in Combined Node Test");
167: }
168: }
169:
170: /**
171: * Get a mask indicating which kinds of nodes this NodeTest can match. This is a combination
172: * of bits: 1<<Type.ELEMENT for element nodes, 1<<Type.TEXT for text nodes, and so on.
173: */
174:
175: public int getNodeKindMask() {
176: switch (operator) {
177: case Token.UNION:
178: return nodetest1.getNodeKindMask()
179: | nodetest2.getNodeKindMask();
180: case Token.INTERSECT:
181: return nodetest1.getNodeKindMask()
182: & nodetest2.getNodeKindMask();
183: case Token.EXCEPT:
184: return nodetest1.getNodeKindMask();
185: default:
186: return 0;
187: }
188:
189: }
190:
191: /**
192: * Get the basic kind of object that this ItemType matches: for a NodeTest, this is the kind of node,
193: * or Type.Node if it matches different kinds of nodes.
194: *
195: * @return the node kind matched by this node test
196: */
197:
198: public int getPrimitiveType() {
199: int mask = getNodeKindMask();
200: if (mask == (1 << Type.ELEMENT)) {
201: return Type.ELEMENT;
202: }
203: if (mask == (1 << Type.ATTRIBUTE)) {
204: return Type.ATTRIBUTE;
205: }
206: if (mask == (1 << Type.DOCUMENT)) {
207: return Type.DOCUMENT;
208: }
209: return Type.NODE;
210: }
211:
212: /**
213: * Get the set of node names allowed by this NodeTest. This is returned as a set of Integer fingerprints.
214: * A null value indicates that all names are permitted (i.e. that there are no constraints on the node name.
215: * The default implementation returns null.
216: */
217:
218: public IntHashSet getRequiredNodeNames() {
219: IntHashSet s1 = nodetest1.getRequiredNodeNames();
220: IntHashSet s2 = nodetest2.getRequiredNodeNames();
221: if (s2 == null) {
222: return s1;
223: }
224: if (s1 == null) {
225: return s2;
226: }
227: switch (operator) {
228: case Token.UNION: {
229: return s1.union(s2);
230: }
231: case Token.INTERSECT: {
232: return s1.intersect(s2);
233: }
234: case Token.EXCEPT: {
235: return s1.except(s2);
236: }
237: default:
238: throw new UnsupportedOperationException();
239: }
240: }
241:
242: /**
243: * Get the content type allowed by this NodeTest (that is, the type annotation of the matched nodes).
244: * Return AnyType if there are no restrictions. The default implementation returns AnyType.
245: */
246:
247: public SchemaType getContentType() {
248: SchemaType type1 = nodetest1.getContentType();
249: SchemaType type2 = nodetest2.getContentType();
250: if (type1.isSameType(type2))
251: return type1;
252: if (operator == Token.INTERSECT) {
253: if (type2 instanceof AnyType) {
254: return type1;
255: }
256: if (type1 instanceof AnyType) {
257: return type2;
258: }
259: }
260: return AnyType.getInstance();
261: }
262:
263: /**
264: * Get the name of the nodes matched by this nodetest, if it matches a specific name.
265: * Return -1 if the node test matches nodes of more than one name
266: */
267:
268: public int getFingerprint() {
269: int fp1 = nodetest1.getFingerprint();
270: int fp2 = nodetest2.getFingerprint();
271: if (fp1 == fp2)
272: return fp1;
273: if (fp2 == -1 && operator == Token.INTERSECT)
274: return fp1;
275: if (fp1 == -1 && operator == Token.INTERSECT)
276: return fp2;
277: return -1;
278: }
279:
280: /**
281: * Returns a hash code value for the object.
282: */
283:
284: public int hashCode() {
285: return nodetest1.hashCode() ^ nodetest2.hashCode();
286: }
287:
288: /**
289: * Indicates whether some other object is "equal to" this one.
290: */
291: public boolean equals(Object other) {
292: return other instanceof CombinedNodeTest
293: && ((CombinedNodeTest) other).nodetest1
294: .equals(nodetest1)
295: && ((CombinedNodeTest) other).nodetest2
296: .equals(nodetest2)
297: && ((CombinedNodeTest) other).operator == operator;
298: }
299:
300: /**
301: * get the default priority of this nodeTest when used as a pattern
302: */
303:
304: public double getDefaultPriority() {
305: return 0.25;
306: }
307:
308: /**
309: * Get the two parts of the combined node test
310: */
311:
312: public NodeTest[] getComponentNodeTests() {
313: NodeTest[] tests = { nodetest1, nodetest2 };
314: return tests;
315: }
316: }
317:
318: //
319: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
320: // you may not use this file except in compliance with the License. You may obtain a copy of the
321: // License at http://www.mozilla.org/MPL/
322: //
323: // Software distributed under the License is distributed on an "AS IS" basis,
324: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
325: // See the License for the specific language governing rights and limitations under the License.
326: //
327: // The Original Code is: all this file.
328: //
329: // The Initial Developer of the Original Code is Michael H. Kay.
330: //
331: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
332: //
333: // Contributor(s): none.
334: //
|