1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.store.serialization.xml.internal

File XMLWriter.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart8.png
54% of files have more coverage

Code metrics

10
52
15
2
349
140
21
0.4
3.47
7.5
1.4

Classes

Class Line # Actions
XMLWriter 57 44 0% 16 21
0.67187567.2%
XMLWriter.LastCharWriter 301 8 0% 5 0
1.0100%
 

Contributing tests

This file is covered by 9 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.store.serialization.xml.internal;
21   
22    import java.io.FilterWriter;
23    import java.io.IOException;
24    import java.io.InputStream;
25    import java.io.OutputStream;
26    import java.io.Reader;
27    import java.io.UnsupportedEncodingException;
28    import java.io.Writer;
29    import java.util.Stack;
30   
31    import org.apache.commons.codec.binary.Base64OutputStream;
32    import org.apache.commons.io.IOUtils;
33    import org.apache.commons.io.output.CloseShieldOutputStream;
34    import org.dom4j.Document;
35    import org.dom4j.Element;
36    import org.dom4j.io.OutputFormat;
37   
38    /**
39    * Extension to <code>{@link org.dom4j.io.XMLWriter}</code> to allow filling some element content
40    * with an input stream, minimizing the memory footprint of the operation.
41    * <p>
42    * This extension is not intended to be used to format a DOM4J tree to a stream, but to immediately
43    * write out the tags produced without building the document tree in memory. It is not compatible
44    * with the SAX part of the original
45    * <code>{@link org.dom4j.io.XMLWriter}</code>.
46    * </p>
47    * <p>
48    * An improvement to the writeOpen/writeClose functions ensure better handling of independent
49    * opening and closing of tags by maintaining a state stack of opened tags.
50    * New writeDocumentStart/End function also ensure proper starting and
51    * ending of the document it self.
52    * </p>
53    *
54    * @version $Id: af9502c87eb12180b0a6811a389cea408beb94ad $
55    * @since 3.0M2
56    */
 
57    public class XMLWriter extends org.dom4j.io.XMLWriter
58    {
59    /**
60    * Number of characters wide base64 content will be.
61    */
62    private static final int BASE64_WIDTH = 80;
63   
64    /**
65    * Platform dependent line seperator.
66    */
67    private static final byte[] NEWLINE;
68   
69    /**
70    * If the last character written is this then it is safe to indent the next tag.
71    */
72    private static final char CLOSE_ANGLE_BRACKET = '>';
73   
74    /**
75    * <code>{@link Stack}</code> of currently opened <code>{@link Element}</code>, the first
76    * <code>{@link Element}</code> is the document root element,
77    * and the top of the stack is the last opened
78    * <code>{@link Element}</code>.
79    */
80    protected Stack<Element> parent = new Stack<Element>();
81   
82    /**
83    * Current <code>{@link OutputStream}</code> of this writer.
84    */
85    private OutputStream out;
86   
87    /**
88    * The underlying writer which is not cast to Writer.
89    */
90    private LastCharWriter lcWriter;
91   
92    /** True if the last thing written was content from an InputStream and
93    private boolean indentUnsafe;
94   
95    /** Need to catch this exception so this has to be done in an initializer block. */
 
96  2 toggle static {
97  2 try {
98  2 NEWLINE = System.getProperty("line.separator").getBytes("UTF-8");
99    } catch (UnsupportedEncodingException e) {
100  0 throw new RuntimeException("No UTF-8, this Java VM is not standards compliant!", e);
101    }
102    }
103   
104    /**
105    * Default constructor used by <code>{@link DOMXMLWriter}</code>.
106    *
107    * @see DOMXMLWriter
108    */
 
109  0 toggle protected XMLWriter()
110    {
111    }
112   
113    /**
114    * Create a new XMLWriter writing to a provided OutputStream in a given format.
115    * Note that other constructor of the original DOM4J XMLWriter are unsupported since an
116    * OutputStream is the only way we can support the extensions provided here.
117    * <p>
118    * Note that the writer is buffered and only a call to flush() or writeDocuemntEnd()
119    * will ensure the output has been fully written to the <code>{@link OutputStream}</code>.
120    * </p>
121    *
122    * @param out an <code>{@link OutputStream}</code> where to output the XML produced.
123    * @param format an <code>{@link OutputFormat}</code> defining the encoding that
124    * should be used and esthetics of the produced XML.
125    * @throws UnsupportedEncodingException the requested encoding is unsupported.
126    */
 
127  9 toggle public XMLWriter(final OutputStream out, final OutputFormat format)
128    throws UnsupportedEncodingException
129    {
130  9 super(out, format);
131  9 this.lcWriter = new LastCharWriter(super.writer);
132  9 super.writer = this.lcWriter;
133  9 this.out = out;
134    }
135   
136    /**
137    * Write the <code>{@link Document}</code> declaration, and its <code>{@link DocumentType}</code>
138    * if available to the output stream.
139    *
140    * @param doc <code>{@link Document}</code> to be started, may specify a
141    * <code>{@link DocumentType}</code>.
142    * @throws IOException a problem occurs during writing
143    */
 
144  0 toggle public void writeDocumentStart(final Document doc) throws IOException
145    {
146  0 writeDeclaration();
147   
148  0 if (doc.getDocType() != null) {
149  0 super.indent();
150  0 super.writeDocType(doc.getDocType());
151    }
152    }
153   
154    /**
155    * Write the end of the document.
156    * Close all remaining opened <code>{@link Element}</code> including the root element to
157    * terminate the current document.
158    * Also flush the writer to ensure the whole document has been written to the
159    * <code>{@link OutputStream}</code>.
160    *
161    * @param doc <code>{@link Document}</code> to be end, actually unused.
162    * @throws IOException a problem occurs during writing.
163    */
 
164  0 toggle public void writeDocumentEnd(final Document doc) throws IOException
165    {
166  0 if (!this.parent.isEmpty()) {
167  0 this.writeClose(this.parent.firstElement());
168    }
169  0 super.writePrintln();
170  0 super.flush();
171    }
172   
173    /**
174    * Writes the <code>{@link Element}</code>, including its <code>{@link
175    * Attribute}</code>s, using the <code>{@link Reader}</code>
176    * for its content.
177    * <p>
178    * Note that proper decoding/encoding will occurs during this operation,
179    * converting the encoding of the Reader into the encoding of the Writer.
180    * </p>
181    *
182    * @param element <code>{@link Element}</code> to output.
183    * @param rd <code>{@link Reader}</code> that will be fully read and transfered
184    * into the element content.
185    * @throws IOException a problem occurs during reading or writing.
186    */
 
187  1 toggle public void write(final Element element, final Reader rd) throws IOException
188    {
189  1 this.writeOpen(element);
190  1 IOUtils.copy(rd, this.lcWriter);
191  1 this.writeClose(element);
192    }
193   
194    /**
195    * Writes the <code>{@link Element}</code>, including its <code>{@link
196    * Attribute}</code>s, using the
197    * <code>{@link InputStream}</code> for its content.
198    * <p>
199    * Note that no decoding/encoding of the InputStream will be ensured during this operation.
200    * The byte content is transfered untouched.
201    * </p>
202    *
203    * @param element <code>{@link Element}</code> to output.
204    * @param is <code>{@link InputStream}</code> that will be fully read and transfered into
205    * the element content.
206    * @throws IOException a problem occurs during reading or writing.
207    */
 
208  0 toggle public void write(final Element element, final InputStream is) throws IOException
209    {
210  0 this.writeOpen(element);
211  0 super.flush();
212  0 IOUtils.copy(is, this.out);
213   
214    // We must prevent indentation even though the
215    // last character written through the writer is a >
216  0 super.writeClose(element);
217    }
218   
219    /**
220    * Writes the <code>{@link Element}</code>, including its <code>{@link
221    * Attribute}</code>s, using the
222    * <code>{@link InputStream}</code> encoded in Base64 for its content.
223    *
224    * @param element <code>{@link Element}</code> to output.
225    * @param is <code>{@link InputStream}</code> that will be fully read and encoded
226    * in Base64 into the element content.
227    * @throws IOException a problem occurs during reading or writing.
228    */
 
229  1 toggle public void writeBase64(final Element element, final InputStream is) throws IOException
230    {
231  1 this.writeOpen(element);
232  1 super.writePrintln();
233   
234  1 super.flush();
235  1 final Base64OutputStream base64 =
236    new Base64OutputStream(new CloseShieldOutputStream(this.out), true, BASE64_WIDTH, NEWLINE);
237  1 IOUtils.copy(is, base64);
238  1 base64.close();
239   
240    // The last char written was a newline, not a > so it will not indent unless it is done manually.
241  1 super.setIndentLevel(this.parent.size() - 1);
242  1 super.indent();
243   
244  1 this.writeClose(element);
245    }
246   
247    /**
248    * Writes the opening tag of an {@link Element}.
249    * Includes its {@link Attribute}s but without its content.
250    * <p>
251    * Compared to the DOM4J implementation, this function keeps track of opened elements.
252    * </p>
253    *
254    * @param element <code>{@link Element}</code> to output.
255    * @throws IOException a problem occurs during writing.
256    * @see org.dom4j.io.XMLWriter#writeOpen(org.dom4j.Element)
257    */
 
258  27 toggle @Override
259    public void writeOpen(final Element element) throws IOException
260    {
261  27 if (this.lcWriter.getLastChar() == CLOSE_ANGLE_BRACKET) {
262  19 super.writePrintln();
263  19 super.indent();
264    }
265  27 super.writeOpen(element);
266  27 this.parent.push(element);
267  27 super.setIndentLevel(this.parent.size());
268    }
269   
270    /**
271    * Writes the closing tag of an {@link Element}.
272    * <p>
273    * Compared to the DOM4J implementation, this function ensure closing of all opened
274    * element including the one that is requested to be closed. Also writes a newline and
275    * indents the closing element if required and if the last thing written was not a string.
276    * </p>
277    *
278    * @param element <code>{@link Element}</code> to output.
279    * @throws IOException a problem occurs during writing.
280    * @see org.dom4j.io.XMLWriter#writeClose(org.dom4j.Element)
281    */
 
282  27 toggle @Override
283    public void writeClose(final Element element) throws IOException
284    {
285  32 while (!this.parent.peek().getQualifiedName().equals(element.getQualifiedName())) {
286  5 this.writeClose(this.parent.peek());
287    }
288   
289  27 super.setIndentLevel(this.parent.size() - 1);
290  27 if (this.lcWriter.getLastChar() == CLOSE_ANGLE_BRACKET) {
291  25 super.writePrintln();
292  25 super.indent();
293    }
294   
295  27 super.writeClose(this.parent.pop());
296    }
297   
298    /**
299    * An OutputStream which allows you to get the last byte which was written to it.
300    */
 
301    private static class LastCharWriter extends FilterWriter
302    {
303    /**
304    * The last byte written to the stream.
305    */
306    private char lastChar;
307   
308    /**
309    * The Constructor.
310    *
311    * @param toWrap the Writer to send all calls to.
312    */
 
313  9 toggle LastCharWriter(final Writer toWrap)
314    {
315  9 super(toWrap);
316    }
317   
 
318  1 toggle @Override
319    public void write(final char[] buffer, final int offset, final int count)
320    throws IOException
321    {
322  1 super.write(buffer, offset, count);
323  1 this.lastChar = buffer[offset + count - 1];
324    }
325   
 
326  1197 toggle @Override
327    public void write(final String str, final int offset, final int count)
328    throws IOException
329    {
330  1197 super.write(str, offset, count);
331  1197 this.lastChar = str.charAt(offset + count - 1);
332    }
333   
 
334  34 toggle @Override
335    public void write(final int oneChar) throws IOException
336    {
337  34 super.write(oneChar);
338  34 this.lastChar = (char) oneChar;
339    }
340   
341    /**
342    * @return the last character written.
343    */
 
344  54 toggle public char getLastChar()
345    {
346  54 return lastChar;
347    }
348    }
349    }