View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2   // Copyright (c) 2018-2020 Saxonica Limited
3   // This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4   // If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
5   // This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
6   ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
7   
8   package net.sf.saxon.option.jdom2;
9   
10  import net.sf.saxon.Configuration;
11  import net.sf.saxon.event.Builder;
12  import net.sf.saxon.event.PipelineConfiguration;
13  import net.sf.saxon.event.Receiver;
14  import net.sf.saxon.expr.JPConverter;
15  import net.sf.saxon.expr.PJConverter;
16  import net.sf.saxon.expr.XPathContext;
17  import net.sf.saxon.lib.ExternalObjectModel;
18  import net.sf.saxon.lib.NamespaceConstant;
19  import net.sf.saxon.om.*;
20  import net.sf.saxon.pattern.AnyNodeTest;
21  import net.sf.saxon.tree.wrapper.VirtualNode;
22  import net.sf.saxon.type.ItemType;
23  import org.jdom2.*;
24  
25  import javax.xml.transform.Result;
26  import javax.xml.transform.Source;
27  
28  
29  /**
30   * This interface must be implemented by any third-party object model that can
31   * be wrapped with a wrapper that implements the Saxon Object Model (the NodeInfo interface).
32   * This implementation of the interface supports wrapping of JDOM Documents.
33   */
34  
35  public class JDOM2ObjectModel extends TreeModel implements ExternalObjectModel {
36  
37      private final static JDOM2ObjectModell.html#JDOM2ObjectModel">JDOM2ObjectModel THE_INSTANCE = new JDOM2ObjectModel();
38  
39      public static JDOM2ObjectModel getInstance() {
40          return THE_INSTANCE;
41      }
42  
43      public JDOM2ObjectModel() {
44      }
45  
46      /**
47       * Get the name of a characteristic class, which, if it can be loaded, indicates that the supporting libraries
48       * for this object model implementation are available on the classpath
49       *
50       * @return by convention (but not necessarily) the class that implements a document node in the relevant
51       * external model
52       */
53      @Override
54      public String getDocumentClassName() {
55          return "org.jdom2.Document";
56      }
57  
58      /**
59       * Get the URI of the external object model as used in the JAXP factory interfaces for obtaining
60       * an XPath implementation
61       */
62  
63      @Override
64      public String getIdentifyingURI() {
65          return NamespaceConstant.OBJECT_MODEL_JDOM;
66      }
67  
68      @Override
69      public Builder makeBuilder(PipelineConfiguration pipe) {
70          return new JDOM2Writer(pipe);
71      }
72  
73      @Override
74      public int getSymbolicValue() {
75          return Builder.JDOM2_TREE;
76      }
77  
78      @Override
79      public String getName() {
80          return "JDOM2";
81      }
82  
83  
84      /**
85       * Test whether this object model recognizes a given node as one of its own
86       *
87       * @param object the object in question
88       * @return true if the object is a JDOM node
89       */
90  
91      private static boolean isRecognizedNode(Object object) {
92          return object instanceof Document ||
93                  object instanceof Element ||
94                  object instanceof Attribute ||
95                  object instanceof Text ||
96                  object instanceof Comment ||
97                  object instanceof ProcessingInstruction ||
98                  object instanceof Namespace;
99      }
100 
101 
102     @Override
103     public PJConverter getPJConverter(Class<?> targetClass) {
104         if (isRecognizedNodeClass(targetClass)) {
105             return new PJConverter() {
106                 @Override
107                 public Object convert(Sequence value, Class<?> targetClass, XPathContext context) {
108                     return convertXPathValueToObject(value, targetClass);
109                 }
110             };
111         } else {
112             return null;
113         }
114     }
115 
116     @Override
117     public JPConverter getJPConverter(Class sourceClass, Configuration config) {
118         if (isRecognizedNodeClass(sourceClass)) {
119             return new JPConverter() {
120                 @Override
121                 public Sequence convert(Object object, XPathContext context) {
122                     return convertObjectToXPathValue(object, context.getConfiguration());
123                 }
124 
125                 @Override
126                 public ItemType getItemType() {
127                     return AnyNodeTest.getInstance();
128                 }
129             };
130         } else {
131             return null;
132         }
133     }
134 
135     /**
136      * Get a converter that converts a sequence of XPath nodes to this model's representation
137      * of a node list.
138      *
139      * @param node an example of the kind of node used in this model
140      * @return if the model does not recognize this node as one of its own, return null. Otherwise
141      *         return a PJConverter that takes a list of XPath nodes (represented as NodeInfo objects) and
142      *         returns a collection of nodes in this object model
143      */
144 
145     @Override
146     public PJConverter getNodeListCreator(Object node) {
147         return null;
148     }
149 
150     /**
151      * Test whether this object model recognizes a given class as representing a
152      * node in that object model. This method will generally be called at compile time.
153      *
154      * @param nodeClass A class that possibly represents nodes
155      * @return true if the class is used to represent nodes in this object model
156      */
157 
158     private boolean isRecognizedNodeClass(Class nodeClass) {
159         return Document.class.isAssignableFrom(nodeClass) ||
160                 Element.class.isAssignableFrom(nodeClass) ||
161                 Attribute.class.isAssignableFrom(nodeClass) ||
162                 Text.class.isAssignableFrom(nodeClass) ||
163                 CDATA.class.isAssignableFrom(nodeClass) ||
164                 Comment.class.isAssignableFrom(nodeClass) ||
165                 ProcessingInstruction.class.isAssignableFrom(nodeClass) ||
166                 Namespace.class.isAssignableFrom(nodeClass);
167     }
168 
169     /**
170      * Test whether this object model recognizes a particular kind of JAXP Result object,
171      * and if it does, return a Receiver that builds an instance of this data model from
172      * a sequence of events. If the Result is not recognised, return null.
173      * @return always null
174      */
175 
176     @Override
177     public Receiver getDocumentBuilder(Result result) {
178         return null;
179     }
180 
181     /**
182      * Test whether this object model recognizes a particular kind of JAXP Source object,
183      * and if it does, send the contents of the document to a supplied Receiver, and return true.
184      * Otherwise, return false.
185      */
186 
187     @Override
188     public boolean sendSource(Source source, Receiver receiver) {
189         return false;
190     }
191 
192     /**
193      * Wrap or unwrap a node using this object model to return the corresponding Saxon node. If the supplied
194      * source does not belong to this object model, return null
195      */
196 
197     @Override
198     public NodeInfo unravel(Source source, Configuration config) {
199         return null;
200     }
201 
202 
203     /**
204      * Convert a Java object to an XPath value. If the supplied object is recognized as a representation
205      * of a value using this object model, the object model should convert the value to an XPath value
206      * and return this as the result. If not, it should return null. If the object is recognized but cannot
207      * be converted, an exception should be thrown
208      *
209      * @param object the object to be converted . If this is not a JDOM node, the method returns null
210      * @param config the Saxon Configuration
211      * @return either an XPath node that wraps the supplied JDOM node, or null
212      */
213 
214     /*@Nullable*/
215     private Sequence convertObjectToXPathValue(Object object, Configuration config) {
216         if (isRecognizedNode(object)) {
217             if (object instanceof Document) {
218                 return wrapDocument(object, config).getRootNode();
219             } else {
220                 Document root = getDocumentRoot(object);
221                 TreeInfo docInfo = wrapDocument(root, config);
222                 return wrapNode(docInfo, object);
223             }
224         } else {
225             return null;
226         }
227     }
228 
229     /**
230      * Convert an XPath value to an object in this object model. If the supplied value can be converted
231      * to an object in this model, of the specified class, then the conversion should be done and the
232      * resulting object returned. If the value cannot be converted, the method should return null. Note
233      * that the supplied class might be a List, in which case the method should inspect the contents of the
234      * Value to see whether they belong to this object model.
235      *
236      * @param value       the XPath value to be converted
237      * @param target      the Java class required for the result of the conversion
238      * @return the object that results from conversion if conversion is possible, or null otherwise
239      */
240 
241     /*@Nullable*/
242     private Object convertXPathValueToObject(Sequence value, Class<?> target) {
243         if (value instanceof VirtualNode) {
244             Object u = ((VirtualNode) value).getRealNode();
245             if (target.isAssignableFrom(u.getClass())) {
246                 return u;
247             }
248         }
249         return null;
250     }
251 
252 
253     /**
254      * Wrap a document node in the external object model in a document wrapper that implements
255      * the Saxon DocumentInfo interface
256      *
257      * @param node    a node (any node) in the third party document
258      * @param config  the Saxon configuration (which among other things provides access to the NamePool)
259      * @return the wrapper, which must implement DocumentInfo
260      */
261 
262     public TreeInfo wrapDocument(Object node, Configuration config) {
263         Document documentNode = getDocumentRoot(node);
264         return new JDOM2DocumentWrapper(documentNode, config);
265     }
266 
267     /**
268      * Wrap a node within the external object model in a node wrapper that implements the Saxon
269      * VirtualNode interface (which is an extension of NodeInfo)
270      *
271      * @param document the document wrapper, as a DocumentInfo object
272      * @param node     the node to be wrapped. This must be a node within the document wrapped by the
273      *                 DocumentInfo provided in the first argument
274      * @return the wrapper for the node, as an instance of VirtualNode
275      */
276 
277     public NodeInfo wrapNode(TreeInfo document, Object node) {
278         return ((JDOM2DocumentWrapper) document).wrap(node);
279     }
280 
281     /**
282      * Get the document root
283      *
284      * @param node a JDOM node
285      * @return the root of the JDOM tree containing the given node
286      */
287 
288     private static Document getDocumentRoot(Object node) {
289         while (!(node instanceof Document)) {
290             if (node instanceof Element) {
291                 if (((Element) node).isRootElement()) {
292                     return ((Element) node).getDocument();
293                 } else {
294                     node = ((Element) node).getParent();
295                 }
296             } else if (node instanceof Text) {
297                 node = ((Text) node).getParent();
298             } else if (node instanceof Comment) {
299                 node = ((Comment) node).getParent();
300             } else if (node instanceof ProcessingInstruction) {
301                 node = ((ProcessingInstruction) node).getParent();
302             } else if (node instanceof Attribute) {
303                 node = ((Attribute) node).getParent();
304             } else if (node instanceof Namespace) {
305                 throw new UnsupportedOperationException("Cannot find parent of JDOM namespace node");
306             } else {
307                 throw new IllegalStateException("Unknown JDOM node type " + node.getClass());
308             }
309         }
310         return (Document) node;
311     }
312 
313 }
314 
315 
316 // Original Code is Copyright (c) 2009-2020 Saxonica Limited