/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; /** * This is a quick and dirty implementation of XmlSerializer that isn't horribly * painfully slow like the normal one. It only does what is needed for the * specific XML files being written with it. */ public class FastXmlSerializer implements XmlSerializer { private static final String ESCAPE_TABLE[] = new String[] { "�", "", "", "", "", "", "", "", // 0-7 "", " ", " ", " ", " ", " ", "", "", // 8-15 "", "", "", "", "", "", "", "", // 16-23 "", "", "", "", "", "", "", "", // 24-31 null, null, """, null, null, null, "&", null, // 32-39 null, null, null, null, null, null, null, null, // 40-47 null, null, null, null, null, null, null, null, // 48-55 null, null, null, null, "<", null, ">", null, // 56-63 }; private static final int DEFAULT_BUFFER_LEN = 32*1024; private static String sSpace = " "; private final int mBufferLen; private final char[] mText; private int mPos; private Writer mWriter; private OutputStream mOutputStream; private CharsetEncoder mCharset; private ByteBuffer mBytes; private boolean mIndent = false; private boolean mInTag; private int mNesting = 0; private boolean mLineStart = true; public FastXmlSerializer() { this(DEFAULT_BUFFER_LEN); } /** * Allocate a FastXmlSerializer with the given internal output buffer size. If the * size is zero or negative, then the default buffer size will be used. * * @param bufferSize Size in bytes of the in-memory output buffer that the writer will use. */ public FastXmlSerializer(int bufferSize) { mBufferLen = (bufferSize > 0) ? bufferSize : DEFAULT_BUFFER_LEN; mText = new char[mBufferLen]; mBytes = ByteBuffer.allocate(mBufferLen); } private void append(char c) throws IOException { int pos = mPos; if (pos >= (mBufferLen-1)) { flush(); pos = mPos; } mText[pos] = c; mPos = pos+1; } private void append(String str, int i, final int length) throws IOException { if (length > mBufferLen) { final int end = i + length; while (i < end) { int next = i + mBufferLen; append(str, i, next mBufferLen) { flush(); pos = mPos; } str.getChars(i, i+length, mText, pos); mPos = pos + length; } private void append(char[] buf, int i, final int length) throws IOException { if (length > mBufferLen) { final int end = i + length; while (i < end) { int next = i + mBufferLen; append(buf, i, next mBufferLen) { flush(); pos = mPos; } System.arraycopy(buf, i, mText, pos, length); mPos = pos + length; } private void append(String str) throws IOException { append(str, 0, str.length()); } private void appendIndent(int indent) throws IOException { indent *= 4; if (indent > sSpace.length()) { indent = sSpace.length(); } append(sSpace, 0, indent); } private void escapeAndAppendString(final String string) throws IOException { final int N = string.length(); final char NE = (char)ESCAPE_TABLE.length; final String[] escapes = ESCAPE_TABLE; int lastPos = 0; int pos; for (pos=0; pos= NE) continue; String escape = escapes[c]; if (escape == null) continue; if (lastPos < pos) append(string, lastPos, pos-lastPos); lastPos = pos + 1; append(escape); } if (lastPos < pos) append(string, lastPos, pos-lastPos); } private void escapeAndAppendString(char[] buf, int start, int len) throws IOException { final char NE = (char)ESCAPE_TABLE.length; final String[] escapes = ESCAPE_TABLE; int end = start+len; int lastPos = start; int pos; for (pos=start; pos= NE) continue; String escape = escapes[c]; if (escape == null) continue; if (lastPos < pos) append(buf, lastPos, pos-lastPos); lastPos = pos + 1; append(escape); } if (lastPos < pos) append(buf, lastPos, pos-lastPos); } public XmlSerializer attribute(String namespace, String name, String value) throws IOException, IllegalArgumentException, IllegalStateException { append(' '); if (namespace != null) { append(namespace); append(':'); } append(name); append("=\""); escapeAndAppendString(value); append('"'); mLineStart = false; return this; } public void cdsect(String text) throws IOException, IllegalArgumentException, IllegalStateException { throw new UnsupportedOperationException(); } public void comment(String text) throws IOException, IllegalArgumentException, IllegalStateException { throw new UnsupportedOperationException(); } public void docdecl(String text) throws IOException, IllegalArgumentException, IllegalStateException { throw new UnsupportedOperationException(); } public void endDocument() throws IOException, IllegalArgumentException, IllegalStateException { flush(); } public XmlSerializer endTag(String namespace, String name) throws IOException, IllegalArgumentException, IllegalStateException { mNesting--; if (mInTag) { append(" />\n"); } else { if (mIndent && mLineStart) { appendIndent(mNesting); } append("\n"); } mLineStart = true; mInTag = false; return this; } public void entityRef(String text) throws IOException, IllegalArgumentException, IllegalStateException { throw new UnsupportedOperationException(); } private void flushBytes() throws IOException { int position; if ((position = mBytes.position()) > 0) { mBytes.flip(); mOutputStream.write(mBytes.array(), 0, position); mBytes.clear(); } } public void flush() throws IOException { //Log.i("PackageManager", "flush mPos=" + mPos); if (mPos > 0) { if (mOutputStream != null) { CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos); CoderResult result = mCharset.encode(charBuffer, mBytes, true); while (true) { if (result.isError()) { throw new IOException(result.toString()); } else if (result.isOverflow()) { flushBytes(); result = mCharset.encode(charBuffer, mBytes, true); continue; } break; } flushBytes(); mOutputStream.flush(); } else { mWriter.write(mText, 0, mPos); mWriter.flush(); } mPos = 0; } } public int getDepth() { throw new UnsupportedOperationException(); } public boolean getFeature(String name) { throw new UnsupportedOperationException(); } public String getName() { throw new UnsupportedOperationException(); } public String getNamespace() { throw new UnsupportedOperationException(); } public String getPrefix(String namespace, boolean generatePrefix) throws IllegalArgumentException { throw new UnsupportedOperationException(); } public Object getProperty(String name) { throw new UnsupportedOperationException(); } public void ignorableWhitespace(String text) throws IOException, IllegalArgumentException, IllegalStateException { throw new UnsupportedOperationException(); } public void processingInstruction(String text) throws IOException, IllegalArgumentException, IllegalStateException { throw new UnsupportedOperationException(); } public void setFeature(String name, boolean state) throws IllegalArgumentException, IllegalStateException { if (name.equals("http://xmlpull.org/v1/doc/features.html#indent-output")) { mIndent = true; return; } throw new UnsupportedOperationException(); } public void setOutput(OutputStream os, String encoding) throws IOException, IllegalArgumentException, IllegalStateException { if (os == null) throw new IllegalArgumentException(); if (true) { try { mCharset = Charset.forName(encoding).newEncoder() .onMalformedInput(CodingErrorAction.REPLACE) .onUnmappableCharacter(CodingErrorAction.REPLACE); } catch (IllegalCharsetNameException e) { throw (UnsupportedEncodingException) (new UnsupportedEncodingException( encoding).initCause(e)); } catch (UnsupportedCharsetException e) { throw (UnsupportedEncodingException) (new UnsupportedEncodingException( encoding).initCause(e)); } mOutputStream = os; } else { setOutput( encoding == null ? new OutputStreamWriter(os) : new OutputStreamWriter(os, encoding)); } } public void setOutput(Writer writer) throws IOException, IllegalArgumentException, IllegalStateException { mWriter = writer; } public void setPrefix(String prefix, String namespace) throws IOException, IllegalArgumentException, IllegalStateException { throw new UnsupportedOperationException(); } public void setProperty(String name, Object value) throws IllegalArgumentException, IllegalStateException { throw new UnsupportedOperationException(); } public void startDocument(String encoding, Boolean standalone) throws IOException, IllegalArgumentException, IllegalStateException { append("\n"); mLineStart = true; } public XmlSerializer startTag(String namespace, String name) throws IOException, IllegalArgumentException, IllegalStateException { if (mInTag) { append(">\n"); } if (mIndent) { appendIndent(mNesting); } mNesting++; append('<'); if (namespace != null) { append(namespace); append(':'); } append(name); mInTag = true; mLineStart = false; return this; } public XmlSerializer text(char[] buf, int start, int len) throws IOException, IllegalArgumentException, IllegalStateException { if (mInTag) { append(">"); mInTag = false; } escapeAndAppendString(buf, start, len); if (mIndent) { mLineStart = buf[start+len-1] == '\n'; } return this; } public XmlSerializer text(String text) throws IOException, IllegalArgumentException, IllegalStateException { if (mInTag) { append(">"); mInTag = false; } escapeAndAppendString(text); if (mIndent) { mLineStart = text.length() > 0 && (text.charAt(text.length()-1) == '\n'); } return this; } }