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 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