| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /* | |
| * $Id$ | |
| */ | |
| package org.apache.xpath.objects; | |
| import java.io.Serializable; | |
| import org.apache.xalan.res.XSLMessages; | |
| import org.apache.xml.dtm.DTM; | |
| import org.apache.xml.dtm.DTMIterator; | |
| import org.apache.xml.utils.XMLString; | |
| import org.apache.xpath.Expression; | |
| import org.apache.xpath.ExpressionOwner; | |
| import org.apache.xpath.NodeSetDTM; | |
| import org.apache.xpath.XPathContext; | |
| import org.apache.xpath.XPathException; | |
| import org.apache.xpath.XPathVisitor; | |
| import org.apache.xpath.res.XPATHErrorResources; | |
| import org.w3c.dom.DocumentFragment; | |
| import org.w3c.dom.NodeList; | |
| import org.w3c.dom.traversal.NodeIterator; | |
| /** | |
| * This class represents an XPath object, and is capable of | |
| * converting the object to various types, such as a string. | |
| * This class acts as the base class to other XPath type objects, | |
| * such as XString, and provides polymorphic casting capabilities. | |
| * @xsl.usage general | |
| */ | |
| public class XObject extends Expression implements Serializable, Cloneable | |
| { | |
| static final long serialVersionUID = -821887098985662951L; | |
| /** | |
| * The java object which this object wraps. | |
| * @serial | |
| */ | |
| protected Object m_obj; // This may be NULL!!! | |
| /** | |
| * Create an XObject. | |
| */ | |
| public XObject(){} | |
| /** | |
| * Create an XObject. | |
| * | |
| * @param obj Can be any object, should be a specific type | |
| * for derived classes, or null. | |
| */ | |
| public XObject(Object obj) | |
| { | |
| setObject(obj); | |
| } | |
| protected void setObject(Object obj) { | |
| m_obj = obj; | |
| } | |
| /** | |
| * For support of literal objects in xpaths. | |
| * | |
| * @param xctxt The XPath execution context. | |
| * | |
| * @return This object. | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public XObject execute(XPathContext xctxt) | |
| throws javax.xml.transform.TransformerException | |
| { | |
| return this; | |
| } | |
| /** | |
| * Specify if it's OK for detach to release the iterator for reuse. | |
| * This function should be called with a value of false for objects that are | |
| * stored in variables. | |
| * Calling this with a value of false on a XNodeSet will cause the nodeset | |
| * to be cached. | |
| * | |
| * @param allowRelease true if it is OK for detach to release this iterator | |
| * for pooling. | |
| */ | |
| public void allowDetachToRelease(boolean allowRelease){} | |
| /** | |
| * Detaches the <code>DTMIterator</code> from the set which it iterated | |
| * over, releasing any computational resources and placing the iterator | |
| * in the INVALID state. After <code>detach</code> has been invoked, | |
| * calls to <code>nextNode</code> or <code>previousNode</code> will | |
| * raise a runtime exception. | |
| */ | |
| public void detach(){} | |
| /** | |
| * Forces the object to release it's resources. This is more harsh than | |
| * detach(). | |
| */ | |
| public void destruct() | |
| { | |
| if (null != m_obj) | |
| { | |
| allowDetachToRelease(true); | |
| detach(); | |
| setObject(null); | |
| } | |
| } | |
| /** | |
| * Reset for fresh reuse. | |
| */ | |
| public void reset() | |
| { | |
| } | |
| /** | |
| * Directly call the | |
| * characters method on the passed ContentHandler for the | |
| * string-value. Multiple calls to the | |
| * ContentHandler's characters methods may well occur for a single call to | |
| * this method. | |
| * | |
| * @param ch A non-null reference to a ContentHandler. | |
| * | |
| * @throws org.xml.sax.SAXException | |
| */ | |
| public void dispatchCharactersEvents(org.xml.sax.ContentHandler ch) | |
| throws org.xml.sax.SAXException | |
| { | |
| xstr().dispatchCharactersEvents(ch); | |
| } | |
| /** | |
| * Create the right XObject based on the type of the object passed. This | |
| * function can not make an XObject that exposes DOM Nodes, NodeLists, and | |
| * NodeIterators to the XSLT stylesheet as node-sets. | |
| * | |
| * @param val The java object which this object will wrap. | |
| * | |
| * @return the right XObject based on the type of the object passed. | |
| */ | |
| static public XObject create(Object val) | |
| { | |
| return XObjectFactory.create(val); | |
| } | |
| /** | |
| * Create the right XObject based on the type of the object passed. | |
| * This function <emph>can</emph> make an XObject that exposes DOM Nodes, NodeLists, and | |
| * NodeIterators to the XSLT stylesheet as node-sets. | |
| * | |
| * @param val The java object which this object will wrap. | |
| * @param xctxt The XPath context. | |
| * | |
| * @return the right XObject based on the type of the object passed. | |
| */ | |
| static public XObject create(Object val, XPathContext xctxt) | |
| { | |
| return XObjectFactory.create(val, xctxt); | |
| } | |
| /** Constant for NULL object type */ | |
| public static final int CLASS_NULL = -1; | |
| /** Constant for UNKNOWN object type */ | |
| public static final int CLASS_UNKNOWN = 0; | |
| /** Constant for BOOLEAN object type */ | |
| public static final int CLASS_BOOLEAN = 1; | |
| /** Constant for NUMBER object type */ | |
| public static final int CLASS_NUMBER = 2; | |
| /** Constant for STRING object type */ | |
| public static final int CLASS_STRING = 3; | |
| /** Constant for NODESET object type */ | |
| public static final int CLASS_NODESET = 4; | |
| /** Constant for RESULT TREE FRAGMENT object type */ | |
| public static final int CLASS_RTREEFRAG = 5; | |
| /** Represents an unresolved variable type as an integer. */ | |
| public static final int CLASS_UNRESOLVEDVARIABLE = 600; | |
| /** | |
| * Tell what kind of class this is. | |
| * | |
| * @return CLASS_UNKNOWN | |
| */ | |
| public int getType() | |
| { | |
| return CLASS_UNKNOWN; | |
| } | |
| /** | |
| * Given a request type, return the equivalent string. | |
| * For diagnostic purposes. | |
| * | |
| * @return type string "#UNKNOWN" + object class name | |
| */ | |
| public String getTypeString() | |
| { | |
| return "#UNKNOWN (" + object().getClass().getName() + ")"; | |
| } | |
| /** | |
| * Cast result object to a number. Always issues an error. | |
| * | |
| * @return 0.0 | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public double num() throws javax.xml.transform.TransformerException | |
| { | |
| error(XPATHErrorResources.ER_CANT_CONVERT_TO_NUMBER, | |
| new Object[]{ getTypeString() }); //"Can not convert "+getTypeString()+" to a number"); | |
| return 0.0; | |
| } | |
| /** | |
| * Cast result object to a number, but allow side effects, such as the | |
| * incrementing of an iterator. | |
| * | |
| * @return numeric value of the string conversion from the | |
| * next node in the NodeSetDTM, or NAN if no node was found | |
| */ | |
| public double numWithSideEffects() throws javax.xml.transform.TransformerException | |
| { | |
| return num(); | |
| } | |
| /** | |
| * Cast result object to a boolean. Always issues an error. | |
| * | |
| * @return false | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public boolean bool() throws javax.xml.transform.TransformerException | |
| { | |
| error(XPATHErrorResources.ER_CANT_CONVERT_TO_NUMBER, | |
| new Object[]{ getTypeString() }); //"Can not convert "+getTypeString()+" to a number"); | |
| return false; | |
| } | |
| /** | |
| * Cast result object to a boolean, but allow side effects, such as the | |
| * incrementing of an iterator. | |
| * | |
| * @return True if there is a next node in the nodeset | |
| */ | |
| public boolean boolWithSideEffects() throws javax.xml.transform.TransformerException | |
| { | |
| return bool(); | |
| } | |
| /** | |
| * Cast result object to a string. | |
| * | |
| * @return The string this wraps or the empty string if null | |
| */ | |
| public XMLString xstr() | |
| { | |
| return XMLStringFactoryImpl.getFactory().newstr(str()); | |
| } | |
| /** | |
| * Cast result object to a string. | |
| * | |
| * @return The object as a string | |
| */ | |
| public String str() | |
| { | |
| return (m_obj != null) ? m_obj.toString() : ""; | |
| } | |
| /** | |
| * Return the string representation of the object | |
| * | |
| * | |
| * @return the string representation of the object | |
| */ | |
| public String toString() | |
| { | |
| return str(); | |
| } | |
| /** | |
| * Cast result object to a result tree fragment. | |
| * | |
| * @param support XPath context to use for the conversion | |
| * | |
| * @return the objec as a result tree fragment. | |
| */ | |
| public int rtf(XPathContext support) | |
| { | |
| int result = rtf(); | |
| if (DTM.NULL == result) | |
| { | |
| DTM frag = support.createDocumentFragment(); | |
| // %OPT% | |
| frag.appendTextChild(str()); | |
| result = frag.getDocument(); | |
| } | |
| return result; | |
| } | |
| /** | |
| * Cast result object to a result tree fragment. | |
| * | |
| * @param support XPath context to use for the conversion | |
| * | |
| * @return the objec as a result tree fragment. | |
| */ | |
| public DocumentFragment rtree(XPathContext support) | |
| { | |
| DocumentFragment docFrag = null; | |
| int result = rtf(); | |
| if (DTM.NULL == result) | |
| { | |
| DTM frag = support.createDocumentFragment(); | |
| // %OPT% | |
| frag.appendTextChild(str()); | |
| docFrag = (DocumentFragment)frag.getNode(frag.getDocument()); | |
| } | |
| else | |
| { | |
| DTM frag = support.getDTM(result); | |
| docFrag = (DocumentFragment)frag.getNode(frag.getDocument()); | |
| } | |
| return docFrag; | |
| } | |
| /** | |
| * For functions to override. | |
| * | |
| * @return null | |
| */ | |
| public DocumentFragment rtree() | |
| { | |
| return null; | |
| } | |
| /** | |
| * For functions to override. | |
| * | |
| * @return null | |
| */ | |
| public int rtf() | |
| { | |
| return DTM.NULL; | |
| } | |
| /** | |
| * Return a java object that's closest to the representation | |
| * that should be handed to an extension. | |
| * | |
| * @return The object that this class wraps | |
| */ | |
| public Object object() | |
| { | |
| return m_obj; | |
| } | |
| /** | |
| * Cast result object to a nodelist. Always issues an error. | |
| * | |
| * @return null | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public DTMIterator iter() throws javax.xml.transform.TransformerException | |
| { | |
| error(XPATHErrorResources.ER_CANT_CONVERT_TO_NODELIST, | |
| new Object[]{ getTypeString() }); //"Can not convert "+getTypeString()+" to a NodeList!"); | |
| return null; | |
| } | |
| /** | |
| * Get a fresh copy of the object. For use with variables. | |
| * | |
| * @return This object, unless overridden by subclass. | |
| */ | |
| public XObject getFresh() | |
| { | |
| return this; | |
| } | |
| /** | |
| * Cast result object to a nodelist. Always issues an error. | |
| * | |
| * @return null | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public NodeIterator nodeset() throws javax.xml.transform.TransformerException | |
| { | |
| error(XPATHErrorResources.ER_CANT_CONVERT_TO_NODELIST, | |
| new Object[]{ getTypeString() }); //"Can not convert "+getTypeString()+" to a NodeList!"); | |
| return null; | |
| } | |
| /** | |
| * Cast result object to a nodelist. Always issues an error. | |
| * | |
| * @return null | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public NodeList nodelist() throws javax.xml.transform.TransformerException | |
| { | |
| error(XPATHErrorResources.ER_CANT_CONVERT_TO_NODELIST, | |
| new Object[]{ getTypeString() }); //"Can not convert "+getTypeString()+" to a NodeList!"); | |
| return null; | |
| } | |
| /** | |
| * Cast result object to a nodelist. Always issues an error. | |
| * | |
| * @return The object as a NodeSetDTM. | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public NodeSetDTM mutableNodeset() | |
| throws javax.xml.transform.TransformerException | |
| { | |
| error(XPATHErrorResources.ER_CANT_CONVERT_TO_MUTABLENODELIST, | |
| new Object[]{ getTypeString() }); //"Can not convert "+getTypeString()+" to a NodeSetDTM!"); | |
| return (NodeSetDTM) m_obj; | |
| } | |
| /** | |
| * Cast object to type t. | |
| * | |
| * @param t Type of object to cast this to | |
| * @param support XPath context to use for the conversion | |
| * | |
| * @return This object as the given type t | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public Object castToType(int t, XPathContext support) | |
| throws javax.xml.transform.TransformerException | |
| { | |
| Object result; | |
| switch (t) | |
| { | |
| case CLASS_STRING : | |
| result = str(); | |
| break; | |
| case CLASS_NUMBER : | |
| result = new Double(num()); | |
| break; | |
| case CLASS_NODESET : | |
| result = iter(); | |
| break; | |
| case CLASS_BOOLEAN : | |
| result = bool() ? Boolean.TRUE : Boolean.FALSE; | |
| break; | |
| case CLASS_UNKNOWN : | |
| result = m_obj; | |
| break; | |
| // %TBD% What to do here? | |
| // case CLASS_RTREEFRAG : | |
| // result = rtree(support); | |
| // break; | |
| default : | |
| error(XPATHErrorResources.ER_CANT_CONVERT_TO_TYPE, | |
| new Object[]{ getTypeString(), | |
| Integer.toString(t) }); //"Can not convert "+getTypeString()+" to a type#"+t); | |
| result = null; | |
| } | |
| return result; | |
| } | |
| /** | |
| * Tell if one object is less than the other. | |
| * | |
| * @param obj2 Object to compare this to | |
| * | |
| * @return True if this object is less than the given object | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public boolean lessThan(XObject obj2) | |
| throws javax.xml.transform.TransformerException | |
| { | |
| // In order to handle the 'all' semantics of | |
| // nodeset comparisons, we always call the | |
| // nodeset function. Because the arguments | |
| // are backwards, we call the opposite comparison | |
| // function. | |
| if (obj2.getType() == XObject.CLASS_NODESET) | |
| return obj2.greaterThan(this); | |
| return this.num() < obj2.num(); | |
| } | |
| /** | |
| * Tell if one object is less than or equal to the other. | |
| * | |
| * @param obj2 Object to compare this to | |
| * | |
| * @return True if this object is less than or equal to the given object | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public boolean lessThanOrEqual(XObject obj2) | |
| throws javax.xml.transform.TransformerException | |
| { | |
| // In order to handle the 'all' semantics of | |
| // nodeset comparisons, we always call the | |
| // nodeset function. Because the arguments | |
| // are backwards, we call the opposite comparison | |
| // function. | |
| if (obj2.getType() == XObject.CLASS_NODESET) | |
| return obj2.greaterThanOrEqual(this); | |
| return this.num() <= obj2.num(); | |
| } | |
| /** | |
| * Tell if one object is greater than the other. | |
| * | |
| * @param obj2 Object to compare this to | |
| * | |
| * @return True if this object is greater than the given object | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public boolean greaterThan(XObject obj2) | |
| throws javax.xml.transform.TransformerException | |
| { | |
| // In order to handle the 'all' semantics of | |
| // nodeset comparisons, we always call the | |
| // nodeset function. Because the arguments | |
| // are backwards, we call the opposite comparison | |
| // function. | |
| if (obj2.getType() == XObject.CLASS_NODESET) | |
| return obj2.lessThan(this); | |
| return this.num() > obj2.num(); | |
| } | |
| /** | |
| * Tell if one object is greater than or equal to the other. | |
| * | |
| * @param obj2 Object to compare this to | |
| * | |
| * @return True if this object is greater than or equal to the given object | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public boolean greaterThanOrEqual(XObject obj2) | |
| throws javax.xml.transform.TransformerException | |
| { | |
| // In order to handle the 'all' semantics of | |
| // nodeset comparisons, we always call the | |
| // nodeset function. Because the arguments | |
| // are backwards, we call the opposite comparison | |
| // function. | |
| if (obj2.getType() == XObject.CLASS_NODESET) | |
| return obj2.lessThanOrEqual(this); | |
| return this.num() >= obj2.num(); | |
| } | |
| /** | |
| * Tell if two objects are functionally equal. | |
| * | |
| * @param obj2 Object to compare this to | |
| * | |
| * @return True if this object is equal to the given object | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public boolean equals(XObject obj2) | |
| { | |
| // In order to handle the 'all' semantics of | |
| // nodeset comparisons, we always call the | |
| // nodeset function. | |
| if (obj2.getType() == XObject.CLASS_NODESET) | |
| return obj2.equals(this); | |
| if (null != m_obj) | |
| { | |
| return m_obj.equals(obj2.m_obj); | |
| } | |
| else | |
| { | |
| return obj2.m_obj == null; | |
| } | |
| } | |
| /** | |
| * Tell if two objects are functionally not equal. | |
| * | |
| * @param obj2 Object to compare this to | |
| * | |
| * @return True if this object is not equal to the given object | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| public boolean notEquals(XObject obj2) | |
| throws javax.xml.transform.TransformerException | |
| { | |
| // In order to handle the 'all' semantics of | |
| // nodeset comparisons, we always call the | |
| // nodeset function. | |
| if (obj2.getType() == XObject.CLASS_NODESET) | |
| return obj2.notEquals(this); | |
| return !equals(obj2); | |
| } | |
| /** | |
| * Tell the user of an error, and probably throw an | |
| * exception. | |
| * | |
| * @param msg Error message to issue | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| protected void error(String msg) | |
| throws javax.xml.transform.TransformerException | |
| { | |
| error(msg, null); | |
| } | |
| /** | |
| * Tell the user of an error, and probably throw an | |
| * exception. | |
| * | |
| * @param msg Error message to issue | |
| * @param args Arguments to use in the message | |
| * | |
| * @throws javax.xml.transform.TransformerException | |
| */ | |
| protected void error(String msg, Object[] args) | |
| throws javax.xml.transform.TransformerException | |
| { | |
| String fmsg = XSLMessages.createXPATHMessage(msg, args); | |
| // boolean shouldThrow = support.problem(m_support.XPATHPROCESSOR, | |
| // m_support.ERROR, | |
| // null, | |
| // null, fmsg, 0, 0); | |
| // if(shouldThrow) | |
| { | |
| throw new XPathException(fmsg, this); | |
| } | |
| } | |
| /** | |
| * XObjects should not normally need to fix up variables. | |
| */ | |
| public void fixupVariables(java.util.Vector vars, int globalsSize) | |
| { | |
| // no-op | |
| } | |
| /** | |
| * Cast result object to a string. | |
| * | |
| * | |
| * NEEDSDOC @param fsb | |
| * @return The string this wraps or the empty string if null | |
| */ | |
| public void appendToFsb(org.apache.xml.utils.FastStringBuffer fsb) | |
| { | |
| fsb.append(str()); | |
| } | |
| /** | |
| * @see org.apache.xpath.XPathVisitable#callVisitors(ExpressionOwner, XPathVisitor) | |
| */ | |
| public void callVisitors(ExpressionOwner owner, XPathVisitor visitor) | |
| { | |
| assertion(false, "callVisitors should not be called for this object!!!"); | |
| } | |
| /** | |
| * @see Expression#deepEquals(Expression) | |
| */ | |
| public boolean deepEquals(Expression expr) | |
| { | |
| if(!isSameClass(expr)) | |
| return false; | |
| // If equals at the expression level calls deepEquals, I think we're | |
| // still safe from infinite recursion since this object overrides | |
| // equals. I hope. | |
| if(!this.equals((XObject)expr)) | |
| return false; | |
| return true; | |
| } | |
| } |