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