001: package net.sf.saxon.expr;
002:
003: import net.sf.saxon.functions.NumberFn;
004: import net.sf.saxon.functions.StringFn;
005: import net.sf.saxon.functions.SystemFunction;
006: import net.sf.saxon.style.StandardNames;
007: import net.sf.saxon.trans.StaticError;
008: import net.sf.saxon.trans.XPathException;
009: import net.sf.saxon.trans.DynamicError;
010: import net.sf.saxon.type.*;
011: import net.sf.saxon.value.*;
012: import net.sf.saxon.pattern.NoNodeTest;
013: import net.sf.saxon.Configuration;
014:
015: import javax.xml.transform.SourceLocator;
016:
017: /**
018: * This class provides Saxon's type checking capability. It contains a static method,
019: * staticTypeCheck, which is called at compile time to perform type checking of
020: * an expression. This class is never instantiated.
021: */
022:
023: public final class TypeChecker {
024:
025: // Class is not instantiated
026: private TypeChecker() {
027: }
028:
029: /**
030: * Check an expression against a required type, modifying it if necessary.
031: *
032: * <p>This method takes the supplied expression and checks to see whether it is
033: * known statically to conform to the specified type. There are three possible
034: * outcomes. If the static type of the expression is a subtype of the required
035: * type, the method returns the expression unchanged. If the static type of
036: * the expression is incompatible with the required type (for example, if the
037: * supplied type is integer and the required type is string) the method throws
038: * an exception (this results in a compile-time type error being reported). If
039: * the static type is a supertype of the required type, then a new expression
040: * is constructed that evaluates the original expression and checks the dynamic
041: * type of the result; this new expression is returned as the result of the
042: * method.</p>
043: *
044: * <p>The rules applied are those for function calling in XPath, that is, the rules
045: * that the argument of a function call must obey in relation to the signature of
046: * the function. Some contexts require slightly different rules (for example,
047: * operands of polymorphic operators such as "+"). In such cases this method cannot
048: * be used.</p>
049: *
050: * <p>Note that this method does <b>not</b> do recursive type-checking of the
051: * sub-expressions.</p>
052: *
053: * @param supplied The expression to be type-checked
054: * @param req The required type for the context in which the expression is used
055: * @param backwardsCompatible
056: * True if XPath 1.0 backwards compatibility mode is applicable
057: * @param role Information about the role of the subexpression within the
058: * containing expression, used to provide useful error messages
059: * @param env The static context containing the types being checked. At present
060: * this is used only to locate a NamePool
061: * @return The original expression if it is type-safe, or the expression
062: * wrapped in a run-time type checking expression if not.
063: * @throws net.sf.saxon.trans.StaticError if the supplied type is statically inconsistent with the
064: * required type (that is, if they have no common subtype)
065: */
066:
067: public static Expression staticTypeCheck(Expression supplied,
068: SequenceType req, boolean backwardsCompatible,
069: RoleLocator role, StaticContext env) throws StaticError {
070:
071: // System.err.println("Static Type Check on expression (requiredType = " + req + "):"); supplied.display(10);
072:
073: Expression exp = supplied;
074: final TypeHierarchy th = env.getNamePool().getTypeHierarchy();
075:
076: ItemType reqItemType = req.getPrimaryType();
077: int reqCard = req.getCardinality();
078: boolean allowsMany = Cardinality.allowsMany(reqCard);
079:
080: ItemType suppliedItemType = null;
081: // item type of the supplied expression: null means not yet calculated
082: int suppliedCard = -1;
083: // cardinality of the supplied expression: -1 means not yet calculated
084:
085: boolean cardOK = (reqCard == StaticProperty.ALLOWS_ZERO_OR_MORE);
086: // Unless the required cardinality is zero-or-more (no constraints).
087: // check the static cardinality of the supplied expression
088: if (!cardOK) {
089: suppliedCard = exp.getCardinality();
090: cardOK = Cardinality.subsumes(reqCard, suppliedCard);
091: // May later find that cardinality is not OK after all, if atomization takes place
092: }
093:
094: boolean itemTypeOK = reqItemType instanceof AnyItemType;
095: // Unless the required item type and content type are ITEM (no constraints)
096: // check the static item type against the supplied expression.
097: // NOTE: we don't currently do any static inference regarding the content type
098: if (!itemTypeOK) {
099: suppliedItemType = exp.getItemType(th);
100: if (suppliedItemType instanceof NoNodeTest) {
101: // supplied type is empty-sequence(): this can violate a cardinality constraint but not an item type constraint
102: itemTypeOK = true;
103: } else {
104: int relation = th.relationship(reqItemType,
105: suppliedItemType);
106: itemTypeOK = relation == TypeHierarchy.SAME_TYPE
107: || relation == TypeHierarchy.SUBSUMES;
108: }
109: }
110:
111: // Handle the special rules for 1.0 compatibility mode
112: if (backwardsCompatible && !allowsMany) {
113: // rule 1
114: if (Cardinality.allowsMany(suppliedCard)) {
115: ComputedExpression cexp = new FirstItemExpression(exp);
116: cexp.adoptChildExpression(exp);
117: exp = cexp;
118: suppliedCard = StaticProperty.ALLOWS_ZERO_OR_ONE;
119: cardOK = Cardinality.subsumes(reqCard, suppliedCard);
120: }
121: if (!itemTypeOK) {
122: // rule 2
123: if (reqItemType == Type.STRING_TYPE) {
124: StringFn fn = (StringFn) SystemFunction
125: .makeSystemFunction("string", 1, env
126: .getNamePool());
127: fn.setParentExpression(exp.getParentExpression());
128: Expression[] args = { exp };
129: fn.setArguments(args);
130: try {
131: exp = fn.simplify(env).typeCheck(env,
132: AnyItemType.getInstance());
133: } catch (XPathException err) {
134: throw err.makeStatic();
135: }
136: suppliedItemType = Type.STRING_TYPE;
137: suppliedCard = StaticProperty.EXACTLY_ONE;
138: cardOK = Cardinality
139: .subsumes(reqCard, suppliedCard);
140: itemTypeOK = true;
141: }
142: // rule 3
143: if (reqItemType == Type.NUMBER_TYPE
144: || reqItemType == Type.DOUBLE_TYPE) {
145: NumberFn fn = (NumberFn) SystemFunction
146: .makeSystemFunction("number", 1, env
147: .getNamePool());
148: Expression[] args = { exp };
149: fn.setArguments(args);
150: try {
151: exp = fn.simplify(env).typeCheck(env,
152: AnyItemType.getInstance());
153: } catch (XPathException err) {
154: throw err.makeStatic();
155: }
156: suppliedItemType = Type.DOUBLE_TYPE;
157: suppliedCard = StaticProperty.EXACTLY_ONE;
158: cardOK = Cardinality
159: .subsumes(reqCard, suppliedCard);
160: itemTypeOK = true;
161: }
162: }
163: }
164:
165: if (!itemTypeOK) {
166: // Now apply the conversions needed in 2.0 mode
167:
168: if (reqItemType instanceof AtomicType) {
169:
170: // rule 1: Atomize
171: if (!(suppliedItemType instanceof AtomicType)
172: && !(suppliedCard == StaticProperty.EMPTY)) {
173: ComputedExpression cexp = new Atomizer(exp, env
174: .getConfiguration());
175: exp = cexp;
176: suppliedItemType = exp.getItemType(th);
177: suppliedCard = exp.getCardinality();
178: cardOK = Cardinality
179: .subsumes(reqCard, suppliedCard);
180: }
181:
182: // rule 2: convert untypedAtomic to the required type
183:
184: // 2a: all supplied values are untyped atomic. Convert if necessary, and we're finished.
185:
186: if ((suppliedItemType == Type.UNTYPED_ATOMIC_TYPE)
187: && !(reqItemType == Type.UNTYPED_ATOMIC_TYPE || reqItemType == Type.ANY_ATOMIC_TYPE)) {
188:
189: ComputedExpression cexp = new UntypedAtomicConverter(
190: exp, (AtomicType) reqItemType, true);
191: try {
192: if (exp instanceof Value) {
193: exp = new SequenceExtent(cexp.iterate(env
194: .makeEarlyEvaluationContext()))
195: .simplify();
196: } else {
197: exp = cexp;
198: }
199: } catch (XPathException err) {
200: throw err.makeStatic();
201: }
202: itemTypeOK = true;
203: suppliedItemType = reqItemType;
204: }
205:
206: // 2b: some supplied values are untyped atomic. Convert these to the required type; but
207: // there may be other values in the sequence that won't convert and still need to be checked
208:
209: if ((suppliedItemType == Type.ANY_ATOMIC_TYPE)
210: && !(reqItemType == Type.UNTYPED_ATOMIC_TYPE || reqItemType == Type.ANY_ATOMIC_TYPE)) {
211:
212: ComputedExpression cexp = new UntypedAtomicConverter(
213: exp, (AtomicType) reqItemType, false);
214: try {
215: if (exp instanceof Value) {
216: exp = new SequenceExtent(cexp.iterate(env
217: .makeEarlyEvaluationContext()))
218: .simplify();
219: } else {
220: exp = cexp;
221: }
222: } catch (XPathException err) {
223: throw err.makeStatic();
224: }
225: }
226:
227: // Rule 3a: numeric promotion decimal -> float -> double
228:
229: int rt = ((AtomicType) reqItemType).getFingerprint();
230: if (rt == StandardNames.XS_DOUBLE
231: || rt == StandardNames.XS_FLOAT) {
232: if (th.relationship(suppliedItemType,
233: Type.NUMBER_TYPE) != TypeHierarchy.DISJOINT) {
234: exp = new NumericPromoter(exp, rt);
235: try {
236: exp = exp.simplify(env).typeCheck(env,
237: AnyItemType.getInstance());
238: } catch (XPathException err) {
239: throw err.makeStatic();
240: }
241: suppliedItemType = (rt == StandardNames.XS_DOUBLE ? Type.DOUBLE_TYPE
242: : Type.FLOAT_TYPE);
243: suppliedCard = -1;
244: }
245: }
246:
247: // Rule 3b: promotion from anyURI -> string
248:
249: if (rt == Type.STRING
250: && th.isSubType(suppliedItemType,
251: Type.ANY_URI_TYPE)) {
252: suppliedItemType = Type.STRING_TYPE;
253: itemTypeOK = true;
254: // we don't generate code to do a run-time type conversion; rather, we rely on
255: // operators and functions that accept a string to also accept an xs:anyURI. This
256: // is straightforward, because anyURIValue is a subclass of StringValue
257: }
258:
259: }
260: }
261:
262: // If both the cardinality and item type are statically OK, return now.
263: if (itemTypeOK && cardOK) {
264: return exp;
265: }
266:
267: // If we haven't evaluated the cardinality of the supplied expression, do it now
268: if (suppliedCard == -1) {
269: suppliedCard = exp.getCardinality();
270: if (!cardOK) {
271: cardOK = Cardinality.subsumes(reqCard, suppliedCard);
272: }
273: }
274:
275: // If an empty sequence was explicitly supplied, and empty sequence is allowed,
276: // then the item type doesn't matter
277: if (cardOK && suppliedCard == StaticProperty.EMPTY) {
278: return exp;
279: }
280:
281: // If the supplied value is () and () isn't allowed, fail now
282: if (suppliedCard == StaticProperty.EMPTY
283: && ((reqCard & StaticProperty.ALLOWS_ZERO) == 0)) {
284: StaticError err = new StaticError(
285: "An empty sequence is not allowed as the "
286: + role.getMessage(), getLocator(supplied,
287: role));
288: err.setErrorCode(role.getErrorCode());
289: err.setIsTypeError(true);
290: throw err;
291: }
292:
293: // Try a static type check. We only throw it out if the call cannot possibly succeed.
294:
295: int relation = (itemTypeOK ? TypeHierarchy.SUBSUMED_BY : th
296: .relationship(suppliedItemType, reqItemType));
297: if (relation == TypeHierarchy.DISJOINT) {
298: // The item types may be disjoint, but if both the supplied and required types permit
299: // an empty sequence, we can't raise a static error. Raise a warning instead.
300: if (Cardinality.allowsZero(suppliedCard)
301: && Cardinality.allowsZero(reqCard)) {
302: if (suppliedCard != StaticProperty.EMPTY) {
303: String msg = "Required item type of "
304: + role.getMessage()
305: + " is "
306: + reqItemType.toString(env.getNamePool())
307: + "; supplied value has item type "
308: + suppliedItemType.toString(env
309: .getNamePool())
310: + ". The expression can succeed only if the supplied value is an empty sequence.";
311: env.issueWarning(msg, getLocator(supplied, role));
312: }
313: } else {
314: StaticError err = new StaticError(
315: "Required item type of "
316: + role.getMessage()
317: + " is "
318: + reqItemType.toString(env
319: .getNamePool())
320: + "; supplied value has item type "
321: + suppliedItemType.toString(env
322: .getNamePool()), getLocator(
323: supplied, role));
324: err.setErrorCode(role.getErrorCode());
325: err.setIsTypeError(true);
326: throw err;
327: }
328: }
329:
330: // Unless the type is guaranteed to match, add a dynamic type check,
331: // unless the value is already known in which case we might as well report
332: // the error now.
333:
334: if (!(relation == TypeHierarchy.SAME_TYPE || relation == TypeHierarchy.SUBSUMED_BY)) {
335: if (exp instanceof Value) {
336: StaticError err = new StaticError(
337: "Required item type of "
338: + role.getMessage()
339: + " is "
340: + reqItemType.toString(env
341: .getNamePool())
342: + "; supplied value has item type "
343: + suppliedItemType.toString(env
344: .getNamePool()), getLocator(
345: supplied, role));
346: err.setErrorCode(role.getErrorCode());
347: err.setIsTypeError(true);
348: throw err;
349: }
350: ComputedExpression cexp = new ItemChecker(exp, reqItemType,
351: role);
352: cexp.adoptChildExpression(exp);
353: exp = cexp;
354: }
355:
356: if (!cardOK) {
357: if (exp instanceof Value) {
358: StaticError err = new StaticError(
359: "Required cardinality of " + role.getMessage()
360: + " is "
361: + Cardinality.toString(reqCard)
362: + "; supplied value has cardinality "
363: + Cardinality.toString(suppliedCard),
364: getLocator(supplied, role));
365: err.setIsTypeError(true);
366: err.setErrorCode(role.getErrorCode());
367: throw err;
368: } else {
369: ComputedExpression cexp = CardinalityChecker
370: .makeCardinalityChecker(exp, reqCard, role);
371: cexp.adoptChildExpression(exp);
372: exp = cexp;
373: }
374: }
375:
376: return exp;
377: }
378:
379: /**
380: * Check an expression against a required type, modifying it if necessary. This
381: * is a variant of the method {@link #staticTypeCheck} used for expressions that
382: * declare variables in XQuery. In these contexts, conversions such as numeric
383: * type promotion and atomization are not allowed.
384: *
385: * @param supplied The expression to be type-checked
386: * @param req The required type for the context in which the expression is used
387: * @param role Information about the role of the subexpression within the
388: * containing expression, used to provide useful error messages
389: * @param env The static context containing the types being checked. At present
390: * this is used only to locate a NamePool
391: * @return The original expression if it is type-safe, or the expression
392: * wrapped in a run-time type checking expression if not.
393: * @throws net.sf.saxon.trans.StaticError if the supplied type is statically inconsistent with the
394: * required type (that is, if they have no common subtype)
395: */
396:
397: public static Expression strictTypeCheck(Expression supplied,
398: SequenceType req, RoleLocator role, StaticContext env)
399: throws StaticError {
400:
401: // System.err.println("Strict Type Check on expression (requiredType = " + req + "):"); supplied.display(10);
402:
403: Expression exp = supplied;
404: final TypeHierarchy th = env.getNamePool().getTypeHierarchy();
405:
406: ItemType reqItemType = req.getPrimaryType();
407: int reqCard = req.getCardinality();
408:
409: ItemType suppliedItemType = null;
410: // item type of the supplied expression: null means not yet calculated
411: int suppliedCard = -1;
412: // cardinality of the supplied expression: -1 means not yet calculated
413:
414: boolean cardOK = (reqCard == StaticProperty.ALLOWS_ZERO_OR_MORE);
415: // Unless the required cardinality is zero-or-more (no constraints).
416: // check the static cardinality of the supplied expression
417: if (!cardOK) {
418: suppliedCard = exp.getCardinality();
419: cardOK = Cardinality.subsumes(reqCard, suppliedCard);
420: }
421:
422: boolean itemTypeOK = req.getPrimaryType() instanceof AnyItemType;
423: // Unless the required item type and content type are ITEM (no constraints)
424: // check the static item type against the supplied expression.
425: // NOTE: we don't currently do any static inference regarding the content type
426: if (!itemTypeOK) {
427: suppliedItemType = exp.getItemType(th);
428: int relation = th.relationship(reqItemType,
429: suppliedItemType);
430: itemTypeOK = relation == TypeHierarchy.SAME_TYPE
431: || relation == TypeHierarchy.SUBSUMES;
432: }
433:
434: // If both the cardinality and item type are statically OK, return now.
435: if (itemTypeOK && cardOK) {
436: return exp;
437: }
438:
439: // If we haven't evaluated the cardinality of the supplied expression, do it now
440: if (suppliedCard == -1) {
441: if (suppliedItemType instanceof NoNodeTest) {
442: suppliedCard = StaticProperty.EMPTY;
443: } else {
444: suppliedCard = exp.getCardinality();
445: }
446: if (!cardOK) {
447: cardOK = Cardinality.subsumes(reqCard, suppliedCard);
448: }
449: }
450:
451: // If an empty sequence was explicitly supplied, and empty sequence is allowed,
452: // then the item type doesn't matter
453: if (cardOK && suppliedCard == StaticProperty.EMPTY) {
454: return exp;
455: }
456:
457: // If we haven't evaluated the item type of the supplied expression, do it now
458: if (suppliedItemType == null) {
459: suppliedItemType = exp.getItemType(th);
460: }
461:
462: if (suppliedCard == StaticProperty.EMPTY
463: && ((reqCard & StaticProperty.ALLOWS_ZERO) == 0)) {
464: StaticError err = new StaticError(
465: "An empty sequence is not allowed as the "
466: + role.getMessage(), getLocator(supplied,
467: role));
468: err.setErrorCode(role.getErrorCode());
469: err.setIsTypeError(true);
470: throw err;
471: }
472:
473: // Try a static type check. We only throw it out if the call cannot possibly succeed.
474:
475: int relation = th.relationship(suppliedItemType, reqItemType);
476: if (relation == TypeHierarchy.DISJOINT) {
477: // The item types may be disjoint, but if both the supplied and required types permit
478: // an empty sequence, we can't raise a static error. Raise a warning instead.
479: if (Cardinality.allowsZero(suppliedCard)
480: && Cardinality.allowsZero(reqCard)) {
481: if (suppliedCard != StaticProperty.EMPTY) {
482: String msg = "Required item type of "
483: + role.getMessage()
484: + " is "
485: + reqItemType.toString(env.getNamePool())
486: + "; supplied value has item type "
487: + suppliedItemType.toString(env
488: .getNamePool())
489: + ". The expression can succeed only if the supplied value is an empty sequence.";
490: env.issueWarning(msg, getLocator(supplied, role));
491: }
492: } else {
493: StaticError err = new StaticError(
494: "Required item type of "
495: + role.getMessage()
496: + " is "
497: + reqItemType.toString(env
498: .getNamePool())
499: + "; supplied value has item type "
500: + suppliedItemType.toString(env
501: .getNamePool()), getLocator(
502: supplied, role));
503: err.setErrorCode(role.getErrorCode());
504: err.setIsTypeError(true);
505: throw err;
506: }
507: }
508:
509: // Unless the type is guaranteed to match, add a dynamic type check,
510: // unless the value is already known in which case we might as well report
511: // the error now.
512:
513: if (!(relation == TypeHierarchy.SAME_TYPE || relation == TypeHierarchy.SUBSUMED_BY)) {
514: ComputedExpression cexp = new ItemChecker(exp, reqItemType,
515: role);
516: cexp.adoptChildExpression(exp);
517: exp = cexp;
518: }
519:
520: if (!cardOK) {
521: if (exp instanceof Value) {
522: StaticError err = new StaticError(
523: "Required cardinality of " + role.getMessage()
524: + " is "
525: + Cardinality.toString(reqCard)
526: + "; supplied value has cardinality "
527: + Cardinality.toString(suppliedCard),
528: getLocator(supplied, role));
529: err.setIsTypeError(true);
530: err.setErrorCode(role.getErrorCode());
531: throw err;
532: } else {
533: ComputedExpression cexp = CardinalityChecker
534: .makeCardinalityChecker(exp, reqCard, role);
535: cexp.adoptChildExpression(exp);
536: exp = cexp;
537: }
538: }
539:
540: return exp;
541: }
542:
543: /**
544: * Get a SourceLocator given an expression and a role
545: */
546:
547: private static SourceLocator getLocator(Expression supplied,
548: RoleLocator role) {
549: SourceLocator loc = ExpressionTool.getLocator(supplied);
550: if (loc == null) {
551: loc = role.getSourceLocator();
552: }
553: return loc;
554: }
555:
556: /**
557: * Test whether a given value conforms to a given type
558: * @param val the value
559: * @param requiredType the required type
560: * @param config
561: * @return a DynamicError describing the error condition if the value doesn't conform;
562: * or null if it does.
563: */
564:
565: public static DynamicError testConformance(Value val,
566: SequenceType requiredType, Configuration config) {
567: ItemType reqItemType = requiredType.getPrimaryType();
568: final TypeHierarchy th = config.getNamePool()
569: .getTypeHierarchy();
570: if (!th.isSubType(val.getItemType(th), reqItemType)) {
571: DynamicError err = new DynamicError(
572: "Global parameter requires type " + reqItemType
573: + "; supplied value has type "
574: + val.getItemType(th));
575: err.setIsTypeError(true);
576: return err;
577: }
578: int reqCardinality = requiredType.getCardinality();
579: if (!Cardinality.subsumes(reqCardinality, val.getCardinality())) {
580: DynamicError err = new DynamicError(
581: "Supplied value of external parameter does not match the required cardinality");
582: err.setIsTypeError(true);
583: return err;
584: }
585: return null;
586: }
587:
588: /**
589: * Test whether a given expression is capable of returning a value that has an effective boolean
590: * value.
591: * @return null if the expression is OK (optimistically), an exception object if not
592: */
593:
594: public static XPathException ebvError(Expression exp,
595: TypeHierarchy th) {
596: if (Cardinality.allowsZero(exp.getCardinality())) {
597: return null;
598: }
599: ItemType t = exp.getItemType(th);
600: if (th.relationship(t, Type.NODE_TYPE) == TypeHierarchy.DISJOINT
601: && th.relationship(t, Type.BOOLEAN_TYPE) == TypeHierarchy.DISJOINT
602: && th.relationship(t, Type.STRING_TYPE) == TypeHierarchy.DISJOINT
603: && th.relationship(t, Type.UNTYPED_ATOMIC_TYPE) == TypeHierarchy.DISJOINT
604: && th.relationship(t, Type.NUMBER_TYPE) == TypeHierarchy.DISJOINT) {
605: DynamicError err = new DynamicError(
606: "Effective boolean value is defined only for sequences containing "
607: + "booleans, strings, numbers, or nodes");
608: err.setErrorCode("FORG0006");
609: err.setIsTypeError(true);
610: return err;
611: }
612: return null;
613: }
614: }
615:
616: //
617: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
618: // you may not use this file except in compliance with the License. You may obtain a copy of the
619: // License at http://www.mozilla.org/MPL/
620: //
621: // Software distributed under the License is distributed on an "AS IS" basis,
622: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
623: // See the License for the specific language governing rights and limitations under the License.
624: //
625: // The Original Code is: all this file.
626: //
627: // The Initial Developer of the Original Code is Michael H. Kay
628: //
629: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
630: //
631: // Contributor(s): none.
632: //
|