/* |
|
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. |
|
*/ |
|
/* |
|
* 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. |
|
*/ |
|
package com.sun.org.apache.xpath.internal.objects; |
|
import com.sun.org.apache.xalan.internal.res.XSLMessages; |
|
import com.sun.org.apache.xml.internal.dtm.DTM; |
|
import com.sun.org.apache.xml.internal.dtm.DTMIterator; |
|
import com.sun.org.apache.xml.internal.utils.QName; |
|
import com.sun.org.apache.xml.internal.utils.XMLString; |
|
import com.sun.org.apache.xpath.internal.Expression; |
|
import com.sun.org.apache.xpath.internal.ExpressionOwner; |
|
import com.sun.org.apache.xpath.internal.NodeSetDTM; |
|
import com.sun.org.apache.xpath.internal.XPathContext; |
|
import com.sun.org.apache.xpath.internal.XPathException; |
|
import com.sun.org.apache.xpath.internal.XPathVisitor; |
|
import com.sun.org.apache.xpath.internal.res.XPATHErrorResources; |
|
import java.io.Serializable; |
|
import java.util.List; |
|
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 |
|
* @LastModified: Oct 2017 |
|
*/ |
|
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 = num(); |
|
break; |
|
case CLASS_NODESET : |
|
result = iter(); |
|
break; |
|
case CLASS_BOOLEAN : |
|
result = bool(); |
|
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(List<QName> 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(com.sun.org.apache.xml.internal.utils.FastStringBuffer fsb) |
|
{ |
|
fsb.append(str()); |
|
} |
|
/** |
|
* @see com.sun.org.apache.xpath.internal.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; |
|
} |
|
} |