FastXmlSerializer.java revision 8a2ed1d7c0c4f6476e39cc37d9ebd29c7562ce01
1/*
2 * Copyright (C) 2006 The Android Open Source Project
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
17package com.android.internal.util;
18
19import org.xmlpull.v1.XmlSerializer;
20
21import java.io.IOException;
22import java.io.OutputStream;
23import java.io.OutputStreamWriter;
24import java.io.UnsupportedEncodingException;
25import java.io.Writer;
26import java.nio.ByteBuffer;
27import java.nio.CharBuffer;
28import java.nio.charset.Charset;
29import java.nio.charset.CharsetEncoder;
30import java.nio.charset.CoderResult;
31import java.nio.charset.IllegalCharsetNameException;
32import java.nio.charset.UnsupportedCharsetException;
33
34/**
35 * This is a quick and dirty implementation of XmlSerializer that isn't horribly
36 * painfully slow like the normal one.  It only does what is needed for the
37 * specific XML files being written with it.
38 */
39public class FastXmlSerializer implements XmlSerializer {
40    private static final String ESCAPE_TABLE[] = new String[] {
41        null,     null,     null,     null,     null,     null,     null,     null,  // 0-7
42        null,     null,     null,     null,     null,     null,     null,     null,  // 8-15
43        null,     null,     null,     null,     null,     null,     null,     null,  // 16-23
44        null,     null,     null,     null,     null,     null,     null,     null,  // 24-31
45        null,     null,     """, null,     null,     null,     "&",  null,  // 32-39
46        null,     null,     null,     null,     null,     null,     null,     null,  // 40-47
47        null,     null,     null,     null,     null,     null,     null,     null,  // 48-55
48        null,     null,     null,     null,     "<",   null,     ">",   null,  // 56-63
49    };
50
51    private static final int BUFFER_LEN = 8192;
52
53    private static String sSpace = "                                                              ";
54
55    private final char[] mText = new char[BUFFER_LEN];
56    private int mPos;
57
58    private Writer mWriter;
59
60    private OutputStream mOutputStream;
61    private CharsetEncoder mCharset;
62    private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN);
63
64    private boolean mIndent = false;
65    private boolean mInTag;
66
67    private int mNesting = 0;
68    private boolean mLineStart = true;
69
70    private void append(char c) throws IOException {
71        int pos = mPos;
72        if (pos >= (BUFFER_LEN-1)) {
73            flush();
74            pos = mPos;
75        }
76        mText[pos] = c;
77        mPos = pos+1;
78    }
79
80    private void append(String str, int i, final int length) throws IOException {
81        if (length > BUFFER_LEN) {
82            final int end = i + length;
83            while (i < end) {
84                int next = i + BUFFER_LEN;
85                append(str, i, next<end ? BUFFER_LEN : (end-i));
86                i = next;
87            }
88            return;
89        }
90        int pos = mPos;
91        if ((pos+length) > BUFFER_LEN) {
92            flush();
93            pos = mPos;
94        }
95        str.getChars(i, i+length, mText, pos);
96        mPos = pos + length;
97    }
98
99    private void append(char[] buf, int i, final int length) throws IOException {
100        if (length > BUFFER_LEN) {
101            final int end = i + length;
102            while (i < end) {
103                int next = i + BUFFER_LEN;
104                append(buf, i, next<end ? BUFFER_LEN : (end-i));
105                i = next;
106            }
107            return;
108        }
109        int pos = mPos;
110        if ((pos+length) > BUFFER_LEN) {
111            flush();
112            pos = mPos;
113        }
114        System.arraycopy(buf, i, mText, pos, length);
115        mPos = pos + length;
116    }
117
118    private void append(String str) throws IOException {
119        append(str, 0, str.length());
120    }
121
122    private void appendIndent(int indent) throws IOException {
123        indent *= 4;
124        if (indent > sSpace.length()) {
125            indent = sSpace.length();
126        }
127        append(sSpace, 0, indent);
128    }
129
130    private void escapeAndAppendString(final String string) throws IOException {
131        final int N = string.length();
132        final char NE = (char)ESCAPE_TABLE.length;
133        final String[] escapes = ESCAPE_TABLE;
134        int lastPos = 0;
135        int pos;
136        for (pos=0; pos<N; pos++) {
137            char c = string.charAt(pos);
138            if (c >= NE) continue;
139            String escape = escapes[c];
140            if (escape == null) continue;
141            if (lastPos < pos) append(string, lastPos, pos-lastPos);
142            lastPos = pos + 1;
143            append(escape);
144        }
145        if (lastPos < pos) append(string, lastPos, pos-lastPos);
146    }
147
148    private void escapeAndAppendString(char[] buf, int start, int len) throws IOException {
149        final char NE = (char)ESCAPE_TABLE.length;
150        final String[] escapes = ESCAPE_TABLE;
151        int end = start+len;
152        int lastPos = start;
153        int pos;
154        for (pos=start; pos<end; pos++) {
155            char c = buf[pos];
156            if (c >= NE) continue;
157            String escape = escapes[c];
158            if (escape == null) continue;
159            if (lastPos < pos) append(buf, lastPos, pos-lastPos);
160            lastPos = pos + 1;
161            append(escape);
162        }
163        if (lastPos < pos) append(buf, lastPos, pos-lastPos);
164    }
165
166    public XmlSerializer attribute(String namespace, String name, String value) throws IOException,
167            IllegalArgumentException, IllegalStateException {
168        append(' ');
169        if (namespace != null) {
170            append(namespace);
171            append(':');
172        }
173        append(name);
174        append("=\"");
175
176        escapeAndAppendString(value);
177        append('"');
178        mLineStart = false;
179        return this;
180    }
181
182    public void cdsect(String text) throws IOException, IllegalArgumentException,
183            IllegalStateException {
184        throw new UnsupportedOperationException();
185    }
186
187    public void comment(String text) throws IOException, IllegalArgumentException,
188            IllegalStateException {
189        throw new UnsupportedOperationException();
190    }
191
192    public void docdecl(String text) throws IOException, IllegalArgumentException,
193            IllegalStateException {
194        throw new UnsupportedOperationException();
195    }
196
197    public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException {
198        flush();
199    }
200
201    public XmlSerializer endTag(String namespace, String name) throws IOException,
202            IllegalArgumentException, IllegalStateException {
203        mNesting--;
204        if (mInTag) {
205            append(" />\n");
206        } else {
207            if (mIndent && mLineStart) {
208                appendIndent(mNesting);
209            }
210            append("</");
211            if (namespace != null) {
212                append(namespace);
213                append(':');
214            }
215            append(name);
216            append(">\n");
217        }
218        mLineStart = true;
219        mInTag = false;
220        return this;
221    }
222
223    public void entityRef(String text) throws IOException, IllegalArgumentException,
224            IllegalStateException {
225        throw new UnsupportedOperationException();
226    }
227
228    private void flushBytes() throws IOException {
229        int position;
230        if ((position = mBytes.position()) > 0) {
231            mBytes.flip();
232            mOutputStream.write(mBytes.array(), 0, position);
233            mBytes.clear();
234        }
235    }
236
237    public void flush() throws IOException {
238        //Log.i("PackageManager", "flush mPos=" + mPos);
239        if (mPos > 0) {
240            if (mOutputStream != null) {
241                CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos);
242                CoderResult result = mCharset.encode(charBuffer, mBytes, true);
243                while (true) {
244                    if (result.isError()) {
245                        throw new IOException(result.toString());
246                    } else if (result.isOverflow()) {
247                        flushBytes();
248                        result = mCharset.encode(charBuffer, mBytes, true);
249                        continue;
250                    }
251                    break;
252                }
253                flushBytes();
254                mOutputStream.flush();
255            } else {
256                mWriter.write(mText, 0, mPos);
257                mWriter.flush();
258            }
259            mPos = 0;
260        }
261    }
262
263    public int getDepth() {
264        throw new UnsupportedOperationException();
265    }
266
267    public boolean getFeature(String name) {
268        throw new UnsupportedOperationException();
269    }
270
271    public String getName() {
272        throw new UnsupportedOperationException();
273    }
274
275    public String getNamespace() {
276        throw new UnsupportedOperationException();
277    }
278
279    public String getPrefix(String namespace, boolean generatePrefix)
280            throws IllegalArgumentException {
281        throw new UnsupportedOperationException();
282    }
283
284    public Object getProperty(String name) {
285        throw new UnsupportedOperationException();
286    }
287
288    public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException,
289            IllegalStateException {
290        throw new UnsupportedOperationException();
291    }
292
293    public void processingInstruction(String text) throws IOException, IllegalArgumentException,
294            IllegalStateException {
295        throw new UnsupportedOperationException();
296    }
297
298    public void setFeature(String name, boolean state) throws IllegalArgumentException,
299            IllegalStateException {
300        if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) {
301            mIndent = true;
302            return;
303        }
304        throw new UnsupportedOperationException();
305    }
306
307    public void setOutput(OutputStream os, String encoding) throws IOException,
308            IllegalArgumentException, IllegalStateException {
309        if (os == null)
310            throw new IllegalArgumentException();
311        if (true) {
312            try {
313                mCharset = Charset.forName(encoding).newEncoder();
314            } catch (IllegalCharsetNameException e) {
315                throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
316                        encoding).initCause(e));
317            } catch (UnsupportedCharsetException e) {
318                throw (UnsupportedEncodingException) (new UnsupportedEncodingException(
319                        encoding).initCause(e));
320            }
321            mOutputStream = os;
322        } else {
323            setOutput(
324                encoding == null
325                    ? new OutputStreamWriter(os)
326                    : new OutputStreamWriter(os, encoding));
327        }
328    }
329
330    public void setOutput(Writer writer) throws IOException, IllegalArgumentException,
331            IllegalStateException {
332        mWriter = writer;
333    }
334
335    public void setPrefix(String prefix, String namespace) throws IOException,
336            IllegalArgumentException, IllegalStateException {
337        throw new UnsupportedOperationException();
338    }
339
340    public void setProperty(String name, Object value) throws IllegalArgumentException,
341            IllegalStateException {
342        throw new UnsupportedOperationException();
343    }
344
345    public void startDocument(String encoding, Boolean standalone) throws IOException,
346            IllegalArgumentException, IllegalStateException {
347        append("<?xml version='1.0' encoding='utf-8' standalone='"
348                + (standalone ? "yes" : "no") + "' ?>\n");
349        mLineStart = true;
350    }
351
352    public XmlSerializer startTag(String namespace, String name) throws IOException,
353            IllegalArgumentException, IllegalStateException {
354        if (mInTag) {
355            append(">\n");
356        }
357        if (mIndent) {
358            appendIndent(mNesting);
359        }
360        mNesting++;
361        append('<');
362        if (namespace != null) {
363            append(namespace);
364            append(':');
365        }
366        append(name);
367        mInTag = true;
368        mLineStart = false;
369        return this;
370    }
371
372    public XmlSerializer text(char[] buf, int start, int len) throws IOException,
373            IllegalArgumentException, IllegalStateException {
374        if (mInTag) {
375            append(">");
376            mInTag = false;
377        }
378        escapeAndAppendString(buf, start, len);
379        if (mIndent) {
380            mLineStart = buf[start+len-1] == '\n';
381        }
382        return this;
383    }
384
385    public XmlSerializer text(String text) throws IOException, IllegalArgumentException,
386            IllegalStateException {
387        if (mInTag) {
388            append(">");
389            mInTag = false;
390        }
391        escapeAndAppendString(text);
392        if (mIndent) {
393            mLineStart = text.charAt(text.length()-1) == '\n';
394        }
395        return this;
396    }
397
398}
399