View Javadoc

1   /*
2    * Copyright (C) 2004-2006 Christian Siefkes <christian@siefkes.net>.
3    * Development of this software is supported by the German Research Society,
4    * Berlin-Brandenburg Graduate School in Distributed Information Systems
5    * (DFG grant no. GRK 316).
6    *
7    * This program is free software; you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation; either version 2 of the License, or
10   * (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with this program; if not, visit
19   * http://www.gnu.org/licenses/gpl.html or write to the Free Software
20   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21   */
22  package de.fu_berlin.ties.io;
23  
24  import java.io.File;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.OutputStream;
28  import java.lang.reflect.Constructor;
29  import java.lang.reflect.InvocationTargetException;
30  import java.util.Iterator;
31  
32  import org.dom4j.Attribute;
33  import org.dom4j.Document;
34  import org.dom4j.DocumentException;
35  import org.dom4j.Element;
36  import org.dom4j.Namespace;
37  import org.dom4j.QName;
38  import org.dom4j.tree.DefaultDocument;
39  import org.dom4j.tree.DefaultElement;
40  
41  import de.fu_berlin.ties.TiesConfiguration;
42  import de.fu_berlin.ties.xml.dom.DOMUtils;
43  
44  /***
45   * An XML element that is targeted at storing an Java object.
46   *
47   * @author Christian Siefkes
48   * @version $Revision: 1.12 $, $Date: 2006/10/21 16:04:22 $, $Author: siefkes $
49   */
50  public class ObjectElement extends DefaultElement {
51  
52      /***
53       * The name of the attribute used to store the Java class of an object:
54       * {@value}.
55       */
56      public static final String JAVA_CLASS_ATTRIBUTE = "java";
57  
58      /***
59       * Uses the {@linkplain Iterator#next() next} element returned by an
60       * iterator to initialize an object. If the iterator does not have any more
61       * elements, <code>null</code> is returned.
62       *
63       * @param iter an iterator over {@link Element}s to use
64       * @return the created by calling {@link #createObject(Element)} on the
65       * next element returned by the iterator; or <code>null</code> if
66       * {@link Iterator#hasNext()} is <code>false</code>
67       * @throws InstantiationException if instantiation failed
68       * @throws SecurityException if access to the required reflection
69       * information is denied
70       * @throws ClassCastException if the object returned by calling
71       * {@link Iterator#next()} does not implement the {@link Element} interface
72       * @throws NullPointerException if no java class attribute is present
73       */
74      public static final Object createNextObject(final Iterator iter)
75      throws InstantiationException, SecurityException, ClassCastException,
76      NullPointerException {
77          if (iter.hasNext()) {
78              return createObject((Element) iter.next());
79          } else {
80              return null;
81          }
82      }
83  
84      /***
85       * Convenience method that delegates to {@link #createObject(Document)},
86       * reading from a file.
87       *
88       * @param file the file to read
89       * @return the created object
90       * @throws IOException if an I/O error occurs while reading the data
91       * @throws InstantiationException if instantiation failed or if the class
92       * specified in the {@link #JAVA_CLASS_ATTRIBUTE} of the root element is
93       * not available on this system
94       * @throws SecurityException if access to the required reflection
95       * information is denied
96       * @throws NullPointerException if no java class attribute is present
97       */
98      public static final Object createObject(final File file)
99      throws IOException, InstantiationException, SecurityException,
100     NullPointerException {
101         try {
102             return createObject(DOMUtils.readDocument(file));
103         } catch (DocumentException de) {       // repackage exception
104             final InstantiationException ie =
105                 new InstantiationException(de.toString());
106             ie.initCause(de);
107             throw ie;
108         }
109     }
110 
111     /***
112      * Convenience method that delegates to {@link #createObject(Element)},
113      * using the root element of a given document.
114      *
115      * @param document the document to use
116      * @return the created object
117      * @throws InstantiationException if instantiation failed or if the class
118      * specified in the {@link #JAVA_CLASS_ATTRIBUTE} of the root element is
119      * not available on this system
120      * @throws SecurityException if access to the required reflection
121      * information is denied
122      * @throws NullPointerException if no java class attribute is present
123      */
124     public static final Object createObject(final Document document)
125     throws InstantiationException, SecurityException, NullPointerException {
126         return createObject(document.getRootElement());
127     }
128 
129     /***
130      * Creates (deserializes) an object of a specified type by calling a
131      * constructor of the class that accepts an XML element as single argument
132      * and passing itself as parameter. Only classes that provide a suitable
133      * constructor can be instantiated this way.
134      *
135      * @param element the element to use
136      * @return the created object; the returned object will be an instance of
137      * the specified class
138      * @throws InstantiationException if instantiation failed or if the class
139      * specified in the {@link #JAVA_CLASS_ATTRIBUTE} is not available on this
140      * system
141      * @throws SecurityException if access to the required reflection
142      * information is denied
143      * @throws NullPointerException if no java class attribute is present
144      */
145     public static final Object createObject(final Element element)
146     throws InstantiationException, SecurityException, NullPointerException {
147         try {
148             return createObject(element, javaClass(element));
149         } catch (ClassNotFoundException cnfe) {       // repackage exception
150             final InstantiationException ie =
151                 new InstantiationException(cnfe.toString());
152             ie.initCause(cnfe);
153             throw ie;
154         }
155    }
156 
157     /***
158      * Creates (deserializes) an object of a specified type by calling a
159      * constructor of the class that accepts an XML element as single argument
160      * and passing itself as parameter. Only classes that provide a suitable
161      * constructor can be instantiated this way.
162      *
163      * @param element the element to use
164      * @param type the class of the object to create; must have a constructor
165      * whose only argument is an {@link Element}
166      * @return the created object; the returned object will be an instance of
167      * the specified class
168      * @throws InstantiationException if instantiation failed
169      * @throws SecurityException if access to the required reflection
170      * information is denied
171      */
172     public static final Object createObject(final Element element,
173             final Class type)
174     throws InstantiationException, SecurityException {
175         try {
176             // load constructor with Element as only argument
177             final Constructor cons = type.getConstructor(
178                     new Class[] {Element.class});
179             // pass element as single argument
180             return cons.newInstance(new Object[] {element});
181         } catch (IllegalAccessException iae) {        // repackage exception
182             final InstantiationException ie =
183                 new InstantiationException(iae.toString());
184             ie.initCause(iae);
185             throw ie;
186         } catch (InvocationTargetException ite) {     // repackage exception
187             final InstantiationException ie =
188                 new InstantiationException(ite.toString());
189             ie.initCause(ite);
190             throw ie;
191         } catch (ExceptionInInitializerError eiie) {  // repackage exception
192             final InstantiationException ie =
193                 new InstantiationException(eiie.toString());
194             ie.initCause(eiie);
195             throw ie;
196         } catch (NoSuchMethodException nsme) {        // repackage exception
197             final InstantiationException ie =
198                 new InstantiationException(nsme.toString());
199             ie.initCause(nsme);
200             throw ie;
201         }
202     }
203 
204     /***
205      * Looks up and returns the name of the {@link Class} of Java object stored
206      * in an element.
207      *
208      * @param element the element to use
209      * @return the class name of the stored object; or <code>null</code> if
210      * no {@link #JAVA_CLASS_ATTRIBUTE} is present
211      */
212     public static String javaClassName(final Element element) {
213         return element.attributeValue(JAVA_CLASS_ATTRIBUTE);
214     }
215 
216     /***
217      * Looks up and returns the {@link Class} of a Java object stored in an
218      * element.
219      *
220      * @param element the element to use
221      * @return the class of the stored object; or <code>null</code> if
222      * no {@link #JAVA_CLASS_ATTRIBUTE} is present
223      * @throws ClassNotFoundException if the class cannot be located
224      */
225     public static Class javaClass(final Element element)
226     throws ClassNotFoundException {
227         final String className = javaClassName(element);
228 
229         if (className != null) {
230             return Class.forName(className);
231         } else {
232             return null;
233         }
234     }
235 
236     /***
237      * Returns the file extension recommended for serializing Java code.
238      *
239      * @return the recommended extension: "xsj" (XML-serialized Java)
240      */
241     public static String recommendedExtension() {
242         return "xsj";
243     }
244 
245 
246     /***
247      * Creates a new instance.
248      *
249      * @param name the name of the element
250      * @param javaClass the class of the object to store; or <code>null</code>
251      * if no class should be stored
252      */
253     public ObjectElement(final String name, final Class javaClass) {
254         super(name);
255         if (javaClass != null) {
256             addAttribute(JAVA_CLASS_ATTRIBUTE, javaClass.getName());
257         }
258     }
259 
260     /***
261      * Creates a new instance.
262      *
263      * @param qname the qualified name of the element
264      * @param javaClass the class of the object to store; or <code>null</code>
265      * if no class should be stored
266      */
267     public ObjectElement(final QName qname, final Class javaClass) {
268         super(qname);
269         if (javaClass != null) {
270             addAttribute(JAVA_CLASS_ATTRIBUTE, javaClass.getName());
271         }
272     }
273 
274     /***
275      * Creates a new instance.
276      *
277      * @param name the local name of the element
278      * @param namespace the namespace of the element
279      * @param javaClass the class of the object to store; or <code>null</code>
280      * if no class should be stored
281      */
282     public ObjectElement(final String name, final Namespace namespace,
283             final Class javaClass) {
284         super(name, namespace);
285         if (javaClass != null) {
286             addAttribute(JAVA_CLASS_ATTRIBUTE, javaClass.getName());
287         }
288     }
289 
290 
291     /***
292      * Creates (deserializes) an object of a specified type by calling a
293      * constructor of the class that accepts an XML element as single argument
294      * and passing itself as parameter. Only classes that provide a suitable
295      * constructor can be instantiated this way.
296      *
297      * @return the created object
298      * @throws InstantiationException if instantiation failed
299      * @throws SecurityException if access to the required reflection
300      * information is denied
301      * @throws ClassNotFoundException if the class specified in the
302      * {@link #JAVA_CLASS_ATTRIBUTE} is not available on this system
303      * @throws NullPointerException if the java class attribute has been
304      * removed (by calling {@link #unsetJavaClass()}
305      */
306     public final Object createObject() throws InstantiationException,
307     SecurityException, ClassNotFoundException, NullPointerException {
308         return createObject(this);
309     }
310 
311     /***
312      * Returns the name of the {@link Class} of the stored object.
313      *
314      * @return the class name of the stored object; or <code>null</code> if
315      * {@link #unsetJavaClass()} has been called before
316      */
317     public String javaClassName() {
318         // delegate to static method
319         return javaClassName(this);
320     }
321 
322     /***
323      * Returns the {@link Class} of the stored object.
324      *
325      * @return the class of the stored object; or <code>null</code> if
326      * {@link #unsetJavaClass()} has been called before
327      * @throws ClassNotFoundException if the class cannot be located
328      */
329     public Class javaClass() throws ClassNotFoundException {
330         // delegate to static method
331         return javaClass(this);
332     }
333 
334     /***
335      * Writes this object element document to a file, consulting a given
336      * configuration about {@linkplain
337      * IOUtils#openCompressableOutStream(OutputStream,TiesConfiguration,String)
338      * whether to use compression}. For this purpose, the
339      * {@linkplain #recommendedExtension() recommendedExtension} is used as
340      * suffix.
341      *
342      * @param file the file to write to
343      * @param conf used to decide whether to use compression
344      * @throws IOException if an I/O error occurs while writing
345      */
346     public void store(final File file, final TiesConfiguration conf)
347     throws IOException {
348         final OutputStream out = IOUtils.openCompressableOutStream(
349                 new FileOutputStream(file), conf, recommendedExtension());
350         try {
351             store(out);
352         } finally {
353             IOUtils.tryToClose(out);
354         }
355     }
356 
357     /***
358      * Writes this object element document to an output stream.
359      * 
360      * @param out the output stream to write to; not closed by this method
361      * @throws IOException if an I/O error occurs while writing
362      */
363     public void store(final OutputStream out)
364     throws IOException {
365         DOMUtils.writeDocument(new DefaultDocument(this), out);
366     }
367 
368     /***
369      * Unsets the attribute representing the {@link Class} of the stored object.
370      *
371      * @return a pointer to this object (to allow method chaining)
372      */
373     public ObjectElement unsetJavaClass() {
374         final Attribute javaAttrib = attribute(JAVA_CLASS_ATTRIBUTE);
375 
376         if (javaAttrib != null) {
377             remove(javaAttrib);
378         }
379         return this;
380     }
381 
382 }