001: package net.sf.saxon.event;
002:
003: import net.sf.saxon.expr.ExpressionLocation;
004: import net.sf.saxon.expr.RoleLocator;
005: import net.sf.saxon.expr.Token;
006: import net.sf.saxon.om.Item;
007: import net.sf.saxon.om.NamePool;
008: import net.sf.saxon.pattern.CombinedNodeTest;
009: import net.sf.saxon.pattern.ContentTypeTest;
010: import net.sf.saxon.pattern.NameTest;
011: import net.sf.saxon.pattern.NodeKindTest;
012: import net.sf.saxon.trans.DynamicError;
013: import net.sf.saxon.trans.XPathException;
014: import net.sf.saxon.type.ItemType;
015: import net.sf.saxon.type.Type;
016: import net.sf.saxon.value.Cardinality;
017: import net.sf.saxon.value.Value;
018:
019: import java.util.HashSet;
020:
021: /**
022: * A filter on the push pipeline that performs type checking, both of the item type and the
023: * cardinality.
024: * <p>
025: * Note that the TypeCheckingFilter cannot currently check document node tests of the form
026: * document-node(element(X,Y)), so it is not invoked in such cases. This isn't a big problem, because most
027: * instructions that return document nodes materialize them anyway.
028: */
029:
030: public class TypeCheckingFilter extends ProxyReceiver {
031:
032: private ItemType itemType;
033: private int cardinality;
034: private RoleLocator role;
035: private int count = 0;
036: private int level = 0;
037: private HashSet checkedElements = new HashSet(10);
038:
039: // used to avoid repeated checking when a template creates large numbers of elements of the same type
040:
041: public void setRequiredType(ItemType type, int cardinality,
042: RoleLocator role) {
043: this .itemType = type;
044: this .cardinality = cardinality;
045: this .role = role;
046: }
047:
048: /**
049: * Notify an attribute. Attributes are notified after the startElement event, and before any
050: * children. Namespaces and attributes may be intermingled.
051: *
052: * @param nameCode The name of the attribute, as held in the name pool
053: * @param typeCode The type of the attribute, as held in the name pool
054: * @param properties Bit significant value. The following bits are defined:
055: * <dd>DISABLE_ESCAPING</dd> <dt>Disable escaping for this attribute</dt>
056: * <dd>NO_SPECIAL_CHARACTERS</dd> <dt>Attribute value contains no special characters</dt>
057: * @throws IllegalStateException: attempt to output an attribute when there is no open element
058: * start tag
059: */
060:
061: public void attribute(int nameCode, int typeCode,
062: CharSequence value, int locationId, int properties)
063: throws XPathException {
064: if (level == 0) {
065: if (++count == 2) {
066: checkAllowsMany(locationId);
067: }
068: ItemType type = new CombinedNodeTest(new NameTest(
069: Type.ATTRIBUTE, nameCode, getNamePool()),
070: Token.INTERSECT, new ContentTypeTest(
071: Type.ATTRIBUTE, getConfiguration()
072: .getSchemaType(typeCode),
073: getConfiguration()));
074: checkItemType(type, locationId);
075: }
076: super .attribute(nameCode, typeCode, value, locationId,
077: properties);
078: }
079:
080: /**
081: * Character data
082: */
083:
084: public void characters(CharSequence chars, int locationId,
085: int properties) throws XPathException {
086: if (level == 0) {
087: if (++count == 2) {
088: checkAllowsMany(locationId);
089: }
090: ItemType type = NodeKindTest.TEXT;
091: checkItemType(type, locationId);
092: }
093: super .characters(chars, locationId, properties);
094: }
095:
096: /**
097: * Output a comment
098: */
099:
100: public void comment(CharSequence chars, int locationId,
101: int properties) throws XPathException {
102: if (level == 0) {
103: if (++count == 2) {
104: checkAllowsMany(locationId);
105: }
106: ItemType type = NodeKindTest.COMMENT;
107: checkItemType(type, locationId);
108: }
109: super .comment(chars, locationId, properties); //To change body of overridden methods use File | Settings | File Templates.
110: }
111:
112: /**
113: * Notify a namespace. Namespaces are notified <b>after</b> the startElement event, and before
114: * any children for the element. The namespaces that are reported are only required
115: * to include those that are different from the parent element; however, duplicates may be reported.
116: * A namespace must not conflict with any namespaces already used for element or attribute names.
117: *
118: * @param namespaceCode an integer: the top half is a prefix code, the bottom half a URI code.
119: * These may be translated into an actual prefix and URI using the name pool. A prefix code of
120: * zero represents the empty prefix (that is, the default namespace). A URI code of zero represents
121: * a URI of "", that is, a namespace undeclaration.
122: * @throws IllegalStateException: attempt to output a namespace when there is no open element
123: * start tag
124: */
125:
126: public void namespace(int namespaceCode, int properties)
127: throws XPathException {
128: if (level == 0) {
129: if (++count == 2) {
130: checkAllowsMany(0);
131: }
132: ItemType type = NodeKindTest.NAMESPACE;
133: checkItemType(type, 0);
134: }
135: super .namespace(namespaceCode, properties); //To change body of overridden methods use File | Settings | File Templates.
136: }
137:
138: /**
139: * Processing Instruction
140: */
141:
142: public void processingInstruction(String target, CharSequence data,
143: int locationId, int properties) throws XPathException {
144: if (level == 0) {
145: if (++count == 2) {
146: checkAllowsMany(locationId);
147: }
148: ItemType type = NodeKindTest.PROCESSING_INSTRUCTION;
149: checkItemType(type, locationId);
150: }
151: super .processingInstruction(target, data, locationId,
152: properties);
153: }
154:
155: /**
156: * Start of a document node.
157: */
158:
159: public void startDocument(int properties) throws XPathException {
160: if (level == 0) {
161: if (++count == 2) {
162: checkAllowsMany(0);
163: }
164: ItemType type = NodeKindTest.DOCUMENT;
165: checkItemType(type, 0);
166: }
167: level++;
168: super .startDocument(properties);
169: }
170:
171: /**
172: * Notify the start of an element
173: *
174: * @param nameCode integer code identifying the name of the element within the name pool.
175: * @param typeCode integer code identifying the element's type within the name pool.
176: * @param properties properties of the element node
177: */
178:
179: public void startElement(int nameCode, int typeCode,
180: int locationId, int properties) throws XPathException {
181: if (level == 0) {
182: if (++count == 1) {
183: // don't bother with any caching on the first item, it will often be the only one
184: ItemType type = new CombinedNodeTest(new NameTest(
185: Type.ELEMENT, nameCode, getNamePool()),
186: Token.INTERSECT, new ContentTypeTest(
187: Type.ELEMENT, getConfiguration()
188: .getSchemaType(typeCode),
189: getConfiguration()));
190: checkItemType(type, locationId);
191: } else {
192: if (count == 2) {
193: checkAllowsMany(locationId);
194: }
195: Long key = new Long(
196: ((long) (nameCode & NamePool.FP_MASK)) << 32
197: | (long) (typeCode & NamePool.FP_MASK));
198: if (!checkedElements.contains(key)) {
199: ItemType type = new CombinedNodeTest(new NameTest(
200: Type.ELEMENT, nameCode, getNamePool()),
201: Token.INTERSECT, new ContentTypeTest(
202: Type.ELEMENT, getConfiguration()
203: .getSchemaType(typeCode),
204: getConfiguration()));
205: checkItemType(type, locationId);
206: checkedElements.add(key);
207: }
208: }
209: }
210: level++;
211: super .startElement(nameCode, typeCode, locationId, properties);
212: }
213:
214: /**
215: * Notify the end of a document node
216: */
217:
218: public void endDocument() throws XPathException {
219: level--;
220: super .endDocument();
221: }
222:
223: /**
224: * End of element
225: */
226:
227: public void endElement() throws XPathException {
228: level--;
229: super .endElement();
230: }
231:
232: /**
233: * End of event stream
234: */
235:
236: public void close() throws XPathException {
237: if (count == 0 && !Cardinality.allowsZero(cardinality)) {
238: DynamicError err = new DynamicError(
239: "An empty sequence is not allowed as the "
240: + role.getMessage());
241: String errorCode = role.getErrorCode();
242: err.setErrorCode(errorCode);
243: if (!"XPDY0050".equals(errorCode)) {
244: err.setIsTypeError(true);
245: }
246: throw err;
247: }
248: // don't pass on the close event
249: }
250:
251: /**
252: * Output an item (atomic value or node) to the sequence
253: */
254:
255: public void append(Item item, int locationId, int copyNamespaces)
256: throws XPathException {
257: if (level == 0) {
258: if (++count == 2) {
259: checkAllowsMany(locationId);
260: }
261: checkItemType(Value.asValue(item).getItemType(
262: getNamePool().getTypeHierarchy()), locationId);
263: }
264: if (nextReceiver instanceof SequenceReceiver) {
265: ((SequenceReceiver) nextReceiver).append(item, locationId,
266: copyNamespaces);
267: } else {
268: super .append(item, locationId, copyNamespaces);
269: }
270: }
271:
272: private void checkItemType(ItemType type, int locationId)
273: throws DynamicError {
274: if (!getNamePool().getTypeHierarchy().isSubType(type, itemType)) {
275: String message = role.composeErrorMessage(itemType, type,
276: getNamePool());
277: String errorCode = role.getErrorCode();
278: DynamicError err = new DynamicError(message);
279: err.setErrorCode(errorCode);
280: if (!"XPDY0050".equals(errorCode)) {
281: err.setIsTypeError(true);
282: }
283: err.setLocator(ExpressionLocation.getSourceLocator(
284: locationId, getPipelineConfiguration()
285: .getLocationProvider()));
286: throw err;
287: }
288: }
289:
290: private void checkAllowsMany(int locationId) throws XPathException {
291: if (!Cardinality.allowsMany(cardinality)) {
292: DynamicError err = new DynamicError(
293: "A sequence of more than one item is not allowed as the "
294: + role.getMessage());
295: String errorCode = role.getErrorCode();
296: err.setErrorCode(errorCode);
297: if (!"XPDY0050".equals(errorCode)) {
298: err.setIsTypeError(true);
299: }
300: err.setLocator(ExpressionLocation.getSourceLocator(
301: locationId, getPipelineConfiguration()
302: .getLocationProvider()));
303: throw err;
304: }
305: }
306:
307: }
308:
309: //
310: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
311: // you may not use this file except in compliance with the License. You may obtain a copy of the
312: // License at http://www.mozilla.org/MPL/
313: //
314: // Software distributed under the License is distributed on an "AS IS" basis,
315: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
316: // See the License for the specific language governing rights and limitations under the License.
317: //
318: // The Original Code is: all this file.
319: //
320: // The Initial Developer of the Original Code is Michael H. Kay.
321: //
322: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
323: //
324: // Contributor(s): none.
325: //
|