KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > xmlrpc > XmlWriter


1 /*
2  * Copyright 1999,2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17
18 package org.apache.xmlrpc;
19
20 import java.io.IOException JavaDoc;
21 import java.io.OutputStream JavaDoc;
22 import java.io.OutputStreamWriter JavaDoc;
23 import java.io.UnsupportedEncodingException JavaDoc;
24 import java.util.Date JavaDoc;
25 import java.util.Enumeration JavaDoc;
26 import java.util.Hashtable JavaDoc;
27 import java.util.Properties JavaDoc;
28 import java.util.Vector JavaDoc;
29
30 import org.apache.xmlrpc.util.DateTool;
31 import org.apache.commons.codec.binary.Base64;
32 import org.apache.commons.codec.EncoderException;
33
34 /**
35  * A XML writer intended for single-thread usage. If you feed it a
36  * <code>ByteArrayInputStream</code>, it may be necessary to call
37  * <code>writer.flush()</code> before calling
38  * <code>buffer.toByteArray()</code> to get the data written to
39  * your byte buffer.
40  *
41  * @author <a HREF="mailto:hannes@apache.org">Hannes Wallnoefer</a>
42  * @author Daniel L. Rall
43  * @see <a HREF="http://www.xml.com/axml/testaxml.htm">Tim Bray's
44  * Annotated XML Spec</a>
45  */

46 class XmlWriter extends OutputStreamWriter JavaDoc
47 {
48     // Various XML pieces.
49
protected static final String JavaDoc PROLOG_START = "<?xml version=\"1.0";
50     protected static final String JavaDoc PROLOG_END = "\"?>";
51     protected static final String JavaDoc CLOSING_TAG_START = "</";
52     protected static final String JavaDoc SINGLE_TAG_END = "/>";
53     protected static final String JavaDoc LESS_THAN_ENTITY = "&lt;";
54     protected static final String JavaDoc GREATER_THAN_ENTITY = "&gt;";
55     protected static final String JavaDoc AMPERSAND_ENTITY = "&amp;";
56
57     private static final char[] PROLOG =
58         new char[PROLOG_START.length() + PROLOG_END.length()];
59     static
60     {
61         int len = PROLOG_START.length();
62         PROLOG_START.getChars(0, len, PROLOG, 0);
63         PROLOG_END.getChars(0, PROLOG_END.length(), PROLOG, len);
64     }
65
66     /**
67      * Java's name for the ISO-8859-1 encoding.
68      */

69     static final String JavaDoc ISO8859_1 = "ISO8859_1";
70
71     /**
72      * Java's name for the UTF-8 encoding.
73      */

74     static final String JavaDoc UTF8 = "UTF8";
75
76     /**
77      * Java's name for the UTF-16 encoding.
78      */

79     static final String JavaDoc UTF16 = "UTF-16";
80     
81     protected static final Base64 base64Codec = new Base64();
82
83     /**
84      * Class to delegate type decoding to.
85      */

86     protected static TypeDecoder typeDecoder;
87
88     /**
89      * Mapping between Java encoding names and "real" names used in
90      * XML prolog.
91      *
92      * @see <a HREF="http://java.sun.com/j2se/1.4.2/docs/guide/intl/encoding.doc.html">Java character set names</a>
93      */

94     private static Properties JavaDoc encodings = new Properties JavaDoc();
95
96     static
97     {
98         encodings.put(UTF8, "UTF-8");
99         encodings.put(ISO8859_1, "ISO-8859-1");
100         typeDecoder = new DefaultTypeDecoder();
101     }
102
103     /**
104      * Thread-safe wrapper for the <code>DateFormat</code> object used
105      * to parse date/time values.
106      */

107     private static DateTool dateTool = new DateTool();
108
109     /**
110      * Whether the XML prolog has been written.
111      */

112     boolean hasWrittenProlog = false;
113
114     /**
115      * Creates a new instance.
116      *
117      * @param out The stream to write output to.
118      * @param enc The encoding to using for outputing XML. Only UTF-8
119      * and UTF-16 are supported. If another encoding is specified,
120      * UTF-8 will be used instead for widest XML parser
121      * interoperability.
122      * @exception UnsupportedEncodingException Since unsupported
123      * encodings are internally converted to UTF-8, this should only
124      * be seen as the result of an internal error.
125      */

126     public XmlWriter(OutputStream JavaDoc out, String JavaDoc enc)
127         throws UnsupportedEncodingException JavaDoc
128     {
129         // Super-class wants the Java form of the encoding.
130
super(out, forceUnicode(enc));
131     }
132
133     /**
134      * @param encoding A caller-specified encoding.
135      * @return An Unicode encoding.
136      */

137     private static String JavaDoc forceUnicode(String JavaDoc encoding)
138     {
139         if (encoding == null || !encoding.toUpperCase().startsWith("UTF"))
140         {
141             encoding = UTF8;
142         }
143         return encoding;
144     }
145
146     /**
147      * Tranforms a Java encoding to the canonical XML form (if a
148      * mapping is available).
149      *
150      * @param javaEncoding The name of the encoding as known by Java.
151      * @return The XML encoding (if a mapping is available);
152      * otherwise, the encoding as provided.
153      *
154      * @deprecated This method will not be visible in 2.0.
155      */

156     protected static String JavaDoc canonicalizeEncoding(String JavaDoc javaEncoding)
157     {
158         return encodings.getProperty(javaEncoding, javaEncoding);
159     }
160
161     /**
162      * A mostly pass-through implementation wrapping
163      * <code>OutputStreamWriter.write()</code> which assures that the
164      * XML prolog is written before any other data.
165      *
166      * @see java.io.OutputStreamWriter.write(char[], int, int)
167      */

168     public void write(char[] cbuf, int off, int len)
169         throws IOException JavaDoc
170     {
171         if (!hasWrittenProlog)
172         {
173             super.write(PROLOG, 0, PROLOG.length);
174             hasWrittenProlog = true;
175         }
176         super.write(cbuf, off, len);
177     }
178
179     /**
180      * A mostly pass-through implementation wrapping
181      * <code>OutputStreamWriter.write()</code> which assures that the
182      * XML prolog is written before any other data.
183      *
184      * @see java.io.OutputStreamWriter.write(char)
185      */

186     public void write(char c)
187         throws IOException JavaDoc
188     {
189         if (!hasWrittenProlog)
190         {
191             super.write(PROLOG, 0, PROLOG.length);
192             hasWrittenProlog = true;
193         }
194         super.write(c);
195     }
196
197     /**
198      * A mostly pass-through implementation wrapping
199      * <code>OutputStreamWriter.write()</code> which assures that the
200      * XML prolog is written before any other data.
201      *
202      * @see java.io.OutputStreamWriter.write(String, int, int)
203      */

204     public void write(String JavaDoc str, int off, int len)
205         throws IOException JavaDoc
206     {
207         if (!hasWrittenProlog)
208         {
209             super.write(PROLOG, 0, PROLOG.length);
210             hasWrittenProlog = true;
211         }
212         super.write(str, off, len);
213     }
214
215     /**
216      * Writes the XML representation of a supported Java object type.
217      *
218      * @param obj The <code>Object</code> to write.
219      * @exception XmlRpcException Unsupported character data found.
220      * @exception IOException Problem writing data.
221      * @throws IllegalArgumentException If a <code>null</code>
222      * parameter is passed to this method (not supported by the <a
223      * HREF="http://xml-rpc.com/spec">XML-RPC specification</a>).
224      */

225     public void writeObject(Object JavaDoc obj)
226         throws XmlRpcException, IOException JavaDoc
227     {
228         startElement("value");
229         if (obj == null)
230         {
231             throw new XmlRpcException
232                 (0, "null values not supported by XML-RPC");
233         }
234         else if (obj instanceof String JavaDoc)
235         {
236             chardata(obj.toString());
237         }
238         else if (typeDecoder.isXmlRpcI4(obj))
239         {
240             startElement("int");
241             write(obj.toString());
242             endElement("int");
243         }
244         else if (obj instanceof Boolean JavaDoc)
245         {
246             startElement("boolean");
247             write(((Boolean JavaDoc) obj).booleanValue() ? "1" : "0");
248             endElement("boolean");
249         }
250         else if (typeDecoder.isXmlRpcDouble(obj))
251         {
252             startElement("double");
253             write(obj.toString());
254             endElement("double");
255         }
256         else if (obj instanceof Date JavaDoc)
257         {
258             startElement("dateTime.iso8601");
259             Date JavaDoc d = (Date JavaDoc) obj;
260             write(dateTool.format(d));
261             endElement("dateTime.iso8601");
262         }
263         else if (obj instanceof byte[])
264         {
265             startElement("base64");
266             try
267             {
268                 this.write((byte[]) base64Codec.encode(obj));
269             }
270             catch (EncoderException e)
271             {
272                 throw new XmlRpcException
273                     (0, "Unable to Base 64 encode byte array", e);
274             }
275             endElement("base64");
276         }
277         else if (obj instanceof Object JavaDoc[])
278         {
279             startElement("array");
280             startElement("data");
281             Object JavaDoc[] array = (Object JavaDoc []) obj;
282             for (int i = 0; i < array.length; i++)
283             {
284                 writeObject(array[i]);
285             }
286             endElement("data");
287             endElement("array");
288         }
289         else if (obj instanceof Vector JavaDoc)
290         {
291             startElement("array");
292             startElement("data");
293             Vector JavaDoc array = (Vector JavaDoc) obj;
294             int size = array.size();
295             for (int i = 0; i < size; i++)
296             {
297                 writeObject(array.elementAt(i));
298             }
299             endElement("data");
300             endElement("array");
301         }
302         else if (obj instanceof Hashtable JavaDoc)
303         {
304             startElement("struct");
305             Hashtable JavaDoc struct = (Hashtable JavaDoc) obj;
306             for (Enumeration JavaDoc e = struct.keys(); e.hasMoreElements(); )
307             {
308                 String JavaDoc key = (String JavaDoc) e.nextElement();
309                 Object JavaDoc value = struct.get(key);
310                 startElement("member");
311                 startElement("name");
312                 chardata(key);
313                 endElement("name");
314                 writeObject(value);
315                 endElement("member");
316             }
317             endElement("struct");
318         }
319         else
320         {
321             throw new XmlRpcException(0, "Unsupported Java type: "
322                                        + obj.getClass(), null);
323         }
324         endElement("value");
325     }
326
327     /**
328      * This is used to write out the Base64 output...
329      */

330     protected void write(byte[] byteData) throws IOException JavaDoc
331     {
332         for (int i = 0; i < byteData.length; i++)
333         {
334             write(byteData[i]);
335         }
336     }
337
338     /**
339      * Writes characters like '\r' (0xd) as "&amp;#13;".
340      */

341     private void writeCharacterReference(char c)
342         throws IOException JavaDoc
343     {
344         write("&#");
345         write(String.valueOf((int) c));
346         write(';');
347     }
348
349     /**
350      *
351      * @param elem
352      * @throws IOException
353      */

354     protected void startElement(String JavaDoc elem) throws IOException JavaDoc
355     {
356         write('<');
357         write(elem);
358         write('>');
359     }
360
361     /**
362      *
363      * @param elem
364      * @throws IOException
365      */

366     protected void endElement(String JavaDoc elem) throws IOException JavaDoc
367     {
368         write(CLOSING_TAG_START);
369         write(elem);
370         write('>');
371     }
372
373     /**
374      *
375      * @param elem
376      * @throws IOException
377      */

378     protected void emptyElement(String JavaDoc elem) throws IOException JavaDoc
379     {
380         write('<');
381         write(elem);
382         write(SINGLE_TAG_END);
383     }
384
385     /**
386      * Writes text as <code>PCDATA</code>.
387      *
388      * @param text The data to write.
389      * @exception XmlRpcException Unsupported character data found.
390      * @exception IOException Problem writing data.
391      */

392     protected void chardata(String JavaDoc text)
393         throws XmlRpcException, IOException JavaDoc
394     {
395         int l = text.length ();
396         // ### TODO: Use a buffer rather than going character by
397
// ### character to scale better for large text sizes.
398
//char[] buf = new char[32];
399
for (int i = 0; i < l; i++)
400         {
401             char c = text.charAt (i);
402             switch (c)
403             {
404             case '\t':
405             case '\n':
406                 write(c);
407                 break;
408             case '\r':
409                 // Avoid normalization of CR to LF.
410
writeCharacterReference(c);
411                 break;
412             case '<':
413                 write(LESS_THAN_ENTITY);
414                 break;
415             case '>':
416                 write(GREATER_THAN_ENTITY);
417                 break;
418             case '&':
419                 write(AMPERSAND_ENTITY);
420                 break;
421             default:
422                 // Though the XML spec requires XML parsers to support
423
// Unicode, not all such code points are valid in XML
424
// documents. Additionally, previous to 2003-06-30
425
// the XML-RPC spec only allowed ASCII data (in
426
// <string> elements). For interoperability with
427
// clients rigidly conforming to the pre-2003 version
428
// of the XML-RPC spec, we entity encode characters
429
// outside of the valid range for ASCII, too.
430
if (c > 0x7f || !isValidXMLChar(c))
431                 {
432                     // Replace the code point with a character reference.
433
writeCharacterReference(c);
434                 }
435                 else
436                 {
437                     write(c);
438                 }
439             }
440         }
441     }
442
443     /**
444      * Section 2.2 of the XML spec describes which Unicode code points
445      * are valid in XML:
446      *
447      * <blockquote><code>#x9 | #xA | #xD | [#x20-#xD7FF] |
448      * [#xE000-#xFFFD] | [#x10000-#x10FFFF]</code></blockquote>
449      *
450      * Code points outside this set must be entity encoded to be
451      * represented in XML.
452      *
453      * @param c The character to inspect.
454      * @return Whether the specified character is valid in XML.
455      */

456     private static final boolean isValidXMLChar(char c)
457     {
458         switch (c)
459         {
460         case 0x9:
461         case 0xa: // line feed, '\n'
462
case 0xd: // carriage return, '\r'
463
return true;
464
465         default:
466             return ( (0x20 < c && c <= 0xd7ff) ||
467                      (0xe000 < c && c <= 0xfffd) ||
468                      (0x10000 < c && c <= 0x10ffff) );
469         }
470     }
471
472     protected static void setTypeDecoder(TypeDecoder newTypeDecoder)
473     {
474         typeDecoder = newTypeDecoder;
475     }
476 }
477
Popular Tags