0001: package net.sf.saxon.value;
0002:
0003: import net.sf.saxon.Configuration;
0004: import net.sf.saxon.Controller;
0005: import net.sf.saxon.event.Builder;
0006: import net.sf.saxon.event.PipelineConfiguration;
0007: import net.sf.saxon.event.Sender;
0008: import net.sf.saxon.event.SequenceReceiver;
0009: import net.sf.saxon.expr.*;
0010: import net.sf.saxon.functions.Aggregate;
0011: import net.sf.saxon.om.*;
0012: import net.sf.saxon.style.StandardNames;
0013: import net.sf.saxon.tinytree.TinyBuilder;
0014: import net.sf.saxon.trans.DynamicError;
0015: import net.sf.saxon.trans.XPathException;
0016: import net.sf.saxon.type.*;
0017:
0018: import javax.xml.transform.Source;
0019: import javax.xml.transform.dom.DOMSource;
0020: import java.io.PrintStream;
0021: import java.io.Serializable;
0022: import java.lang.reflect.Array;
0023: import java.lang.reflect.InvocationTargetException;
0024: import java.lang.reflect.Method;
0025: import java.math.BigDecimal;
0026: import java.math.BigInteger;
0027: import java.net.URI;
0028: import java.net.URL;
0029: import java.util.*;
0030:
0031: /**
0032: * A value is the result of an expression but it is also an expression in its own right.
0033: * Note that every value can be regarded as a sequence - in many cases, a sequence of
0034: * length one.
0035: */
0036:
0037: public abstract class Value implements Expression, Serializable,
0038: ValueRepresentation {
0039:
0040: /**
0041: * Static method to make a Value from a given Item (which may be either an AtomicValue
0042: * or a NodeInfo
0043: * @param val The supplied value, or null, indicating the empty sequence.
0044: * @return The supplied value, if it is a value, or a SingletonNode that
0045: * wraps the item, if it is a node. If the supplied value was null,
0046: * return an EmptySequence
0047: */
0048:
0049: public static Value asValue(ValueRepresentation val) {
0050: if (val instanceof Value) {
0051: return (Value) val;
0052: } else if (val == null) {
0053: return EmptySequence.getInstance();
0054: } else {
0055: return new SingletonNode((NodeInfo) val);
0056: }
0057: }
0058:
0059: /**
0060: * Static method to make an Item from a Value
0061: * @param value the value to be converted
0062: * @param context the context. It is probably safe to set this to null.
0063: * @return null if the value is an empty sequence; or the only item in the value
0064: * if it is a singleton sequence
0065: * @throws XPathException if the Value contains multiple items
0066: */
0067:
0068: public static Item asItem(ValueRepresentation value,
0069: XPathContext context) throws XPathException {
0070: if (value instanceof Item) {
0071: return (Item) value;
0072: } else if (value instanceof EmptySequence) {
0073: return null;
0074: } else if (value instanceof SingletonNode) {
0075: return ((SingletonNode) value).getNode();
0076: } else if (value instanceof AtomicValue) {
0077: return (AtomicValue) value;
0078: } else if (value instanceof Closure) {
0079: return ((Closure) value).evaluateItem(context);
0080: } else {
0081: SequenceIterator iter = Value.getIterator(value);
0082: Item item = iter.next();
0083: if (item == null) {
0084: return null;
0085: } else if (iter.next() != null) {
0086: throw new AssertionError(
0087: "Attempting to access a sequence as an item");
0088: } else {
0089: return item;
0090: }
0091: }
0092: }
0093:
0094: /**
0095: * Static method to get an Iterator over any ValueRepresentation (which may be either a Value
0096: * or a NodeInfo
0097: * @param val The supplied value, or null, indicating the empty sequence.
0098: * @param context The evaluation context. This may be null. It should always be possible to
0099: * iterate over a value without supplying a context, but sometimes the context
0100: * can provide access to better error information
0101: * @return The supplied value, if it is a value, or a SingletonNode that
0102: * wraps the item, if it is a node. If the supplied value was null,
0103: * return an EmptySequence
0104: */
0105:
0106: public static SequenceIterator asIterator(ValueRepresentation val,
0107: XPathContext context) throws XPathException {
0108: if (val instanceof Value) {
0109: return ((Value) val).iterate(context);
0110: } else if (val == null) {
0111: return EmptyIterator.getInstance();
0112: } else {
0113: return SingletonIterator.makeIterator((NodeInfo) val);
0114: }
0115: }
0116:
0117: /**
0118: * Static method to convert strings to numbers. Might as well go here as anywhere else.
0119: * @param s the String to be converted
0120: * @return a double representing the value of the String
0121: * @throws NumberFormatException if the value cannot be converted
0122: */
0123:
0124: public static double stringToNumber(CharSequence s)
0125: throws NumberFormatException {
0126: String n = trimWhitespace(s).toString();
0127: if ("INF".equals(n)) {
0128: return Double.POSITIVE_INFINITY;
0129: } else if ("-INF".equals(n)) {
0130: return Double.NEGATIVE_INFINITY;
0131: } else if ("NaN".equals(n)) {
0132: return Double.NaN;
0133: } else {
0134: return Double.parseDouble(n);
0135: }
0136: }
0137:
0138: /**
0139: * Normalize whitespace as defined in XML Schema
0140: */
0141:
0142: public static CharSequence normalizeWhitespace(CharSequence in) {
0143: FastStringBuffer sb = new FastStringBuffer(in.length());
0144: for (int i = 0; i < in.length(); i++) {
0145: char c = in.charAt(i);
0146: switch (c) {
0147: case '\n':
0148: case '\r':
0149: case '\t':
0150: sb.append(' ');
0151: break;
0152: default:
0153: sb.append(c);
0154: break;
0155: }
0156: }
0157: return sb;
0158: }
0159:
0160: /**
0161: * Collapse whitespace as defined in XML Schema
0162: */
0163:
0164: public static CharSequence collapseWhitespace(CharSequence in) {
0165: if (in.length() == 0) {
0166: return in;
0167: }
0168:
0169: FastStringBuffer sb = new FastStringBuffer(in.length());
0170: boolean inWhitespace = true;
0171: int i = 0;
0172: for (; i < in.length(); i++) {
0173: char c = in.charAt(i);
0174: switch (c) {
0175: case '\n':
0176: case '\r':
0177: case '\t':
0178: case ' ':
0179: if (inWhitespace) {
0180: // remove the whitespace
0181: } else {
0182: sb.append(' ');
0183: inWhitespace = true;
0184: }
0185: break;
0186: default:
0187: sb.append(c);
0188: inWhitespace = false;
0189: break;
0190: }
0191: }
0192: if (sb.charAt(sb.length() - 1) == ' ') {
0193: sb.setLength(sb.length() - 1);
0194: }
0195: return sb;
0196: }
0197:
0198: /**
0199: * Remove leading and trailing whitespace. This has the same effect as collapseWhitespace,
0200: * but is cheaper, for use by data types that do not allow internal whitespace.
0201: * @param in the input string whose whitespace is to be removed
0202: * @return the result of removing excess whitespace
0203: */
0204: public static CharSequence trimWhitespace(CharSequence in) {
0205: if (in.length() == 0) {
0206: return in;
0207: }
0208: int first = 0;
0209: int last = in.length() - 1;
0210: while (in.charAt(first) <= 0x20) {
0211: if (first++ >= last) {
0212: return "";
0213: }
0214: }
0215: while (in.charAt(last) <= 0x20) {
0216: last--;
0217: }
0218: return in.subSequence(first, last + 1);
0219: }
0220:
0221: /**
0222: * Get a SequenceIterator over a ValueRepresentation
0223: */
0224:
0225: public static SequenceIterator getIterator(ValueRepresentation val)
0226: throws XPathException {
0227: if (val instanceof Value) {
0228: return ((Value) val).iterate(null);
0229: } else if (val instanceof NodeInfo) {
0230: return SingletonIterator.makeIterator((NodeInfo) val);
0231: } else if (val == null) {
0232: throw new AssertionError(
0233: "Value of variable is undefined (null)");
0234: } else {
0235: throw new AssertionError("Unknown value representation "
0236: + val.getClass());
0237: }
0238: }
0239:
0240: /**
0241: * Simplify an expression
0242: * @return for a Value, this always returns the value unchanged
0243: */
0244:
0245: public final Expression simplify(StaticContext env) {
0246: return this ;
0247: }
0248:
0249: /**
0250: * TypeCheck an expression
0251: * @return for a Value, this always returns the value unchanged
0252: */
0253:
0254: public final Expression typeCheck(StaticContext env,
0255: ItemType contextItemType) {
0256: return this ;
0257: }
0258:
0259: /**
0260: * Optimize an expression
0261: * @return for a Value, this always returns the value unchanged
0262: */
0263:
0264: public final Expression optimize(Optimizer opt, StaticContext env,
0265: ItemType contextItemType) {
0266: return this ;
0267: }
0268:
0269: /**
0270: * Determine the data type of the items in the expression, if possible
0271: * @return for the default implementation: AnyItemType (not known)
0272: * @param th The TypeHierarchy. Can be null if the target is an AtomicValue.
0273: */
0274:
0275: public ItemType getItemType(TypeHierarchy th) {
0276: return AnyItemType.getInstance();
0277: }
0278:
0279: /**
0280: * Determine the cardinality
0281: */
0282:
0283: public int getCardinality() {
0284: try {
0285: SequenceIterator iter = iterate(null);
0286: Item next = iter.next();
0287: if (next == null) {
0288: return StaticProperty.EMPTY;
0289: } else {
0290: if (iter.next() != null) {
0291: return StaticProperty.ALLOWS_ONE_OR_MORE;
0292: } else {
0293: return StaticProperty.EXACTLY_ONE;
0294: }
0295: }
0296: } catch (XPathException err) {
0297: // can't actually happen
0298: return StaticProperty.ALLOWS_ZERO_OR_MORE;
0299: }
0300: }
0301:
0302: /**
0303: * Get the sub-expressions of this expression.
0304: * @return for a Value, this always returns an empty array
0305: */
0306:
0307: public final Iterator iterateSubExpressions() {
0308: return Collections.EMPTY_LIST.iterator();
0309: }
0310:
0311: /**
0312: * Get the expression that immediately contains this expression. This method
0313: * returns null for an outermost expression; it also return null in the case
0314: * of literal values. For an XPath expression occurring within an XSLT stylesheet,
0315: * this method returns the XSLT instruction containing the XPath expression.
0316: * @return the expression that contains this expression, if known; return null
0317: * if there is no containing expression or if the containing expression is unknown.
0318: */
0319:
0320: public final Container getParentExpression() {
0321: return null;
0322: }
0323:
0324: /**
0325: * Get the static properties of this expression (other than its type). For a
0326: * Value, the only special property is {@link StaticProperty#NON_CREATIVE}.
0327: * @return {@link StaticProperty#NON_CREATIVE}
0328: */
0329:
0330: public int getSpecialProperties() {
0331: return StaticProperty.NON_CREATIVE;
0332: }
0333:
0334: /**
0335: * Offer promotion for this subexpression. Values (constant expressions)
0336: * are never promoted
0337: * @param offer details of the offer, for example the offer to move
0338: * expressions that don't depend on the context to an outer level in
0339: * the containing expression
0340: * @return For a Value, this always returns the value unchanged
0341: */
0342:
0343: public final Expression promote(PromotionOffer offer) {
0344: return this ;
0345: }
0346:
0347: /**
0348: * Determine which aspects of the context the expression depends on. The result is
0349: * a bitwise-or'ed value composed from constants such as StaticProperty.VARIABLES and
0350: * StaticProperty.CURRENT_NODE
0351: * @return for a Value, this always returns zero.
0352: */
0353:
0354: public final int getDependencies() {
0355: return 0;
0356: }
0357:
0358: /**
0359: * Get the n'th item in the sequence (starting from 0). This is defined for all
0360: * Values, but its real benefits come for a sequence Value stored extensionally
0361: * (or for a MemoClosure, once all the values have been read)
0362: */
0363:
0364: public Item itemAt(int n) throws XPathException {
0365: if ((getImplementationMethod() & EVALUATE_METHOD) != 0) {
0366: if (n == 0) {
0367: Item item = evaluateItem(null);
0368: return (item == null ? null : item);
0369: } else {
0370: return null;
0371: }
0372: }
0373: if (n < 0) {
0374: return null;
0375: }
0376: int i = 0; // indexing is zero-based
0377: SequenceIterator iter = iterate(null);
0378: while (true) {
0379: Item item = iter.next();
0380: if (item == null) {
0381: return null;
0382: }
0383: if (i++ == n) {
0384: return item;
0385: }
0386: }
0387: }
0388:
0389: /**
0390: * Get the length of the sequence
0391: */
0392:
0393: public int getLength() throws XPathException {
0394: return Aggregate.count(iterate(null));
0395: }
0396:
0397: /**
0398: * Evaluate as a singleton item (or empty sequence). Note: this implementation returns
0399: * the first item in the sequence. The method should not be used unless appropriate type-checking
0400: * has been done to ensure that the value will be a singleton.
0401: */
0402:
0403: public Item evaluateItem(XPathContext context)
0404: throws XPathException {
0405: return iterate(context).next();
0406: }
0407:
0408: /**
0409: * Process the value as an instruction, without returning any tail calls
0410: * @param context The dynamic context, giving access to the current node,
0411: * the current variables, etc.
0412: */
0413:
0414: public void process(XPathContext context) throws XPathException {
0415: SequenceIterator iter = iterate(context);
0416: SequenceReceiver out = context.getReceiver();
0417: while (true) {
0418: Item it = iter.next();
0419: if (it == null)
0420: break;
0421: out.append(it, 0, NodeInfo.ALL_NAMESPACES);
0422: }
0423: }
0424:
0425: /**
0426: * Convert the value to a string, using the serialization rules.
0427: * For atomic values this is the same as a cast; for sequence values
0428: * it gives a space-separated list.
0429: * @throws XPathException The method can fail if evaluation of the value
0430: * has been deferred, and if a failure occurs during the deferred evaluation.
0431: * No failure is possible in the case of an AtomicValue.
0432: */
0433:
0434: public String getStringValue() throws XPathException {
0435: FastStringBuffer sb = new FastStringBuffer(1024);
0436: SequenceIterator iter = iterate(null);
0437: Item item = iter.next();
0438: if (item != null) {
0439: while (true) {
0440: sb.append(item.getStringValueCS());
0441: item = iter.next();
0442: if (item == null) {
0443: break;
0444: }
0445: sb.append(' ');
0446: }
0447: }
0448: return sb.toString();
0449: }
0450:
0451: /**
0452: * Evaluate an expression as a String. This function must only be called in contexts
0453: * where it is known that the expression will return a single string (or where an empty sequence
0454: * is to be treated as a zero-length string). Implementations should not attempt to convert
0455: * the result to a string, other than converting () to "". This method is used mainly to
0456: * evaluate expressions produced by compiling an attribute value template.
0457: *
0458: * @exception XPathException if any dynamic error occurs evaluating the
0459: * expression
0460: * @exception ClassCastException if the result type of the
0461: * expression is not xs:string?
0462: * @param context The context in which the expression is to be evaluated
0463: * @return the value of the expression, evaluated in the current context.
0464: * The expression must return a string or (); if the value of the
0465: * expression is (), this method returns "".
0466: */
0467:
0468: public String evaluateAsString(XPathContext context)
0469: throws XPathException {
0470: AtomicValue value = (AtomicValue) evaluateItem(context);
0471: if (value == null)
0472: return "";
0473: return value.getStringValue();
0474: }
0475:
0476: /**
0477: * Get the effective boolean value of the expression. This returns false if the value
0478: * is the empty sequence, a zero-length string, a number equal to zero, or the boolean
0479: * false. Otherwise it returns true.
0480: *
0481: * @param context The context in which the expression is to be evaluated
0482: * @exception XPathException if any dynamic error occurs evaluating the
0483: * expression
0484: * @return the effective boolean value
0485: */
0486:
0487: public boolean effectiveBooleanValue(XPathContext context)
0488: throws XPathException {
0489: return ExpressionTool.effectiveBooleanValue(iterate(context));
0490: }
0491:
0492: /**
0493: * Compare two (sequence) values for equality. This supports identity constraints in XML Schema,
0494: * which allow list-valued elements and attributes to participate in key and uniqueness constraints.
0495: * This method returns false if any error occurs during the comparison, or if any of the items
0496: * in either sequence is a node rather than an atomic value.
0497: */
0498:
0499: public boolean equals(Object obj) {
0500: try {
0501: if (obj instanceof Value) {
0502: SequenceIterator iter1 = iterate(null);
0503: SequenceIterator iter2 = ((Value) obj).iterate(null);
0504: while (true) {
0505: Item item1 = iter1.next();
0506: Item item2 = iter2.next();
0507: if (item1 == null && item2 == null) {
0508: return true;
0509: }
0510: if (item1 == null || item2 == null) {
0511: return false;
0512: }
0513: if (item1 instanceof NodeInfo
0514: || item2 instanceof NodeInfo) {
0515: return false;
0516: }
0517: if (!item1.equals(item2)) {
0518: return false;
0519: }
0520: }
0521: } else {
0522: return false;
0523: }
0524: } catch (XPathException e) {
0525: return false;
0526: }
0527: }
0528:
0529: /**
0530: * Compare two (sequence) values for equality. This supports identity constraints in XML Schema,
0531: * which allow list-valued elements and attributes to participate in key and uniqueness constraints.
0532: * This method returns false if any error occurs during the comparison, or if any of the items
0533: * in either sequence is a node rather than an atomic value.
0534: */
0535:
0536: public boolean schemaEquals(Value obj) {
0537: try {
0538: SequenceIterator iter1 = iterate(null);
0539: SequenceIterator iter2 = obj.iterate(null);
0540: while (true) {
0541: Item item1 = iter1.next();
0542: Item item2 = iter2.next();
0543: if (item1 == null && item2 == null) {
0544: return true;
0545: }
0546: if (item1 == null || item2 == null) {
0547: return false;
0548: }
0549: if (item1 instanceof NodeInfo
0550: || item2 instanceof NodeInfo) {
0551: return false;
0552: }
0553: if (!((AtomicValue) item1)
0554: .schemaEquals((AtomicValue) item2)) {
0555: return false;
0556: }
0557: }
0558: } catch (XPathException e) {
0559: return false;
0560: }
0561: }
0562:
0563: /**
0564: * Return a hash code to support the equals() function
0565: */
0566:
0567: public int hashCode() {
0568: try {
0569: int hash = 0x06639662; // arbitrary seed
0570: SequenceIterator iter = iterate(null);
0571: while (true) {
0572: Item item = iter.next();
0573: if (item == null) {
0574: return hash;
0575: }
0576: hash ^= item.hashCode();
0577: }
0578: } catch (XPathException e) {
0579: return 0;
0580: }
0581: }
0582:
0583: /**
0584: * Check statically that the results of the expression are capable of constructing the content
0585: * of a given schema type.
0586: * @param parentType The schema type
0587: * @param env the static context
0588: * @param whole
0589: * @throws XPathException if the expression doesn't match the required content type
0590: */
0591:
0592: public void checkPermittedContents(SchemaType parentType,
0593: StaticContext env, boolean whole) throws XPathException {
0594: return;
0595: }
0596:
0597: /**
0598: * Reduce a value to its simplest form. If the value is a closure or some other form of deferred value
0599: * such as a FunctionCallPackage, then it is reduced to a SequenceExtent. If it is a SequenceExtent containing
0600: * a single item, then it is reduced to that item. One consequence that is exploited by class FilterExpression
0601: * is that if the value is a singleton numeric value, then the result will be an instance of NumericValue
0602: */
0603:
0604: public Value reduce() throws XPathException {
0605: return this ;
0606: }
0607:
0608: /**
0609: * Convert to Java object (for passing to external functions)
0610: */
0611:
0612: public Object convertToJava(Class target, XPathContext context)
0613: throws XPathException {
0614:
0615: if (target == Object.class) {
0616: List list = new ArrayList(20);
0617: return convertToJavaList(list, context);
0618: }
0619:
0620: // See if the extension function is written to accept native Saxon objects
0621:
0622: if (target.isAssignableFrom(this .getClass())) {
0623: return this ;
0624: } else if (target.isAssignableFrom(SequenceIterator.class)) {
0625: return iterate(context);
0626: }
0627:
0628: // Offer the object to registered external object models
0629:
0630: if ((this instanceof ObjectValue || !(this instanceof AtomicValue))
0631: && !(this instanceof EmptySequence)) {
0632: List externalObjectModels = context.getConfiguration()
0633: .getExternalObjectModels();
0634: for (int m = 0; m < externalObjectModels.size(); m++) {
0635: ExternalObjectModel model = (ExternalObjectModel) externalObjectModels
0636: .get(m);
0637: Object object = model.convertXPathValueToObject(this ,
0638: target, context);
0639: if (object != null) {
0640: return object;
0641: }
0642: }
0643: }
0644:
0645: if (Collection.class.isAssignableFrom(target)) {
0646: Collection list;
0647: if (target.isAssignableFrom(ArrayList.class)) {
0648: list = new ArrayList(100);
0649: } else {
0650: try {
0651: list = (Collection) target.newInstance();
0652: } catch (InstantiationException e) {
0653: DynamicError de = new DynamicError(
0654: "Cannot instantiate collection class "
0655: + target);
0656: de.setXPathContext(context);
0657: throw de;
0658: } catch (IllegalAccessException e) {
0659: DynamicError de = new DynamicError(
0660: "Cannot access collection class " + target);
0661: de.setXPathContext(context);
0662: throw de;
0663: }
0664: }
0665: return convertToJavaList(list, context);
0666: } else if (target.isArray()) {
0667: Class component = target.getComponentType();
0668: if (component.isAssignableFrom(Item.class)
0669: || component.isAssignableFrom(NodeInfo.class)
0670: || component.isAssignableFrom(DocumentInfo.class)) {
0671: Value extent = this ;
0672: if (extent instanceof Closure) {
0673: extent = SequenceExtent.makeSequenceExtent(extent
0674: .iterate(null));
0675: }
0676: int length = extent.getLength();
0677: Object array = Array.newInstance(component, length);
0678: SequenceIterator iter = extent.iterate(null);
0679: for (int i = 0; i < length; i++) {
0680: Item item = iter.next();
0681: try {
0682: Array.set(array, i, item);
0683: } catch (IllegalArgumentException err) {
0684: DynamicError d = new DynamicError(
0685: "Item "
0686: + i
0687: + " in supplied sequence cannot be converted "
0688: + "to the component type of the Java array ("
0689: + component + ')', err);
0690: d.setXPathContext(context);
0691: throw d;
0692: }
0693: }
0694: return array;
0695: } else /* if (!(this instanceof AtomicValue)) */{
0696: // try atomizing the sequence, unless this is a single atomic value, in which case we've already
0697: // tried that.
0698: SequenceIterator it = Atomizer.AtomizingFunction
0699: .getAtomizingIterator(iterate(context));
0700: int length;
0701: if ((it.getProperties() & SequenceIterator.LAST_POSITION_FINDER) == 0) {
0702: SequenceExtent extent = new SequenceExtent(it);
0703: length = extent.getLength();
0704: it = extent.iterate(context);
0705: } else {
0706: length = ((LastPositionFinder) it)
0707: .getLastPosition();
0708: }
0709: Object array = Array.newInstance(component, length);
0710: for (int i = 0; i < length; i++) {
0711: try {
0712: AtomicValue val = (AtomicValue) it.next();
0713: Object jval = val.convertToJava(component,
0714: context);
0715: Array.set(array, i, jval);
0716: } catch (XPathException err) {
0717: DynamicError d = new DynamicError(
0718: "Cannot convert item in atomized sequence to the component type of the Java array",
0719: err);
0720: d.setXPathContext(context);
0721: throw d;
0722: }
0723: }
0724: return array;
0725: // } else {
0726: // DynamicError d = new DynamicError(
0727: // "Cannot convert supplied argument value to the required type");
0728: // d.setXPathContext(context);
0729: // throw d;
0730: }
0731:
0732: } else if (target.isAssignableFrom(Item.class)
0733: || target.isAssignableFrom(NodeInfo.class)
0734: || target.isAssignableFrom(DocumentInfo.class)) {
0735:
0736: // try passing the first item in the sequence provided it is the only one
0737: SequenceIterator iter = iterate(null);
0738: Item first = null;
0739: while (true) {
0740: Item next = iter.next();
0741: if (next == null) {
0742: break;
0743: }
0744: if (first != null) {
0745: DynamicError err = new DynamicError(
0746: "Sequence contains more than one value; Java method expects only one");
0747: err.setXPathContext(context);
0748: throw err;
0749: }
0750: first = next;
0751: }
0752: if (first == null) {
0753: // sequence is empty; pass a Java null
0754: return null;
0755: }
0756: if (target.isAssignableFrom(first.getClass())) {
0757: // covers Item and NodeInfo
0758: return first;
0759: }
0760:
0761: Object n = first;
0762: while (n instanceof VirtualNode) {
0763: // If we've got a wrapper around a DOM or JDOM node, and the user wants a DOM
0764: // or JDOM node, we unwrap it
0765: Object vn = ((VirtualNode) n).getUnderlyingNode();
0766: if (target.isAssignableFrom(vn.getClass())) {
0767: return vn;
0768: } else {
0769: n = vn;
0770: }
0771: }
0772:
0773: throw new DynamicError(
0774: "Cannot convert supplied XPath value to the required type for the extension function");
0775: } else if (!(this instanceof AtomicValue)) {
0776: // try atomizing the value, unless this is an atomic value, in which case we've already tried that
0777: SequenceIterator it = Atomizer.AtomizingFunction
0778: .getAtomizingIterator(iterate(context));
0779: Item first = null;
0780: while (true) {
0781: Item next = it.next();
0782: if (next == null) {
0783: break;
0784: }
0785: if (first != null) {
0786: DynamicError err = new DynamicError(
0787: "Sequence contains more than one value; Java method expects only one");
0788: err.setXPathContext(context);
0789: throw err;
0790: }
0791: first = next;
0792: }
0793: if (first == null) {
0794: // sequence is empty; pass a Java null
0795: return null;
0796: }
0797: if (target.isAssignableFrom(first.getClass())) {
0798: return first;
0799: } else {
0800: return ((AtomicValue) first).convertToJava(target,
0801: context);
0802: }
0803: } else {
0804: throw new DynamicError(
0805: "Cannot convert supplied XPath value to the required type for the extension function");
0806: }
0807: }
0808:
0809: private Collection convertToJavaList(Collection list,
0810: XPathContext context) throws XPathException {
0811: // TODO: with JDK 1.5, check to see if the item type of the list is constrained
0812: SequenceIterator iter = iterate(null);
0813: while (true) {
0814: Item it = iter.next();
0815: if (it == null) {
0816: // if (list.size() == 0) {
0817: // // map empty sequence to null
0818: // return null;
0819: // } else {
0820: return list;
0821: // }
0822: }
0823: if (it instanceof AtomicValue) {
0824: list.add(((AtomicValue) it).convertToJava(Object.class,
0825: context));
0826: } else if (it instanceof VirtualNode) {
0827: list.add(((VirtualNode) it).getUnderlyingNode());
0828: } else {
0829: list.add(it);
0830: }
0831: }
0832: }
0833:
0834: /**
0835: * Diagnostic display of the expression
0836: */
0837:
0838: public void display(int level, NamePool pool, PrintStream out) {
0839: final TypeHierarchy th = pool.getTypeHierarchy();
0840: try {
0841: out.println(ExpressionTool.indent(level) + "sequence of "
0842: + getItemType(th).toString() + " (");
0843: SequenceIterator iter = iterate(null);
0844: while (true) {
0845: Item it = iter.next();
0846: if (it == null) {
0847: break;
0848: }
0849: if (it instanceof NodeInfo) {
0850: out.println(ExpressionTool.indent(level + 1)
0851: + "node "
0852: + Navigator.getPath(((NodeInfo) it)));
0853: } else {
0854: out.println(ExpressionTool.indent(level + 1)
0855: + it.toString());
0856: }
0857: }
0858: out.println(ExpressionTool.indent(level) + ')');
0859: } catch (XPathException err) {
0860: out.println(ExpressionTool.indent(level) + "(*error*)");
0861: }
0862: }
0863:
0864: /**
0865: * Convert a Java object to an XPath value. This method is called to handle the result
0866: * of an external function call (but only if the required type is not known),
0867: * and also to process global parameters passed to the stylesheet or query.
0868: * @param object The Java object to be converted
0869: * @param requiredType The required type of the result (if known)
0870: * @param config The Configuration: may be null, in which case certain kinds of object
0871: * (eg. DOM nodes) cannot be handled
0872: * @return the result of converting the value. If the value is null, returns null.
0873: */
0874:
0875: public static Value convertJavaObjectToXPath(Object object,
0876: SequenceType requiredType, Configuration config)
0877: throws XPathException {
0878:
0879: ItemType requiredItemType = requiredType.getPrimaryType();
0880:
0881: if (object == null) {
0882: return EmptySequence.getInstance();
0883: }
0884:
0885: // Offer the object to all the registered external object models
0886:
0887: List externalObjectModels = config.getExternalObjectModels();
0888: for (int m = 0; m < externalObjectModels.size(); m++) {
0889: ExternalObjectModel model = (ExternalObjectModel) externalObjectModels
0890: .get(m);
0891: Value val = model.convertObjectToXPathValue(object, config);
0892: if (val != null
0893: && TypeChecker.testConformance(val, requiredType,
0894: config) == null) {
0895: return val;
0896: }
0897: }
0898:
0899: if (requiredItemType instanceof ExternalObjectType) {
0900: Class theClass = ((ExternalObjectType) requiredItemType)
0901: .getJavaClass();
0902: if (theClass.isAssignableFrom(object.getClass())) {
0903: return new ObjectValue(object);
0904: } else {
0905: throw new DynamicError(
0906: "Supplied parameter value is not of class "
0907: + theClass.getName());
0908: }
0909: }
0910:
0911: Value value = convertToBestFit(object, config);
0912: return value;
0913:
0914: }
0915:
0916: private static Value convertToBestFit(Object object,
0917: Configuration config) throws XPathException {
0918: if (object instanceof String) {
0919: return StringValue.makeStringValue((String) object);
0920:
0921: } else if (object instanceof Character) {
0922: return new StringValue(object.toString());
0923:
0924: } else if (object instanceof Boolean) {
0925: return BooleanValue.get(((Boolean) object).booleanValue());
0926:
0927: } else if (object instanceof Double) {
0928: return new DoubleValue(((Double) object).doubleValue());
0929:
0930: } else if (object instanceof Float) {
0931: return new FloatValue(((Float) object).floatValue());
0932:
0933: } else if (object instanceof Short) {
0934: return new IntegerValue(((Short) object).shortValue(),
0935: (AtomicType) BuiltInSchemaFactory
0936: .getSchemaType(StandardNames.XS_SHORT));
0937: } else if (object instanceof Integer) {
0938: return new IntegerValue(((Integer) object).intValue(),
0939: (AtomicType) BuiltInSchemaFactory
0940: .getSchemaType(StandardNames.XS_INT));
0941: } else if (object instanceof Long) {
0942: return new IntegerValue(((Long) object).longValue(),
0943: (AtomicType) BuiltInSchemaFactory
0944: .getSchemaType(StandardNames.XS_LONG));
0945: } else if (object instanceof Byte) {
0946: return new IntegerValue(((Byte) object).byteValue(),
0947: (AtomicType) BuiltInSchemaFactory
0948: .getSchemaType(StandardNames.XS_BYTE));
0949:
0950: } else if (object instanceof BigInteger) {
0951: return BigIntegerValue.makeValue(((BigInteger) object));
0952:
0953: } else if (object instanceof BigDecimal) {
0954: return new DecimalValue(((BigDecimal) object));
0955:
0956: // } else if (object instanceof QName) {
0957: // return new QNameValue((QName)object);
0958: // TODO: reinstate above lines in JDK 1.5
0959: } else if (object.getClass().getName().equals(
0960: "javax.xml.namespace.QName")) {
0961: return makeQNameValue(object, config);
0962:
0963: } else if (object instanceof URI) {
0964: return new AnyURIValue(object.toString());
0965:
0966: } else if (object instanceof URL) {
0967: return new AnyURIValue(object.toString());
0968:
0969: } else if (object instanceof Closure) {
0970: // Force eager evaluation, because of problems with side-effects.
0971: // (The value might depend on data that is mutable.)
0972: return ExpressionTool.eagerEvaluate((Closure) object,
0973: config.getConversionContext());
0974:
0975: } else if (object instanceof Value) {
0976: return (Value) object;
0977:
0978: } else if (object instanceof NodeInfo) {
0979: if (((NodeInfo) object).getNamePool() != config
0980: .getNamePool()) {
0981: throw new DynamicError(
0982: "Externally-supplied node belongs to wrong NamePool");
0983: }
0984: return new SingletonNode((NodeInfo) object);
0985:
0986: } else if (object instanceof SequenceIterator) {
0987: //return new SequenceIntent((SequenceIterator)object);
0988: return Closure
0989: .makeIteratorClosure((SequenceIterator) object);
0990:
0991: } else if (object instanceof List) {
0992: Item[] array = new Item[((List) object).size()];
0993: int a = 0;
0994: for (Iterator i = ((List) object).iterator(); i.hasNext();) {
0995: Object obj = i.next();
0996: if (obj instanceof NodeInfo) {
0997: array[a++] = (NodeInfo) obj;
0998: } else {
0999: Value v = convertToBestFit(obj, config);
1000: if (v != null) {
1001: if (v instanceof Item) {
1002: array[a++] = (Item) v;
1003: } else if (v instanceof EmptySequence) {
1004: // no action
1005: } else if (v instanceof SingletonNode) {
1006: NodeInfo node = ((SingletonNode) v)
1007: .getNode();
1008: if (node != null) {
1009: array[a++] = node;
1010: }
1011: } else {
1012: throw new DynamicError(
1013: "Returned List contains an object that cannot be converted to an Item ("
1014: + obj.getClass() + ')');
1015: }
1016: }
1017: }
1018: }
1019:
1020: return new SequenceExtent(array);
1021:
1022: } else if (object instanceof Object[]) {
1023: Item[] array = new Item[((Object[]) object).length];
1024: int a = 0;
1025: for (int i = 0; i < ((Object[]) object).length; i++) {
1026: Object obj = ((Object[]) object)[i];
1027: if (obj instanceof NodeInfo) {
1028: array[a++] = (NodeInfo) obj;
1029: } else if (obj != null) {
1030: Value v = convertToBestFit(obj, config);
1031: if (v != null) {
1032: if (v instanceof Item) {
1033: array[a++] = (Item) v;
1034: } else {
1035: throw new DynamicError(
1036: "Returned array contains an object that cannot be converted to an Item ("
1037: + obj.getClass() + ')');
1038: }
1039: }
1040: }
1041: }
1042: return new SequenceExtent(array, 0, a);
1043:
1044: } else if (object instanceof long[]) {
1045: Item[] array = new Item[((long[]) object).length];
1046: for (int i = 0; i < ((long[]) object).length; i++) {
1047: array[i] = new IntegerValue(((long[]) object)[i]);
1048: }
1049: return new SequenceExtent(array);
1050:
1051: } else if (object instanceof int[]) {
1052: Item[] array = new Item[((int[]) object).length];
1053: for (int i = 0; i < ((int[]) object).length; i++) {
1054: array[i] = new IntegerValue(((int[]) object)[i]);
1055: }
1056: return new SequenceExtent(array);
1057:
1058: } else if (object instanceof short[]) {
1059: Item[] array = new Item[((short[]) object).length];
1060: for (int i = 0; i < ((short[]) object).length; i++) {
1061: array[i] = new IntegerValue(((short[]) object)[i]);
1062: }
1063: return new SequenceExtent(array);
1064:
1065: } else if (object instanceof byte[]) { // interpret this as unsigned bytes
1066: Item[] array = new Item[((byte[]) object).length];
1067: for (int i = 0; i < ((byte[]) object).length; i++) {
1068: array[i] = new IntegerValue(
1069: 255 & (int) ((byte[]) object)[i]);
1070: }
1071: return new SequenceExtent(array);
1072:
1073: } else if (object instanceof char[]) {
1074: return StringValue.makeStringValue(new String(
1075: (char[]) object));
1076:
1077: } else if (object instanceof boolean[]) {
1078: Item[] array = new Item[((boolean[]) object).length];
1079: for (int i = 0; i < ((boolean[]) object).length; i++) {
1080: array[i] = BooleanValue.get(((boolean[]) object)[i]);
1081: }
1082: return new SequenceExtent(array);
1083:
1084: } else if (object instanceof Source && config != null) {
1085: if (object instanceof DOMSource) {
1086: return new SingletonNode(Controller.unravel(
1087: (Source) object, config));
1088: }
1089: try {
1090: Builder b = new TinyBuilder();
1091: PipelineConfiguration pipe = config
1092: .makePipelineConfiguration();
1093: b.setPipelineConfiguration(pipe);
1094: new Sender(pipe).send((Source) object, b);
1095: return new SingletonNode(b.getCurrentRoot());
1096: } catch (XPathException err) {
1097: throw new DynamicError(err);
1098: }
1099: } else {
1100: // See whether this is an object representing a Node in some recognized object model
1101: ExternalObjectModel model = config
1102: .findExternalObjectModel(object);
1103: if (model != null) {
1104: DocumentInfo doc = model.wrapDocument(object, "",
1105: config);
1106: NodeInfo node = model.wrapNode(doc, object);
1107: return Value.asValue(node);
1108: }
1109: }
1110: return new ObjectValue(object);
1111: }
1112:
1113: /**
1114: * Temporary method to make a QNameValue from a JAXP 1.3 QName, without creating a compile-time link
1115: * to the JDK 1.5 QName class
1116: * @param object an instance of javax.xml.namespace.QName
1117: * @return a corresponding Saxon QNameValue, or null if any error occurs performing the conversion
1118: */
1119:
1120: public static QNameValue makeQNameValue(Object object,
1121: Configuration config) {
1122: try {
1123: Class qnameClass = config.getClass(
1124: "javax.xml.namespace.QName", false, null);
1125: Class[] args = EMPTY_CLASS_ARRAY;
1126: Method getPrefix = qnameClass.getMethod("getPrefix", args);
1127: Method getLocalPart = qnameClass.getMethod("getLocalPart",
1128: args);
1129: Method getNamespaceURI = qnameClass.getMethod(
1130: "getNamespaceURI", args);
1131: String prefix = (String) getPrefix.invoke(object, args);
1132: String localPart = (String) getLocalPart.invoke(object,
1133: args);
1134: String uri = (String) getNamespaceURI.invoke(object, args);
1135: return new QNameValue(prefix, uri, localPart, config
1136: .getNameChecker());
1137: } catch (XPathException e) {
1138: return null;
1139: } catch (NoSuchMethodException e) {
1140: return null;
1141: } catch (IllegalAccessException e) {
1142: return null;
1143: } catch (InvocationTargetException e) {
1144: return null;
1145: }
1146: }
1147:
1148: public static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
1149:
1150: /**
1151: * Convert to a string for diagnostic output
1152: */
1153:
1154: public String toString() {
1155: try {
1156: return getStringValue();
1157: } catch (XPathException err) {
1158: return super .toString();
1159: }
1160: }
1161:
1162: /**
1163: * Internal method to convert an XPath value to a Java object.
1164: * An atomic value is returned as an instance
1165: * of the best available Java class. If the item is a node, the node is "unwrapped",
1166: * to return the underlying node in the original model (which might be, for example,
1167: * a DOM or JDOM node).
1168: */
1169:
1170: public static Object convert(Item item) throws XPathException {
1171: if (item instanceof NodeInfo) {
1172: Object node = item;
1173: while (node instanceof VirtualNode) {
1174: // strip off any layers of wrapping
1175: node = ((VirtualNode) node).getUnderlyingNode();
1176: }
1177: return node;
1178: } else {
1179: switch (((AtomicValue) item).getItemType(null)
1180: .getPrimitiveType()) {
1181: case Type.STRING:
1182: case Type.UNTYPED_ATOMIC:
1183: case Type.ANY_URI:
1184: case Type.DURATION:
1185: return item.getStringValue();
1186: case Type.BOOLEAN:
1187: return (((BooleanValue) item).getBooleanValue() ? Boolean.TRUE
1188: : Boolean.FALSE);
1189: case Type.DECIMAL:
1190: return ((DecimalValue) item).getValue();
1191: case Type.INTEGER:
1192: return new Long(((NumericValue) item).longValue());
1193: case Type.DOUBLE:
1194: return new Double(((DoubleValue) item).getDoubleValue());
1195: case Type.FLOAT:
1196: return new Float(((FloatValue) item).getValue());
1197: case Type.DATE_TIME:
1198: return ((DateTimeValue) item).getCalendar().getTime();
1199: case Type.DATE:
1200: return ((DateValue) item).getCalendar().getTime();
1201: case Type.TIME:
1202: return item.getStringValue();
1203: case Type.BASE64_BINARY:
1204: return ((Base64BinaryValue) item).getBinaryValue();
1205: case Type.HEX_BINARY:
1206: return ((HexBinaryValue) item).getBinaryValue();
1207: default:
1208: return item;
1209: }
1210: }
1211: }
1212: }
1213:
1214: //
1215: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
1216: // you may not use this file except in compliance with the License. You may obtain a copy of the
1217: // License at http://www.mozilla.org/MPL/
1218: //
1219: // Software distributed under the License is distributed on an "AS IS" basis,
1220: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
1221: // See the License for the specific language governing rights and limitations under the License.
1222: //
1223: // The Original Code is: all this file.
1224: //
1225: // The Initial Developer of the Original Code is Michael H. Kay.
1226: //
1227: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
1228: //
1229: // Contributor(s): none.
1230: //
|