blob: 94e07a85867e26c8a1db0e7ffa3e95e9838a58f3 [file] [log] [blame]
/*
* 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 android.compat.annotation.UnsupportedAppUsage;
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;
@UnsupportedAppUsage
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<end ? mBufferLen : (end-i));
i = next;
}
return;
}
int pos = mPos;
if ((pos+length) > 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<end ? mBufferLen : (end-i));
i = next;
}
return;
}
int pos = mPos;
if ((pos+length) > 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<N; pos++) {
char c = string.charAt(pos);
if (c >= 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<end; pos++) {
char c = buf[pos];
if (c >= 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("</");
if (namespace != null) {
append(namespace);
append(':');
}
append(name);
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("<?xml version='1.0' encoding='utf-8' standalone='"
+ (standalone ? "yes" : "no") + "' ?>\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;
}
}