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