1 package org.apache.xmlrpc;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 import java.io.IOException;
59 import java.io.OutputStream;
60 import java.io.OutputStreamWriter;
61 import java.io.UnsupportedEncodingException;
62 import java.util.Date;
63 import java.util.Enumeration;
64 import java.util.Hashtable;
65 import java.util.Properties;
66 import java.util.Vector;
67
68 import org.apache.xmlrpc.util.DateTool;
69
70 /***
71 * A quick and dirty XML writer. If you feed it a
72 * <code>ByteArrayInputStream</code>, it may be necessary to call
73 * <code>writer.flush()</code> before calling
74 * <code>buffer.toByteArray()</code> to get the data written to
75 * your byte buffer.
76 *
77 * @author <a href="mailto:hannes@apache.org">Hannes Wallnoefer</a>
78 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
79 */
80 class XmlWriter extends OutputStreamWriter {
81
82 protected static final String PROLOG_START =
83 "<?xml version=\"1.0\" encoding=\"";
84 protected static final String PROLOG_END = "\"?>";
85 protected static final String CLOSING_TAG_START = "</";
86 protected static final String SINGLE_TAG_END = "/>";
87 protected static final String LESS_THAN_ENTITY = "<";
88 protected static final String GREATER_THAN_ENTITY = ">";
89 protected static final String AMPERSAND_ENTITY = "&";
90
91 /***
92 * Java's name for the the ISO8859_1 encoding.
93 */
94 protected static final String ISO8859_1 = "ISO8859_1";
95
96 /***
97 * Java's name for the the UTF8 encoding.
98 */
99 protected static final String UTF8 = "UTF8";
100
101 /***
102 * Mapping between Java encoding names and "real" names used in
103 * XML prolog.
104 */
105 private static Properties encodings = new Properties();
106
107 static {
108 encodings.put(UTF8, "UTF-8");
109 encodings.put(ISO8859_1, "ISO-8859-1");
110 }
111
112 /***
113 * Thread-safe wrapper for the <code>DateFormat</code> object used
114 * to parse date/time values.
115 */
116 private static DateTool dateTool = new DateTool();
117
118 /***
119 * Creates a new instance.
120 *
121 * @param out The stream to write output to.
122 * @param enc The encoding to using for outputing XML.
123 * @throws UnsupportedEncodingException Encoding unrecognized.
124 * @throws IOException Problem writing.
125 */
126 public XmlWriter(OutputStream out, String enc)
127 throws UnsupportedEncodingException, IOException {
128
129 super(out, enc);
130
131
132 write(PROLOG_START);
133 write(canonicalizeEncoding(enc));
134 write(PROLOG_END);
135 }
136
137 /***
138 * Tranforms a Java encoding to the canonical XML form (if a
139 * mapping is available).
140 *
141 * @param javaEncoding The name of the encoding as known by Java.
142 * @return The XML encoding (if a mapping is available);
143 * otherwise, the encoding as provided.
144 */
145 protected static String canonicalizeEncoding(String javaEncoding) {
146 return encodings.getProperty(javaEncoding, javaEncoding);
147 }
148
149 /***
150 * Writes the XML representation of a supported Java object type.
151 *
152 * @param obj The <code>Object</code> to write.
153 * @exception XmlRpcException Unsupported character data found.
154 * @exception IOException Problem writing data.
155 * @throws IllegalArgumentException If a <code>null</code>
156 * parameter is passed to this method (not supported by the <a
157 * href="http://xml-rpc.com/spec">XML-RPC specification</a>).
158 */
159 public void writeObject(Object obj) throws XmlRpcException, IOException {
160 startElement("value");
161 if (obj == null) {
162 throw new IllegalArgumentException("null values not supported by XML-RPC");
163 } else if (obj instanceof String) {
164 chardata(obj.toString());
165 } else if (obj instanceof Integer) {
166 startElement("int");
167 write(obj.toString());
168 endElement("int");
169 } else if (obj instanceof Boolean) {
170 startElement("boolean");
171 write(((Boolean)obj).booleanValue() ? "1" : "0");
172 endElement("boolean");
173 } else if (obj instanceof Double || obj instanceof Float) {
174 startElement("double");
175 write(obj.toString());
176 endElement("double");
177 } else if (obj instanceof Date) {
178 startElement("dateTime.iso8601");
179 Date d = (Date)obj;
180 write(dateTool.format(d));
181 endElement("dateTime.iso8601");
182 } else if (obj instanceof byte[]) {
183 startElement("base64");
184 this.write(Base64.encode((byte[])obj));
185 endElement("base64");
186 } else if (obj instanceof Object[]) {
187 startElement("array");
188 startElement("data");
189 Object[] array = (Object[])obj;
190 for (int i = 0; i < array.length; i++) {
191 writeObject(array[i]);
192 }
193 endElement("data");
194 endElement("array");
195 } else if (obj instanceof Vector) {
196 startElement("array");
197 startElement("data");
198 Vector array = (Vector)obj;
199 int size = array.size();
200 for (int i = 0; i < size; i++) {
201 writeObject(array.elementAt(i));
202 }
203 endElement("data");
204 endElement("array");
205 } else if (obj instanceof Hashtable) {
206 startElement("struct");
207 Hashtable struct = (Hashtable)obj;
208 for (Enumeration e = struct.keys(); e.hasMoreElements();) {
209 String key = (String)e.nextElement();
210 Object value = struct.get(key);
211 startElement("member");
212 startElement("name");
213 chardata(key);
214 endElement("name");
215 writeObject(value);
216 endElement("member");
217 }
218 endElement("struct");
219 } else {
220 throw new RuntimeException(
221 "unsupported Java type: " + obj.getClass());
222 }
223 endElement("value");
224 }
225
226 /***
227 * This is used to write out the Base64 output...
228 */
229 protected void write(byte[] byteData) throws IOException {
230 for (int i = 0; i < byteData.length; i++) {
231 write(byteData[i]);
232 }
233 }
234
235 /***
236 *
237 * @param elem
238 * @throws IOException
239 */
240 protected void startElement(String elem) throws IOException {
241 write('<');
242 write(elem);
243 write('>');
244 }
245
246 /***
247 *
248 * @param elem
249 * @throws IOException
250 */
251 protected void endElement(String elem) throws IOException {
252 write(CLOSING_TAG_START);
253 write(elem);
254 write('>');
255 }
256
257 /***
258 *
259 * @param elem
260 * @throws IOException
261 */
262 protected void emptyElement(String elem) throws IOException {
263 write('<');
264 write(elem);
265 write(SINGLE_TAG_END);
266 }
267
268 /***
269 * Writes text as <code>PCDATA</code>.
270 *
271 * @param text The data to write.
272 * @exception XmlRpcException Unsupported character data found.
273 * @exception IOException Problem writing data.
274 */
275 protected void chardata(String text) throws XmlRpcException, IOException {
276 int l = text.length();
277 for (int i = 0; i < l; i++) {
278 char c = text.charAt(i);
279 switch (c) {
280 case '\t' :
281 case '\r' :
282 case '\n' :
283 write(c);
284 break;
285 case '<' :
286 write(LESS_THAN_ENTITY);
287 break;
288 case '>' :
289 write(GREATER_THAN_ENTITY);
290 break;
291 case '&' :
292 write(AMPERSAND_ENTITY);
293 break;
294 default :
295 if (c < 0x20) {
296
297
298
299
300
301 throw new XmlRpcException(
302 0,
303 "Invalid character data "
304 + "corresponding to XML entity &#"
305 + String.valueOf((int)c)
306 + ';');
307 } else {
308 write(c);
309 }
310 }
311 }
312 }
313 }