001    
002    /*
003     * Copyright (C) 2011 Archie L. Cobbs. All rights reserved.
004     *
005     * $Id: JiBXUtil.java 292 2012-02-19 21:14:45Z archie.cobbs $
006     */
007    
008    package org.dellroad.stuff.jibx;
009    
010    import java.io.BufferedWriter;
011    import java.io.IOException;
012    import java.io.InputStream;
013    import java.io.OutputStream;
014    import java.io.OutputStreamWriter;
015    import java.io.StringWriter;
016    import java.io.Writer;
017    import java.net.URL;
018    import java.util.concurrent.Callable;
019    
020    import org.dellroad.stuff.java.IdGenerator;
021    import org.jibx.runtime.BindingDirectory;
022    import org.jibx.runtime.IBindingFactory;
023    import org.jibx.runtime.IMarshallable;
024    import org.jibx.runtime.IMarshallingContext;
025    import org.jibx.runtime.IUnmarshallingContext;
026    import org.jibx.runtime.JiBXException;
027    
028    /**
029     * Some simplified API methods for JiBX XML encoding/decoding.
030     */
031    public final class JiBXUtil {
032    
033        public static final String XML_ENCODING = "UTF-8";
034    
035        private JiBXUtil() {
036        }
037    
038        /**
039         * Read in an object encoded as XML.
040         * This method assumes there is exactly one binding for the given class.
041         *
042         * <p>
043         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
044         * (see {@link IdMapper}).
045         *
046         * <p>
047         * The {@code input} is not closed by this method.
048         *
049         * @param targetClass target class
050         * @param input source for the XML document
051         * @throws JiBXException if there is a JiBX parse error
052         * @throws IOException if an error occurs reading from {@code input}
053         */
054        public static <T> T readObject(Class<T> targetClass, InputStream input) throws JiBXException, IOException {
055            return JiBXUtil.readObject(targetClass, null, input);
056        }
057    
058        /**
059         * Read in an object encoded as XML.
060         *
061         * <p>
062         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
063         * (see {@link IdMapper}).
064         *
065         * <p>
066         * The {@code input} is not closed by this method.
067         *
068         * @param targetClass target class
069         * @param bindingName binding name, or null to choose the only one
070         * @param input source for the XML document
071         * @throws JiBXException if there is a JiBX parse error
072         * @throws IOException if an error occurs reading from {@code input}
073         */
074        public static <T> T readObject(final Class<T> targetClass, String bindingName, final InputStream input)
075          throws JiBXException, IOException {
076            IBindingFactory bindingFactory = bindingName != null ?
077              BindingDirectory.getFactory(bindingName, targetClass) : BindingDirectory.getFactory(targetClass);
078            final IUnmarshallingContext unmarshallingContext = bindingFactory.createUnmarshallingContext();
079            try {
080                return IdGenerator.run(new Callable<T>() {
081                    @Override
082                    public T call() throws Exception {
083                        return targetClass.cast(unmarshallingContext.unmarshalDocument(input, null));
084                    }
085                });
086            } catch (Exception e) {
087                JiBXUtil.unwrapException(e);
088                return null;            // not reached
089            }
090        }
091    
092        /**
093         * Read in an object encoded as XML from an {@link URL}.
094         * This method assumes there is exactly one binding for the given class.
095         *
096         * <p>
097         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
098         * (see {@link IdMapper}).
099         *
100         * @param targetClass target class
101         * @param url source for the XML document
102         * @throws JiBXException if there is a JiBX parse error
103         * @throws IOException if an error occurs reading the referenced document
104         */
105        public static <T> T readObject(Class<T> targetClass, URL url) throws JiBXException, IOException {
106            return JiBXUtil.readObject(targetClass, null, url);
107        }
108    
109        /**
110         * Read in an object encoded as XML from an {@link URL}.
111         *
112         * <p>
113         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
114         * (see {@link IdMapper}).
115         *
116         * @param targetClass target class
117         * @param url source for the XML document
118         * @param bindingName binding name, or null to choose the only one
119         * @throws JiBXException if there is a JiBX parse error
120         * @throws IOException if an error occurs reading the referenced document
121         */
122        public static <T> T readObject(Class<T> targetClass, String bindingName, URL url) throws JiBXException, IOException {
123            InputStream in = url.openStream();
124            try {
125                return JiBXUtil.readObject(targetClass, bindingName, in);
126            } finally {
127                try {
128                    in.close();
129                } catch (IOException e) {
130                    // ignore
131                }
132            }
133        }
134    
135        /**
136         * Write out the given instance encoded as a UTF-8 encoded XML document.
137         * This method assumes there is exactly one binding for the given class.
138         *
139         * <p>
140         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
141         * (see {@link IdMapper}).
142         *
143         * @param obj object to write
144         * @param output output destination; will <b>not</b> be closed by this method
145         * @throws JiBXException if there is a JiBX encoding error
146         * @throws IOException if an error occurs writing to {@code output}
147         */
148        public static <T> void writeObject(T obj, OutputStream output) throws JiBXException, IOException {
149            JiBXUtil.writeObject(obj, null, output);
150        }
151    
152        /**
153         * Write out the given instance encoded as a UTF-8 encoded XML document.
154         *
155         * <p>
156         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
157         * (see {@link IdMapper}).
158         *
159         * @param obj object to write
160         * @param bindingName binding name, or null to choose the only one
161         * @param output output destination; will <b>not</b> be closed by this method
162         * @throws JiBXException if there is a JiBX encoding error
163         * @throws IOException if an error occurs writing to {@code output}
164         */
165        public static <T> void writeObject(T obj, String bindingName, OutputStream output) throws JiBXException, IOException {
166            JiBXUtil.writeObject(obj, bindingName, new OutputStreamWriter(output, XML_ENCODING));
167        }
168    
169        /**
170         * Write out the given instance encoded as an XML document with "UTF-8" as the declared encoding.
171         * This method assumes there is exactly one binding for the given class.
172         *
173         * <p>
174         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
175         * (see {@link IdMapper}).
176         *
177         * @param obj object to write
178         * @param writer output destination; will <b>not</b> be closed by this method
179         */
180        public static <T> void writeObject(T obj, Writer writer) throws JiBXException, IOException {
181            JiBXUtil.writeObject(obj, null, writer);
182        }
183    
184        /**
185         * Write out the given instance encoded as an XML document with "UTF-8" as the declared encoding.
186         *
187         * <p>
188         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
189         * (see {@link IdMapper}).
190         *
191         * @param obj object to write
192         * @param bindingName binding name, or null to choose the only one
193         * @param writer output destination; will <b>not</b> be closed by this method
194         */
195        public static void writeObject(final Object obj, String bindingName, final Writer writer) throws JiBXException, IOException {
196            IBindingFactory bindingFactory = bindingName != null ?
197              BindingDirectory.getFactory(bindingName, obj.getClass()) : BindingDirectory.getFactory(obj.getClass());
198            final IMarshallingContext marshallingContext = bindingFactory.createMarshallingContext();
199            try {
200                IdGenerator.run(new Callable<Void>() {
201                    @Override
202                    public Void call() throws Exception {
203                        BufferedWriter bufferedWriter = new BufferedWriter(writer);
204                        marshallingContext.setIndent(4);
205                        marshallingContext.setOutput(bufferedWriter);
206                        marshallingContext.startDocument(XML_ENCODING, null);
207                        ((IMarshallable)obj).marshal(marshallingContext);
208                        marshallingContext.getXmlWriter().flush();
209                        bufferedWriter.newLine();
210                        bufferedWriter.flush();
211                        return null;
212                    }
213                });
214            } catch (Exception e) {
215                JiBXUtil.unwrapException(e);
216            }
217        }
218    
219        /**
220         * Encode the given instance as an XML document and return it as a {@link String}.
221         * This method assumes there is exactly one binding for the given class.
222         *
223         * <p>
224         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
225         * (see {@link IdMapper}).
226         *
227         * @param obj object to encode
228         * @throws JiBXException if there is a JiBX encoding error
229         */
230        public static String toString(Object obj) throws JiBXException {
231            return JiBXUtil.toString(obj, null);
232        }
233    
234        /**
235         * Encode the given instance as an XML document and return it as a {@link String}.
236         *
237         * <p>
238         * This method runs within a new invocation of {@link IdGenerator#run(Callable) IdGenerator.run()} to support object references
239         * (see {@link IdMapper}).
240         *
241         * @param obj object to encode
242         * @param bindingName binding name, or null to choose the only one
243         * @throws JiBXException if there is a JiBX encoding error
244         */
245        public static String toString(Object obj, String bindingName) throws JiBXException {
246            final StringWriter w = new StringWriter();
247            try {
248                JiBXUtil.writeObject(obj, bindingName, w);
249                w.close();
250            } catch (IOException e) {
251                throw new JiBXException("unexpected exception", e);
252            }
253            return w.toString();
254        }
255    
256        private static void unwrapException(Exception e) throws JiBXException, IOException {
257            if (e instanceof JiBXException)
258                throw (JiBXException)e.getCause();
259            if (e instanceof IOException)
260                throw (IOException)e.getCause();
261            if (e instanceof RuntimeException)
262                throw (RuntimeException)e;
263            throw new RuntimeException(e);
264        }
265    }
266