1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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) {
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) {
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
177 final Constructor cons = type.getConstructor(
178 new Class[] {Element.class});
179
180 return cons.newInstance(new Object[] {element});
181 } catch (IllegalAccessException iae) {
182 final InstantiationException ie =
183 new InstantiationException(iae.toString());
184 ie.initCause(iae);
185 throw ie;
186 } catch (InvocationTargetException ite) {
187 final InstantiationException ie =
188 new InstantiationException(ite.toString());
189 ie.initCause(ite);
190 throw ie;
191 } catch (ExceptionInInitializerError eiie) {
192 final InstantiationException ie =
193 new InstantiationException(eiie.toString());
194 ie.initCause(eiie);
195 throw ie;
196 } catch (NoSuchMethodException nsme) {
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
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
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 }