Home | History | Annotate | Download | only in util
      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 org.xmlpull.v1.XmlSerializer;
     20 
     21 import java.io.IOException;
     22 import java.io.OutputStream;
     23 import java.io.OutputStreamWriter;
     24 import java.io.UnsupportedEncodingException;
     25 import java.io.Writer;
     26 import java.nio.ByteBuffer;
     27 import java.nio.CharBuffer;
     28 import java.nio.charset.Charset;
     29 import java.nio.charset.CharsetEncoder;
     30 import java.nio.charset.CoderResult;
     31 import java.nio.charset.CodingErrorAction;
     32 import java.nio.charset.IllegalCharsetNameException;
     33 import 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  */
     40 public 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