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