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