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