001: package net.sf.saxon.xom;
002:
003: import net.sf.saxon.Configuration;
004: import net.sf.saxon.event.PipelineConfiguration;
005: import net.sf.saxon.event.Receiver;
006: import net.sf.saxon.expr.XPathContext;
007: import net.sf.saxon.om.*;
008: import net.sf.saxon.trans.DynamicError;
009: import net.sf.saxon.trans.XPathException;
010: import net.sf.saxon.value.Value;
011: import net.sf.saxon.value.SingletonNode;
012: import net.sf.saxon.value.SequenceExtent;
013: import nu.xom.Document;
014: import nu.xom.Node;
015:
016: import javax.xml.transform.Result;
017: import javax.xml.transform.Source;
018: import java.util.ArrayList;
019: import java.util.HashSet;
020: import java.util.List;
021: import java.lang.reflect.Array;
022: import java.io.Serializable;
023:
024: /**
025: * This interface must be implemented by any third-party object model that can
026: * be wrapped with a wrapper that implements the Saxon Object Model (the NodeInfo interface).
027: * This implementation of the interface supports wrapping of JDOM Documents.
028: */
029:
030: public class XOMObjectModel implements ExternalObjectModel,
031: Serializable {
032:
033: public XOMObjectModel() {
034: }
035:
036: /**
037: * Test whether this object model recognizes a given node as one of its own
038: */
039:
040: public boolean isRecognizedNode(Object object) {
041: return (object instanceof nu.xom.Node);
042: }
043:
044: /**
045: * Test whether this object model recognizes a given class as representing a
046: * node in that object model. This method will generally be called at compile time.
047: *
048: * @param nodeClass A class that possibly represents nodes
049: * @return true if the class is used to represent nodes in this object model
050: */
051:
052: public boolean isRecognizedNodeClass(Class nodeClass) {
053: return nu.xom.Node.class.isAssignableFrom(nodeClass);
054: }
055:
056: /**
057: * Test whether this object model recognizes a given class as representing a
058: * list of nodes in that object model. This method will generally be called at compile time.
059: *
060: * @param nodeClass A class that possibly represents nodes
061: * @return true if the class is used to represent nodes in this object model
062: */
063:
064: public boolean isRecognizedNodeListClass(Class nodeClass) {
065: return false;
066: // return nu.xom.Nodes.class.isAssignableFrom(nodeClass);
067: }
068:
069: /**
070: * Test whether this object model recognizes a particular kind of JAXP Result object,
071: * and if it does, return a Receiver that builds an instance of this data model from
072: * a sequence of events. If the Result is not recognised, return null.
073: */
074:
075: public Receiver getDocumentBuilder(Result result) {
076: return null; //To change body of implemented methods use File | Settings | File Templates.
077: }
078:
079: /**
080: * Test whether this object model recognizes a particular kind of JAXP Source object,
081: * and if it does, send the contents of the document to a supplied Receiver, and return true.
082: * Otherwise, return false.
083: */
084:
085: public boolean sendSource(Source source, Receiver receiver,
086: PipelineConfiguration pipe) throws XPathException {
087: return false; //To change body of implemented methods use File | Settings | File Templates.
088: }
089:
090: /**
091: * Wrap or unwrap a node using this object model to return the corresponding Saxon node. If the supplied
092: * source does not belong to this object model, return null
093: */
094:
095: public NodeInfo unravel(Source source, Configuration config) {
096: return null; //To change body of implemented methods use File | Settings | File Templates.
097: }
098:
099: /**
100: * Convert a Java object to an XPath value. If the supplied object is recognized as a representation
101: * of a value using this object model, the object model should convert the value to an XPath value
102: * and return this as the result. If not, it should return null. If the object is recognized but cannot
103: * be converted, an exception should be thrown
104: */
105:
106: public Value convertObjectToXPathValue(Object object,
107: Configuration config) throws XPathException {
108: if (object instanceof Node) {
109: return new SingletonNode(wrapNode((Node) object, config));
110: } else if (object instanceof Node[]) {
111: NodeInfo[] nodes = new NodeInfo[((Node[]) object).length];
112: for (int i = 0; i < nodes.length; i++) {
113: nodes[i] = wrapNode(((Node[]) object)[i], config);
114: }
115: return new SequenceExtent(nodes);
116: } else {
117: return null;
118: }
119: }
120:
121: private synchronized NodeInfo wrapNode(Node node,
122: Configuration config) {
123: return new DocumentWrapper(node.getDocument(), "", config)
124: .wrap(node);
125: }
126:
127: /**
128: * Convert an XPath value to an object in this object model. If the supplied value can be converted
129: * to an object in this model, of the specified class, then the conversion should be done and the
130: * resulting object returned. If the value cannot be converted, the method should return null. Note
131: * that the supplied class might be a List, in which case the method should inspect the contents of the
132: * Value to see whether they belong to this object model.
133: */
134:
135: public Object convertXPathValueToObject(Value value,
136: Class targetClass, XPathContext context)
137: throws XPathException {
138: // We accept the object if (a) the target class is Node or Node[],
139: // or (b) the supplied object is a node, or sequence of nodes, that wrap XOM nodes,
140: // provided that the target class is Object or a collection class
141: boolean requireXOM = (Node.class.isAssignableFrom(targetClass) || (targetClass
142: .isArray() && Node.class.isAssignableFrom(targetClass
143: .getComponentType())));
144:
145: // Note: we allow the declared type of the method argument to be a subclass of Node. If the actual
146: // node supplied is the wrong kind of node, this will result in a Java exception.
147:
148: boolean allowXOM = (targetClass == Object.class
149: || targetClass.isAssignableFrom(ArrayList.class)
150: || targetClass.isAssignableFrom(HashSet.class) || (targetClass
151: .isArray() && targetClass.getComponentType() == Object.class));
152: if (!(requireXOM || allowXOM)) {
153: return null;
154: }
155: List nodes = new ArrayList(20);
156:
157: SequenceIterator iter = value.iterate(context);
158: while (true) {
159: Item item = iter.next();
160: if (item == null) {
161: break;
162: }
163: if (item instanceof VirtualNode) {
164: Object o = ((VirtualNode) item).getUnderlyingNode();
165: if (o instanceof Node) {
166: nodes.add(o);
167: } else {
168: if (requireXOM) {
169: DynamicError err = new DynamicError(
170: "Extension function required class "
171: + targetClass.getName()
172: + "; supplied value of class "
173: + item.getClass().getName()
174: + " could not be converted");
175: throw err;
176: }
177: ;
178: }
179: } else if (requireXOM) {
180: DynamicError err = new DynamicError(
181: "Extension function required class "
182: + targetClass.getName()
183: + "; supplied value of class "
184: + item.getClass().getName()
185: + " could not be converted");
186: throw err;
187: } else {
188: return null; // DOM Nodes are not actually required; let someone else try the conversion
189: }
190: }
191:
192: if (nodes.size() == 0 && !requireXOM) {
193: return null; // empty sequence supplied - try a different mapping
194: }
195: if (Node.class.isAssignableFrom(targetClass)) {
196: if (nodes.size() != 1) {
197: DynamicError err = new DynamicError(
198: "Extension function requires a single XOM Node"
199: + "; supplied value contains "
200: + nodes.size() + " nodes");
201: throw err;
202: }
203: return nodes.get(0);
204: } else if (targetClass.isArray()
205: && Node.class.isAssignableFrom(targetClass
206: .getComponentType())) {
207: Node[] array = (Node[]) Array.newInstance(targetClass
208: .getComponentType(), nodes.size());
209: nodes.toArray(array);
210: return array;
211: } else if (targetClass.isAssignableFrom(ArrayList.class)) {
212: return nodes;
213: } else if (targetClass.isAssignableFrom(HashSet.class)) {
214: return new HashSet(nodes);
215: } else {
216: // after all this work, give up
217: return null;
218: }
219: }
220:
221: /**
222: * Wrap a document node in the external object model in a document wrapper that implements
223: * the Saxon DocumentInfo interface
224: * @param node a node (any node) in the third party document
225: * @param baseURI the base URI of the node (supply "" if unknown)
226: * @param config the Saxon configuration (which among other things provides access to the NamePool)
227: * @return the wrapper, which must implement DocumentInfo
228: */
229:
230: public DocumentInfo wrapDocument(Object node, String baseURI,
231: Configuration config) {
232: Document documentNode = ((Node) node).getDocument();
233: return new DocumentWrapper(documentNode, baseURI, config);
234: }
235:
236: /**
237: * Wrap a node within the external object model in a node wrapper that implements the Saxon
238: * VirtualNode interface (which is an extension of NodeInfo)
239: * @param document the document wrapper, as a DocumentInfo object
240: * @param node the node to be wrapped. This must be a node within the document wrapped by the
241: * DocumentInfo provided in the first argument
242: * @return the wrapper for the node, as an instance of VirtualNode
243: */
244:
245: public NodeInfo wrapNode(DocumentInfo document, Object node) {
246: if (!(node instanceof Node)) {
247: throw new IllegalArgumentException(
248: "Object to be wrapped is not a XOM Node: "
249: + node.getClass());
250: }
251: return ((DocumentWrapper) document).wrap((Node) node);
252: }
253:
254: /**
255: * Convert a sequence of values to a NODELIST, as defined in the JAXP XPath API spec. This method
256: * is used when the evaluate() request specifies the return type as NODELIST, regardless of the
257: * actual results of the expression. If the sequence contains things other than nodes, the fallback
258: * is to return the sequence as a Java List object. The method can return null to invoke fallback
259: * behaviour.
260: */
261:
262: public Object convertToNodeList(SequenceExtent extent) {
263: return null;
264: }
265: }
266:
267: //
268: // The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
269: // you may not use this file except in compliance with the License. You may obtain a copy of the
270: // License at http://www.mozilla.org/MPL/
271: //
272: // Software distributed under the License is distributed on an "AS IS" basis,
273: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
274: // See the License for the specific language governing rights and limitations under the License.
275: //
276: // The Original Code is: all this file.
277: //
278: // The Initial Developer of the Original Code is Michael H. Kay.
279: //
280: // Portions created by (your name) are Copyright (C) (your legal entity). All Rights Reserved.
281: //
282: // Contributor(s): Gunther Schadow (changes to allow access to public fields; also wrapping
283: // of extensions and mapping of null to empty sequence).
284: //
|