Move binary XML into modules-utils.
Bug: 243194720 Test: presubmit Change-Id: I5252cf235cedc85aadebbecc078e753b7b54dcdd
This commit is contained in:
@@ -384,6 +384,7 @@ java_defaults {
|
||||
"av-types-aidl-java",
|
||||
"tv_tuner_resource_manager_aidl_interface-java",
|
||||
"soundtrigger_middleware-aidl-java",
|
||||
"modules-utils-binary-xml",
|
||||
"modules-utils-build",
|
||||
"modules-utils-preconditions",
|
||||
"modules-utils-statemachine",
|
||||
|
||||
@@ -22,6 +22,9 @@ import android.perftests.utils.PerfStatusReporter;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.modules.utils.FastDataInput;
|
||||
import com.android.modules.utils.FastDataOutput;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -18,6 +18,8 @@ package android.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import com.android.modules.utils.ModifiedUtf8;
|
||||
|
||||
import dalvik.annotation.optimization.FastNative;
|
||||
|
||||
/**
|
||||
@@ -30,8 +32,7 @@ import dalvik.annotation.optimization.FastNative;
|
||||
* Callers are cautioned that there is a long-standing ART bug that emits
|
||||
* non-standard 4-byte sequences, as described by {@code kUtfUse4ByteSequence}
|
||||
* in {@code art/runtime/jni/jni_internal.cc}. If precise modified UTF-8
|
||||
* encoding is required, use {@link com.android.internal.util.ModifiedUtf8}
|
||||
* instead.
|
||||
* encoding is required, use {@link ModifiedUtf8} instead.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@@ -43,8 +44,8 @@ public class CharsetUtils {
|
||||
* Callers are cautioned that there is a long-standing ART bug that emits
|
||||
* non-standard 4-byte sequences, as described by
|
||||
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
|
||||
* If precise modified UTF-8 encoding is required, use
|
||||
* {@link com.android.internal.util.ModifiedUtf8} instead.
|
||||
* If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
|
||||
* instead.
|
||||
*
|
||||
* @param src string value to be encoded
|
||||
* @param dest destination byte array to encode into
|
||||
@@ -66,8 +67,8 @@ public class CharsetUtils {
|
||||
* Callers are cautioned that there is a long-standing ART bug that emits
|
||||
* non-standard 4-byte sequences, as described by
|
||||
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
|
||||
* If precise modified UTF-8 encoding is required, use
|
||||
* {@link com.android.internal.util.ModifiedUtf8} instead.
|
||||
* If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
|
||||
* instead.
|
||||
*
|
||||
* @param src string value to be encoded
|
||||
* @param srcLen exact length of string to be encoded
|
||||
@@ -88,8 +89,8 @@ public class CharsetUtils {
|
||||
* Callers are cautioned that there is a long-standing ART bug that emits
|
||||
* non-standard 4-byte sequences, as described by
|
||||
* {@code kUtfUse4ByteSequence} in {@code art/runtime/jni/jni_internal.cc}.
|
||||
* If precise modified UTF-8 encoding is required, use
|
||||
* {@link com.android.internal.util.ModifiedUtf8} instead.
|
||||
* If precise modified UTF-8 encoding is required, use {@link ModifiedUtf8}
|
||||
* instead.
|
||||
*
|
||||
* @param src source byte array to decode from
|
||||
* @param srcOff offset into source where decoding should begin
|
||||
|
||||
@@ -24,9 +24,9 @@ import android.system.Os;
|
||||
|
||||
import com.android.internal.util.ArtBinaryXmlPullParser;
|
||||
import com.android.internal.util.ArtBinaryXmlSerializer;
|
||||
import com.android.internal.util.BinaryXmlSerializer;
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
import com.android.modules.utils.BinaryXmlSerializer;
|
||||
import com.android.modules.utils.TypedXmlPullParser;
|
||||
import com.android.modules.utils.TypedXmlSerializer;
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ package com.android.internal.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import com.android.modules.utils.BinaryXmlPullParser;
|
||||
import com.android.modules.utils.FastDataInput;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.InputStream;
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ package com.android.internal.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import com.android.modules.utils.BinaryXmlSerializer;
|
||||
import com.android.modules.utils.FastDataOutput;
|
||||
|
||||
import java.io.DataOutput;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ package com.android.internal.util;
|
||||
import android.annotation.NonNull;
|
||||
import android.util.CharsetUtils;
|
||||
|
||||
import com.android.modules.utils.FastDataInput;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@@ -19,6 +19,8 @@ package com.android.internal.util;
|
||||
import android.annotation.NonNull;
|
||||
import android.util.CharsetUtils;
|
||||
|
||||
import com.android.modules.utils.FastDataOutput;
|
||||
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@@ -1,945 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 static com.android.internal.util.BinaryXmlSerializer.ATTRIBUTE;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.PROTOCOL_MAGIC_VERSION_0;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_FALSE;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_BOOLEAN_TRUE;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_BASE64;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_BYTES_HEX;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_DOUBLE;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_FLOAT;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_INT_HEX;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_LONG_HEX;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_NULL;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING;
|
||||
import static com.android.internal.util.BinaryXmlSerializer.TYPE_STRING_INTERNED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.android.modules.utils.TypedXmlPullParser;
|
||||
import com.android.modules.utils.TypedXmlSerializer;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Parser that reads XML documents using a custom binary wire protocol which
|
||||
* benchmarking has shown to be 8.5x faster than {@link Xml.newFastPullParser()}
|
||||
* for a typical {@code packages.xml}.
|
||||
* <p>
|
||||
* The high-level design of the wire protocol is to directly serialize the event
|
||||
* stream, while efficiently and compactly writing strongly-typed primitives
|
||||
* delivered through the {@link TypedXmlSerializer} interface.
|
||||
* <p>
|
||||
* Each serialized event is a single byte where the lower half is a normal
|
||||
* {@link XmlPullParser} token and the upper half is an optional data type
|
||||
* signal, such as {@link #TYPE_INT}.
|
||||
* <p>
|
||||
* This parser has some specific limitations:
|
||||
* <ul>
|
||||
* <li>Only the UTF-8 encoding is supported.
|
||||
* <li>Variable length values, such as {@code byte[]} or {@link String}, are
|
||||
* limited to 65,535 bytes in length. Note that {@link String} values are stored
|
||||
* as UTF-8 on the wire.
|
||||
* <li>Namespaces, prefixes, properties, and options are unsupported.
|
||||
* </ul>
|
||||
*/
|
||||
public class BinaryXmlPullParser implements TypedXmlPullParser {
|
||||
private FastDataInput mIn;
|
||||
|
||||
private int mCurrentToken = START_DOCUMENT;
|
||||
private int mCurrentDepth = 0;
|
||||
private String mCurrentName;
|
||||
private String mCurrentText;
|
||||
|
||||
/**
|
||||
* Pool of attributes parsed for the currently tag. All interactions should
|
||||
* be done via {@link #obtainAttribute()}, {@link #findAttribute(String)},
|
||||
* and {@link #resetAttributes()}.
|
||||
*/
|
||||
private int mAttributeCount = 0;
|
||||
private Attribute[] mAttributes;
|
||||
|
||||
@Override
|
||||
public void setInput(InputStream is, String encoding) throws XmlPullParserException {
|
||||
if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
if (mIn != null) {
|
||||
mIn.release();
|
||||
mIn = null;
|
||||
}
|
||||
|
||||
mIn = obtainFastDataInput(is);
|
||||
|
||||
mCurrentToken = START_DOCUMENT;
|
||||
mCurrentDepth = 0;
|
||||
mCurrentName = null;
|
||||
mCurrentText = null;
|
||||
|
||||
mAttributeCount = 0;
|
||||
mAttributes = new Attribute[8];
|
||||
for (int i = 0; i < mAttributes.length; i++) {
|
||||
mAttributes[i] = new Attribute();
|
||||
}
|
||||
|
||||
try {
|
||||
final byte[] magic = new byte[4];
|
||||
mIn.readFully(magic);
|
||||
if (!Arrays.equals(magic, PROTOCOL_MAGIC_VERSION_0)) {
|
||||
throw new IOException("Unexpected magic " + bytesToHexString(magic));
|
||||
}
|
||||
|
||||
// We're willing to immediately consume a START_DOCUMENT if present,
|
||||
// but we're okay if it's missing
|
||||
if (peekNextExternalToken() == START_DOCUMENT) {
|
||||
consumeToken();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new XmlPullParserException(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected FastDataInput obtainFastDataInput(@NonNull InputStream is) {
|
||||
return FastDataInput.obtain(is);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(Reader in) throws XmlPullParserException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int next() throws XmlPullParserException, IOException {
|
||||
while (true) {
|
||||
final int token = nextToken();
|
||||
switch (token) {
|
||||
case START_TAG:
|
||||
case END_TAG:
|
||||
case END_DOCUMENT:
|
||||
return token;
|
||||
case TEXT:
|
||||
consumeAdditionalText();
|
||||
// Per interface docs, empty text regions are skipped
|
||||
if (mCurrentText == null || mCurrentText.length() == 0) {
|
||||
continue;
|
||||
} else {
|
||||
return TEXT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextToken() throws XmlPullParserException, IOException {
|
||||
if (mCurrentToken == XmlPullParser.END_TAG) {
|
||||
mCurrentDepth--;
|
||||
}
|
||||
|
||||
int token;
|
||||
try {
|
||||
token = peekNextExternalToken();
|
||||
consumeToken();
|
||||
} catch (EOFException e) {
|
||||
token = END_DOCUMENT;
|
||||
}
|
||||
switch (token) {
|
||||
case XmlPullParser.START_TAG:
|
||||
// We need to peek forward to find the next external token so
|
||||
// that we parse all pending INTERNAL_ATTRIBUTE tokens
|
||||
peekNextExternalToken();
|
||||
mCurrentDepth++;
|
||||
break;
|
||||
}
|
||||
mCurrentToken = token;
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek at the next "external" token without consuming it.
|
||||
* <p>
|
||||
* External tokens, such as {@link #START_TAG}, are expected by typical
|
||||
* {@link XmlPullParser} clients. In contrast, internal tokens, such as
|
||||
* {@link #ATTRIBUTE}, are not expected by typical clients.
|
||||
* <p>
|
||||
* This method consumes any internal events until it reaches the next
|
||||
* external event.
|
||||
*/
|
||||
private int peekNextExternalToken() throws IOException, XmlPullParserException {
|
||||
while (true) {
|
||||
final int token = peekNextToken();
|
||||
switch (token) {
|
||||
case ATTRIBUTE:
|
||||
consumeToken();
|
||||
continue;
|
||||
default:
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek at the next token in the underlying stream without consuming it.
|
||||
*/
|
||||
private int peekNextToken() throws IOException {
|
||||
return mIn.peekByte() & 0x0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and consume the next token in the underlying stream.
|
||||
*/
|
||||
private void consumeToken() throws IOException, XmlPullParserException {
|
||||
final int event = mIn.readByte();
|
||||
final int token = event & 0x0f;
|
||||
final int type = event & 0xf0;
|
||||
switch (token) {
|
||||
case ATTRIBUTE: {
|
||||
final Attribute attr = obtainAttribute();
|
||||
attr.name = mIn.readInternedUTF();
|
||||
attr.type = type;
|
||||
switch (type) {
|
||||
case TYPE_NULL:
|
||||
case TYPE_BOOLEAN_TRUE:
|
||||
case TYPE_BOOLEAN_FALSE:
|
||||
// Nothing extra to fill in
|
||||
break;
|
||||
case TYPE_STRING:
|
||||
attr.valueString = mIn.readUTF();
|
||||
break;
|
||||
case TYPE_STRING_INTERNED:
|
||||
attr.valueString = mIn.readInternedUTF();
|
||||
break;
|
||||
case TYPE_BYTES_HEX:
|
||||
case TYPE_BYTES_BASE64:
|
||||
final int len = mIn.readUnsignedShort();
|
||||
final byte[] res = new byte[len];
|
||||
mIn.readFully(res);
|
||||
attr.valueBytes = res;
|
||||
break;
|
||||
case TYPE_INT:
|
||||
case TYPE_INT_HEX:
|
||||
attr.valueInt = mIn.readInt();
|
||||
break;
|
||||
case TYPE_LONG:
|
||||
case TYPE_LONG_HEX:
|
||||
attr.valueLong = mIn.readLong();
|
||||
break;
|
||||
case TYPE_FLOAT:
|
||||
attr.valueFloat = mIn.readFloat();
|
||||
break;
|
||||
case TYPE_DOUBLE:
|
||||
attr.valueDouble = mIn.readDouble();
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unexpected data type " + type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case XmlPullParser.START_DOCUMENT: {
|
||||
mCurrentName = null;
|
||||
mCurrentText = null;
|
||||
if (mAttributeCount > 0) resetAttributes();
|
||||
break;
|
||||
}
|
||||
case XmlPullParser.END_DOCUMENT: {
|
||||
mCurrentName = null;
|
||||
mCurrentText = null;
|
||||
if (mAttributeCount > 0) resetAttributes();
|
||||
break;
|
||||
}
|
||||
case XmlPullParser.START_TAG: {
|
||||
mCurrentName = mIn.readInternedUTF();
|
||||
mCurrentText = null;
|
||||
if (mAttributeCount > 0) resetAttributes();
|
||||
break;
|
||||
}
|
||||
case XmlPullParser.END_TAG: {
|
||||
mCurrentName = mIn.readInternedUTF();
|
||||
mCurrentText = null;
|
||||
if (mAttributeCount > 0) resetAttributes();
|
||||
break;
|
||||
}
|
||||
case XmlPullParser.TEXT:
|
||||
case XmlPullParser.CDSECT:
|
||||
case XmlPullParser.PROCESSING_INSTRUCTION:
|
||||
case XmlPullParser.COMMENT:
|
||||
case XmlPullParser.DOCDECL:
|
||||
case XmlPullParser.IGNORABLE_WHITESPACE: {
|
||||
mCurrentName = null;
|
||||
mCurrentText = mIn.readUTF();
|
||||
if (mAttributeCount > 0) resetAttributes();
|
||||
break;
|
||||
}
|
||||
case XmlPullParser.ENTITY_REF: {
|
||||
mCurrentName = mIn.readUTF();
|
||||
mCurrentText = resolveEntity(mCurrentName);
|
||||
if (mAttributeCount > 0) resetAttributes();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IOException("Unknown token " + token + " with type " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When the current tag is {@link #TEXT}, consume all subsequent "text"
|
||||
* events, as described by {@link #next}. When finished, the current event
|
||||
* will still be {@link #TEXT}.
|
||||
*/
|
||||
private void consumeAdditionalText() throws IOException, XmlPullParserException {
|
||||
String combinedText = mCurrentText;
|
||||
while (true) {
|
||||
final int token = peekNextExternalToken();
|
||||
switch (token) {
|
||||
case COMMENT:
|
||||
case PROCESSING_INSTRUCTION:
|
||||
// Quietly consumed
|
||||
consumeToken();
|
||||
break;
|
||||
case TEXT:
|
||||
case CDSECT:
|
||||
case ENTITY_REF:
|
||||
// Additional text regions collected
|
||||
consumeToken();
|
||||
combinedText += mCurrentText;
|
||||
break;
|
||||
default:
|
||||
// Next token is something non-text, so wrap things up
|
||||
mCurrentToken = TEXT;
|
||||
mCurrentName = null;
|
||||
mCurrentText = combinedText;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static @NonNull String resolveEntity(@NonNull String entity)
|
||||
throws XmlPullParserException {
|
||||
switch (entity) {
|
||||
case "lt": return "<";
|
||||
case "gt": return ">";
|
||||
case "amp": return "&";
|
||||
case "apos": return "'";
|
||||
case "quot": return "\"";
|
||||
}
|
||||
if (entity.length() > 1 && entity.charAt(0) == '#') {
|
||||
final char c = (char) Integer.parseInt(entity.substring(1));
|
||||
return new String(new char[] { c });
|
||||
}
|
||||
throw new XmlPullParserException("Unknown entity " + entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void require(int type, String namespace, String name)
|
||||
throws XmlPullParserException, IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
if (mCurrentToken != type || !Objects.equals(mCurrentName, name)) {
|
||||
throw new XmlPullParserException(getPositionDescription());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nextText() throws XmlPullParserException, IOException {
|
||||
if (getEventType() != START_TAG) {
|
||||
throw new XmlPullParserException(getPositionDescription());
|
||||
}
|
||||
int eventType = next();
|
||||
if (eventType == TEXT) {
|
||||
String result = getText();
|
||||
eventType = next();
|
||||
if (eventType != END_TAG) {
|
||||
throw new XmlPullParserException(getPositionDescription());
|
||||
}
|
||||
return result;
|
||||
} else if (eventType == END_TAG) {
|
||||
return "";
|
||||
} else {
|
||||
throw new XmlPullParserException(getPositionDescription());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextTag() throws XmlPullParserException, IOException {
|
||||
int eventType = next();
|
||||
if (eventType == TEXT && isWhitespace()) {
|
||||
eventType = next();
|
||||
}
|
||||
if (eventType != START_TAG && eventType != END_TAG) {
|
||||
throw new XmlPullParserException(getPositionDescription());
|
||||
}
|
||||
return eventType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate and return a new {@link Attribute} associated with the tag being
|
||||
* currently processed. This will automatically grow the internal pool as
|
||||
* needed.
|
||||
*/
|
||||
private @NonNull Attribute obtainAttribute() {
|
||||
if (mAttributeCount == mAttributes.length) {
|
||||
final int before = mAttributes.length;
|
||||
final int after = before + (before >> 1);
|
||||
mAttributes = Arrays.copyOf(mAttributes, after);
|
||||
for (int i = before; i < after; i++) {
|
||||
mAttributes[i] = new Attribute();
|
||||
}
|
||||
}
|
||||
return mAttributes[mAttributeCount++];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any {@link Attribute} instances that have been allocated by
|
||||
* {@link #obtainAttribute()}, returning them into the pool for recycling.
|
||||
*/
|
||||
private void resetAttributes() {
|
||||
for (int i = 0; i < mAttributeCount; i++) {
|
||||
mAttributes[i].reset();
|
||||
}
|
||||
mAttributeCount = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeIndex(String namespace, String name) {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
for (int i = 0; i < mAttributeCount; i++) {
|
||||
if (Objects.equals(mAttributes[i].name, name)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeValue(String namespace, String name) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index != -1) {
|
||||
return mAttributes[index].getValueString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeValue(int index) {
|
||||
return mAttributes[index].getValueString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAttributeBytesHex(int index) throws XmlPullParserException {
|
||||
return mAttributes[index].getValueBytesHex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getAttributeBytesBase64(int index) throws XmlPullParserException {
|
||||
return mAttributes[index].getValueBytesBase64();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeInt(int index) throws XmlPullParserException {
|
||||
return mAttributes[index].getValueInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeIntHex(int index) throws XmlPullParserException {
|
||||
return mAttributes[index].getValueIntHex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAttributeLong(int index) throws XmlPullParserException {
|
||||
return mAttributes[index].getValueLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getAttributeLongHex(int index) throws XmlPullParserException {
|
||||
return mAttributes[index].getValueLongHex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAttributeFloat(int index) throws XmlPullParserException {
|
||||
return mAttributes[index].getValueFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAttributeDouble(int index) throws XmlPullParserException {
|
||||
return mAttributes[index].getValueDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAttributeBoolean(int index) throws XmlPullParserException {
|
||||
return mAttributes[index].getValueBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return mCurrentText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char[] getTextCharacters(int[] holderForStartAndLength) {
|
||||
final char[] chars = mCurrentText.toCharArray();
|
||||
holderForStartAndLength[0] = 0;
|
||||
holderForStartAndLength[1] = chars.length;
|
||||
return chars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInputEncoding() {
|
||||
return StandardCharsets.UTF_8.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDepth() {
|
||||
return mCurrentDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPositionDescription() {
|
||||
// Not very helpful, but it's the best information we have
|
||||
return "Token " + mCurrentToken + " at depth " + mCurrentDepth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineNumber() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnNumber() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWhitespace() throws XmlPullParserException {
|
||||
switch (mCurrentToken) {
|
||||
case IGNORABLE_WHITESPACE:
|
||||
return true;
|
||||
case TEXT:
|
||||
case CDSECT:
|
||||
return !TextUtils.isGraphic(mCurrentText);
|
||||
default:
|
||||
throw new XmlPullParserException("Not applicable for token " + mCurrentToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
switch (mCurrentToken) {
|
||||
case START_TAG:
|
||||
case END_TAG:
|
||||
// Namespaces are unsupported
|
||||
return NO_NAMESPACE;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return mCurrentName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix() {
|
||||
// Prefixes are not supported
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmptyElementTag() throws XmlPullParserException {
|
||||
switch (mCurrentToken) {
|
||||
case START_TAG:
|
||||
try {
|
||||
return (peekNextExternalToken() == END_TAG);
|
||||
} catch (IOException e) {
|
||||
throw new XmlPullParserException(e.toString());
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Not at START_TAG");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttributeCount() {
|
||||
return mAttributeCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeNamespace(int index) {
|
||||
// Namespaces are unsupported
|
||||
return NO_NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeName(int index) {
|
||||
return mAttributes[index].name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributePrefix(int index) {
|
||||
// Prefixes are not supported
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeType(int index) {
|
||||
// Validation is not supported
|
||||
return "CDATA";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAttributeDefault(int index) {
|
||||
// Validation is not supported
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEventType() throws XmlPullParserException {
|
||||
return mCurrentToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNamespaceCount(int depth) throws XmlPullParserException {
|
||||
// Namespaces are unsupported
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespacePrefix(int pos) throws XmlPullParserException {
|
||||
// Namespaces are unsupported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespaceUri(int pos) throws XmlPullParserException {
|
||||
// Namespaces are unsupported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace(String prefix) {
|
||||
// Namespaces are unsupported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defineEntityReplacementText(String entityName, String replacementText)
|
||||
throws XmlPullParserException {
|
||||
// Custom entities are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeature(String name, boolean state) throws XmlPullParserException {
|
||||
// Features are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeature(String name) {
|
||||
// Features are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(String name, Object value) throws XmlPullParserException {
|
||||
// Properties are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
// Properties are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private static IllegalArgumentException illegalNamespace() {
|
||||
throw new IllegalArgumentException("Namespaces are not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Holder representing a single attribute. This design enables object
|
||||
* recycling without resorting to autoboxing.
|
||||
* <p>
|
||||
* To support conversion between human-readable XML and binary XML, the
|
||||
* various accessor methods will transparently convert from/to
|
||||
* human-readable values when needed.
|
||||
*/
|
||||
private static class Attribute {
|
||||
public String name;
|
||||
public int type;
|
||||
|
||||
public String valueString;
|
||||
public byte[] valueBytes;
|
||||
public int valueInt;
|
||||
public long valueLong;
|
||||
public float valueFloat;
|
||||
public double valueDouble;
|
||||
|
||||
public void reset() {
|
||||
name = null;
|
||||
valueString = null;
|
||||
valueBytes = null;
|
||||
}
|
||||
|
||||
public @Nullable String getValueString() {
|
||||
switch (type) {
|
||||
case TYPE_NULL:
|
||||
return null;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
return valueString;
|
||||
case TYPE_BYTES_HEX:
|
||||
return bytesToHexString(valueBytes);
|
||||
case TYPE_BYTES_BASE64:
|
||||
return Base64.encodeToString(valueBytes, Base64.NO_WRAP);
|
||||
case TYPE_INT:
|
||||
return Integer.toString(valueInt);
|
||||
case TYPE_INT_HEX:
|
||||
return Integer.toString(valueInt, 16);
|
||||
case TYPE_LONG:
|
||||
return Long.toString(valueLong);
|
||||
case TYPE_LONG_HEX:
|
||||
return Long.toString(valueLong, 16);
|
||||
case TYPE_FLOAT:
|
||||
return Float.toString(valueFloat);
|
||||
case TYPE_DOUBLE:
|
||||
return Double.toString(valueDouble);
|
||||
case TYPE_BOOLEAN_TRUE:
|
||||
return "true";
|
||||
case TYPE_BOOLEAN_FALSE:
|
||||
return "false";
|
||||
default:
|
||||
// Unknown data type; null is the best we can offer
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable byte[] getValueBytesHex() throws XmlPullParserException {
|
||||
switch (type) {
|
||||
case TYPE_NULL:
|
||||
return null;
|
||||
case TYPE_BYTES_HEX:
|
||||
case TYPE_BYTES_BASE64:
|
||||
return valueBytes;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
try {
|
||||
return hexStringToBytes(valueString);
|
||||
} catch (Exception e) {
|
||||
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Invalid conversion from " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable byte[] getValueBytesBase64() throws XmlPullParserException {
|
||||
switch (type) {
|
||||
case TYPE_NULL:
|
||||
return null;
|
||||
case TYPE_BYTES_HEX:
|
||||
case TYPE_BYTES_BASE64:
|
||||
return valueBytes;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
try {
|
||||
return Base64.decode(valueString, Base64.NO_WRAP);
|
||||
} catch (Exception e) {
|
||||
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Invalid conversion from " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public int getValueInt() throws XmlPullParserException {
|
||||
switch (type) {
|
||||
case TYPE_INT:
|
||||
case TYPE_INT_HEX:
|
||||
return valueInt;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
try {
|
||||
return Integer.parseInt(valueString);
|
||||
} catch (Exception e) {
|
||||
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Invalid conversion from " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public int getValueIntHex() throws XmlPullParserException {
|
||||
switch (type) {
|
||||
case TYPE_INT:
|
||||
case TYPE_INT_HEX:
|
||||
return valueInt;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
try {
|
||||
return Integer.parseInt(valueString, 16);
|
||||
} catch (Exception e) {
|
||||
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Invalid conversion from " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public long getValueLong() throws XmlPullParserException {
|
||||
switch (type) {
|
||||
case TYPE_LONG:
|
||||
case TYPE_LONG_HEX:
|
||||
return valueLong;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
try {
|
||||
return Long.parseLong(valueString);
|
||||
} catch (Exception e) {
|
||||
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Invalid conversion from " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public long getValueLongHex() throws XmlPullParserException {
|
||||
switch (type) {
|
||||
case TYPE_LONG:
|
||||
case TYPE_LONG_HEX:
|
||||
return valueLong;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
try {
|
||||
return Long.parseLong(valueString, 16);
|
||||
} catch (Exception e) {
|
||||
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Invalid conversion from " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public float getValueFloat() throws XmlPullParserException {
|
||||
switch (type) {
|
||||
case TYPE_FLOAT:
|
||||
return valueFloat;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
try {
|
||||
return Float.parseFloat(valueString);
|
||||
} catch (Exception e) {
|
||||
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Invalid conversion from " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public double getValueDouble() throws XmlPullParserException {
|
||||
switch (type) {
|
||||
case TYPE_DOUBLE:
|
||||
return valueDouble;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
try {
|
||||
return Double.parseDouble(valueString);
|
||||
} catch (Exception e) {
|
||||
throw new XmlPullParserException("Invalid attribute " + name + ": " + e);
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Invalid conversion from " + type);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getValueBoolean() throws XmlPullParserException {
|
||||
switch (type) {
|
||||
case TYPE_BOOLEAN_TRUE:
|
||||
return true;
|
||||
case TYPE_BOOLEAN_FALSE:
|
||||
return false;
|
||||
case TYPE_STRING:
|
||||
case TYPE_STRING_INTERNED:
|
||||
if ("true".equalsIgnoreCase(valueString)) {
|
||||
return true;
|
||||
} else if ("false".equalsIgnoreCase(valueString)) {
|
||||
return false;
|
||||
} else {
|
||||
throw new XmlPullParserException(
|
||||
"Invalid attribute " + name + ": " + valueString);
|
||||
}
|
||||
default:
|
||||
throw new XmlPullParserException("Invalid conversion from " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: To support unbundled clients, we include an inlined copy
|
||||
// of hex conversion logic from HexDump below
|
||||
private final static char[] HEX_DIGITS =
|
||||
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
private static int toByte(char c) {
|
||||
if (c >= '0' && c <= '9') return (c - '0');
|
||||
if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
|
||||
if (c >= 'a' && c <= 'f') return (c - 'a' + 10);
|
||||
throw new IllegalArgumentException("Invalid hex char '" + c + "'");
|
||||
}
|
||||
|
||||
static String bytesToHexString(byte[] value) {
|
||||
final int length = value.length;
|
||||
final char[] buf = new char[length * 2];
|
||||
int bufIndex = 0;
|
||||
for (int i = 0; i < length; i++) {
|
||||
byte b = value[i];
|
||||
buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
|
||||
buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
|
||||
}
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
static byte[] hexStringToBytes(String value) {
|
||||
final int length = value.length();
|
||||
if (length % 2 != 0) {
|
||||
throw new IllegalArgumentException("Invalid hex length " + length);
|
||||
}
|
||||
byte[] buffer = new byte[length / 2];
|
||||
for (int i = 0; i < length; i += 2) {
|
||||
buffer[i / 2] = (byte) ((toByte(value.charAt(i)) << 4)
|
||||
| toByte(value.charAt(i + 1)));
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
@@ -1,404 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 static org.xmlpull.v1.XmlPullParser.CDSECT;
|
||||
import static org.xmlpull.v1.XmlPullParser.COMMENT;
|
||||
import static org.xmlpull.v1.XmlPullParser.DOCDECL;
|
||||
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
|
||||
import static org.xmlpull.v1.XmlPullParser.END_TAG;
|
||||
import static org.xmlpull.v1.XmlPullParser.ENTITY_REF;
|
||||
import static org.xmlpull.v1.XmlPullParser.IGNORABLE_WHITESPACE;
|
||||
import static org.xmlpull.v1.XmlPullParser.PROCESSING_INSTRUCTION;
|
||||
import static org.xmlpull.v1.XmlPullParser.START_DOCUMENT;
|
||||
import static org.xmlpull.v1.XmlPullParser.START_TAG;
|
||||
import static org.xmlpull.v1.XmlPullParser.TEXT;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import com.android.modules.utils.TypedXmlSerializer;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Serializer that writes XML documents using a custom binary wire protocol
|
||||
* which benchmarking has shown to be 4.3x faster and use 2.4x less disk space
|
||||
* than {@code Xml.newFastSerializer()} for a typical {@code packages.xml}.
|
||||
* <p>
|
||||
* The high-level design of the wire protocol is to directly serialize the event
|
||||
* stream, while efficiently and compactly writing strongly-typed primitives
|
||||
* delivered through the {@link TypedXmlSerializer} interface.
|
||||
* <p>
|
||||
* Each serialized event is a single byte where the lower half is a normal
|
||||
* {@link XmlPullParser} token and the upper half is an optional data type
|
||||
* signal, such as {@link #TYPE_INT}.
|
||||
* <p>
|
||||
* This serializer has some specific limitations:
|
||||
* <ul>
|
||||
* <li>Only the UTF-8 encoding is supported.
|
||||
* <li>Variable length values, such as {@code byte[]} or {@link String}, are
|
||||
* limited to 65,535 bytes in length. Note that {@link String} values are stored
|
||||
* as UTF-8 on the wire.
|
||||
* <li>Namespaces, prefixes, properties, and options are unsupported.
|
||||
* </ul>
|
||||
*/
|
||||
public class BinaryXmlSerializer implements TypedXmlSerializer {
|
||||
/**
|
||||
* The wire protocol always begins with a well-known magic value of
|
||||
* {@code ABX_}, representing "Android Binary XML." The final byte is a
|
||||
* version number which may be incremented as the protocol changes.
|
||||
*/
|
||||
public static final byte[] PROTOCOL_MAGIC_VERSION_0 = new byte[] { 0x41, 0x42, 0x58, 0x00 };
|
||||
|
||||
/**
|
||||
* Internal token which represents an attribute associated with the most
|
||||
* recent {@link #START_TAG} token.
|
||||
*/
|
||||
static final int ATTRIBUTE = 15;
|
||||
|
||||
static final int TYPE_NULL = 1 << 4;
|
||||
static final int TYPE_STRING = 2 << 4;
|
||||
static final int TYPE_STRING_INTERNED = 3 << 4;
|
||||
static final int TYPE_BYTES_HEX = 4 << 4;
|
||||
static final int TYPE_BYTES_BASE64 = 5 << 4;
|
||||
static final int TYPE_INT = 6 << 4;
|
||||
static final int TYPE_INT_HEX = 7 << 4;
|
||||
static final int TYPE_LONG = 8 << 4;
|
||||
static final int TYPE_LONG_HEX = 9 << 4;
|
||||
static final int TYPE_FLOAT = 10 << 4;
|
||||
static final int TYPE_DOUBLE = 11 << 4;
|
||||
static final int TYPE_BOOLEAN_TRUE = 12 << 4;
|
||||
static final int TYPE_BOOLEAN_FALSE = 13 << 4;
|
||||
|
||||
private FastDataOutput mOut;
|
||||
|
||||
/**
|
||||
* Stack of tags which are currently active via {@link #startTag} and which
|
||||
* haven't been terminated via {@link #endTag}.
|
||||
*/
|
||||
private int mTagCount = 0;
|
||||
private String[] mTagNames;
|
||||
|
||||
/**
|
||||
* Write the given token and optional {@link String} into our buffer.
|
||||
*/
|
||||
private void writeToken(int token, @Nullable String text) throws IOException {
|
||||
if (text != null) {
|
||||
mOut.writeByte(token | TYPE_STRING);
|
||||
mOut.writeUTF(text);
|
||||
} else {
|
||||
mOut.writeByte(token | TYPE_NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutput(@NonNull OutputStream os, @Nullable String encoding) throws IOException {
|
||||
if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
mOut = obtainFastDataOutput(os);
|
||||
mOut.write(PROTOCOL_MAGIC_VERSION_0);
|
||||
|
||||
mTagCount = 0;
|
||||
mTagNames = new String[8];
|
||||
}
|
||||
|
||||
@NonNull
|
||||
protected FastDataOutput obtainFastDataOutput(@NonNull OutputStream os) {
|
||||
return FastDataOutput.obtain(os);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutput(Writer writer) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if (mOut != null) {
|
||||
mOut.flush();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startDocument(@Nullable String encoding, @Nullable Boolean standalone)
|
||||
throws IOException {
|
||||
if (encoding != null && !StandardCharsets.UTF_8.name().equalsIgnoreCase(encoding)) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
if (standalone != null && !standalone) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
mOut.writeByte(START_DOCUMENT | TYPE_NULL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endDocument() throws IOException {
|
||||
mOut.writeByte(END_DOCUMENT | TYPE_NULL);
|
||||
flush();
|
||||
|
||||
mOut.release();
|
||||
mOut = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDepth() {
|
||||
return mTagCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
// Namespaces are unsupported
|
||||
return XmlPullParser.NO_NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return mTagNames[mTagCount - 1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer startTag(String namespace, String name) throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
if (mTagCount == mTagNames.length) {
|
||||
mTagNames = Arrays.copyOf(mTagNames, mTagCount + (mTagCount >> 1));
|
||||
}
|
||||
mTagNames[mTagCount++] = name;
|
||||
mOut.writeByte(START_TAG | TYPE_STRING_INTERNED);
|
||||
mOut.writeInternedUTF(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer endTag(String namespace, String name) throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mTagCount--;
|
||||
mOut.writeByte(END_TAG | TYPE_STRING_INTERNED);
|
||||
mOut.writeInternedUTF(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attribute(String namespace, String name, String value) throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_STRING);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeUTF(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeInterned(String namespace, String name, String value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_STRING_INTERNED);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeInternedUTF(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeBytesHex(String namespace, String name, byte[] value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_BYTES_HEX);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeShort(value.length);
|
||||
mOut.write(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeBytesBase64(String namespace, String name, byte[] value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_BYTES_BASE64);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeShort(value.length);
|
||||
mOut.write(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeInt(String namespace, String name, int value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_INT);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeInt(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeIntHex(String namespace, String name, int value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_INT_HEX);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeInt(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeLong(String namespace, String name, long value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_LONG);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeLong(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeLongHex(String namespace, String name, long value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_LONG_HEX);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeLong(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeFloat(String namespace, String name, float value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_FLOAT);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeFloat(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeDouble(String namespace, String name, double value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_DOUBLE);
|
||||
mOut.writeInternedUTF(name);
|
||||
mOut.writeDouble(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer attributeBoolean(String namespace, String name, boolean value)
|
||||
throws IOException {
|
||||
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
|
||||
if (value) {
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_BOOLEAN_TRUE);
|
||||
mOut.writeInternedUTF(name);
|
||||
} else {
|
||||
mOut.writeByte(ATTRIBUTE | TYPE_BOOLEAN_FALSE);
|
||||
mOut.writeInternedUTF(name);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer text(char[] buf, int start, int len) throws IOException {
|
||||
writeToken(TEXT, new String(buf, start, len));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlSerializer text(String text) throws IOException {
|
||||
writeToken(TEXT, text);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cdsect(String text) throws IOException {
|
||||
writeToken(CDSECT, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void entityRef(String text) throws IOException {
|
||||
writeToken(ENTITY_REF, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processingInstruction(String text) throws IOException {
|
||||
writeToken(PROCESSING_INSTRUCTION, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void comment(String text) throws IOException {
|
||||
writeToken(COMMENT, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void docdecl(String text) throws IOException {
|
||||
writeToken(DOCDECL, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ignorableWhitespace(String text) throws IOException {
|
||||
writeToken(IGNORABLE_WHITESPACE, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeature(String name, boolean state) {
|
||||
// Quietly handle no-op features
|
||||
if ("http://xmlpull.org/v1/doc/features.html#indent-output".equals(name)) {
|
||||
return;
|
||||
}
|
||||
// Features are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getFeature(String name) {
|
||||
// Features are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperty(String name, Object value) {
|
||||
// Properties are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProperty(String name) {
|
||||
// Properties are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrefix(String prefix, String namespace) {
|
||||
// Prefixes are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix(String namespace, boolean generatePrefix) {
|
||||
// Prefixes are not supported
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private static IllegalArgumentException illegalNamespace() {
|
||||
throw new IllegalArgumentException("Namespaces are not supported");
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.annotation.NonNull;
|
||||
|
||||
import dalvik.system.VMRuntime;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Optimized implementation of {@link DataInput} which buffers data in memory
|
||||
* from the underlying {@link InputStream}.
|
||||
* <p>
|
||||
* Benchmarks have demonstrated this class is 3x more efficient than using a
|
||||
* {@link DataInputStream} with a {@link BufferedInputStream}.
|
||||
*/
|
||||
public class FastDataInput implements DataInput, Closeable {
|
||||
protected static final int MAX_UNSIGNED_SHORT = 65_535;
|
||||
|
||||
protected static final int DEFAULT_BUFFER_SIZE = 32_768;
|
||||
|
||||
protected final VMRuntime mRuntime;
|
||||
|
||||
protected final byte[] mBuffer;
|
||||
protected final int mBufferCap;
|
||||
|
||||
private InputStream mIn;
|
||||
protected int mBufferPos;
|
||||
protected int mBufferLim;
|
||||
|
||||
/**
|
||||
* Values that have been "interned" by {@link #readInternedUTF()}.
|
||||
*/
|
||||
private int mStringRefCount = 0;
|
||||
private String[] mStringRefs = new String[32];
|
||||
|
||||
public FastDataInput(@NonNull InputStream in, int bufferSize) {
|
||||
mRuntime = VMRuntime.getRuntime();
|
||||
mIn = Objects.requireNonNull(in);
|
||||
if (bufferSize < 8) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
|
||||
mBufferCap = mBuffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link FastDataInput} configured with the given
|
||||
* {@link InputStream} and which encodes large code-points using 3-byte
|
||||
* sequences.
|
||||
* <p>
|
||||
* This <em>is</em> compatible with the {@link DataInput} API contract,
|
||||
* which specifies that large code-points must be encoded with 3-byte
|
||||
* sequences.
|
||||
*/
|
||||
public static FastDataInput obtain(@NonNull InputStream in) {
|
||||
return new FastDataInput(in, DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a {@link FastDataInput} to potentially be recycled. You must not
|
||||
* interact with the object after releasing it.
|
||||
*/
|
||||
public void release() {
|
||||
mIn = null;
|
||||
mBufferPos = 0;
|
||||
mBufferLim = 0;
|
||||
mStringRefCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initializes the object for the new input.
|
||||
*/
|
||||
protected void setInput(@NonNull InputStream in) {
|
||||
if (mIn != null) {
|
||||
throw new IllegalStateException("setInput() called before calling release()");
|
||||
}
|
||||
|
||||
mIn = Objects.requireNonNull(in);
|
||||
mBufferPos = 0;
|
||||
mBufferLim = 0;
|
||||
mStringRefCount = 0;
|
||||
}
|
||||
|
||||
protected void fill(int need) throws IOException {
|
||||
final int remain = mBufferLim - mBufferPos;
|
||||
System.arraycopy(mBuffer, mBufferPos, mBuffer, 0, remain);
|
||||
mBufferPos = 0;
|
||||
mBufferLim = remain;
|
||||
need -= remain;
|
||||
|
||||
while (need > 0) {
|
||||
int c = mIn.read(mBuffer, mBufferLim, mBufferCap - mBufferLim);
|
||||
if (c == -1) {
|
||||
throw new EOFException();
|
||||
} else {
|
||||
mBufferLim += c;
|
||||
need -= c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mIn.close();
|
||||
release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b) throws IOException {
|
||||
readFully(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b, int off, int len) throws IOException {
|
||||
// Attempt to read directly from buffer space if there's enough room,
|
||||
// otherwise fall back to chunking into place
|
||||
if (mBufferCap >= len) {
|
||||
if (mBufferLim - mBufferPos < len) fill(len);
|
||||
System.arraycopy(mBuffer, mBufferPos, b, off, len);
|
||||
mBufferPos += len;
|
||||
} else {
|
||||
final int remain = mBufferLim - mBufferPos;
|
||||
System.arraycopy(mBuffer, mBufferPos, b, off, remain);
|
||||
mBufferPos += remain;
|
||||
off += remain;
|
||||
len -= remain;
|
||||
|
||||
while (len > 0) {
|
||||
int c = mIn.read(b, off, len);
|
||||
if (c == -1) {
|
||||
throw new EOFException();
|
||||
} else {
|
||||
off += c;
|
||||
len -= c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readUTF() throws IOException {
|
||||
// Attempt to read directly from buffer space if there's enough room,
|
||||
// otherwise fall back to chunking into place
|
||||
final int len = readUnsignedShort();
|
||||
if (mBufferCap > len) {
|
||||
if (mBufferLim - mBufferPos < len) fill(len);
|
||||
final String res = ModifiedUtf8.decode(mBuffer, new char[len], mBufferPos, len);
|
||||
mBufferPos += len;
|
||||
return res;
|
||||
} else {
|
||||
final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
|
||||
readFully(tmp, 0, len);
|
||||
return ModifiedUtf8.decode(tmp, new char[len], 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a {@link String} value with the additional signal that the given
|
||||
* value is a candidate for being canonicalized, similar to
|
||||
* {@link String#intern()}.
|
||||
* <p>
|
||||
* Canonicalization is implemented by writing each unique string value once
|
||||
* the first time it appears, and then writing a lightweight {@code short}
|
||||
* reference when that string is written again in the future.
|
||||
*
|
||||
* @see FastDataOutput#writeInternedUTF(String)
|
||||
*/
|
||||
public @NonNull String readInternedUTF() throws IOException {
|
||||
final int ref = readUnsignedShort();
|
||||
if (ref == MAX_UNSIGNED_SHORT) {
|
||||
final String s = readUTF();
|
||||
|
||||
// We can only safely intern when we have remaining values; if we're
|
||||
// full we at least sent the string value above
|
||||
if (mStringRefCount < MAX_UNSIGNED_SHORT) {
|
||||
if (mStringRefCount == mStringRefs.length) {
|
||||
mStringRefs = Arrays.copyOf(mStringRefs,
|
||||
mStringRefCount + (mStringRefCount >> 1));
|
||||
}
|
||||
mStringRefs[mStringRefCount++] = s;
|
||||
}
|
||||
|
||||
return s;
|
||||
} else {
|
||||
return mStringRefs[ref];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() throws IOException {
|
||||
return readByte() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the same decoded value as {@link #readByte()} but without
|
||||
* actually consuming the underlying data.
|
||||
*/
|
||||
public byte peekByte() throws IOException {
|
||||
if (mBufferLim - mBufferPos < 1) fill(1);
|
||||
return mBuffer[mBufferPos];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() throws IOException {
|
||||
if (mBufferLim - mBufferPos < 1) fill(1);
|
||||
return mBuffer[mBufferPos++];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedByte() throws IOException {
|
||||
return Byte.toUnsignedInt(readByte());
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() throws IOException {
|
||||
if (mBufferLim - mBufferPos < 2) fill(2);
|
||||
return (short) (((mBuffer[mBufferPos++] & 0xff) << 8) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() throws IOException {
|
||||
return Short.toUnsignedInt((short) readShort());
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() throws IOException {
|
||||
return (char) readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() throws IOException {
|
||||
if (mBufferLim - mBufferPos < 4) fill(4);
|
||||
return (((mBuffer[mBufferPos++] & 0xff) << 24) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 16) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 8) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() throws IOException {
|
||||
if (mBufferLim - mBufferPos < 8) fill(8);
|
||||
int h = ((mBuffer[mBufferPos++] & 0xff) << 24) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 16) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 8) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 0);
|
||||
int l = ((mBuffer[mBufferPos++] & 0xff) << 24) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 16) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 8) |
|
||||
((mBuffer[mBufferPos++] & 0xff) << 0);
|
||||
return (((long) h) << 32L) | ((long) l) & 0xffffffffL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() throws IOException {
|
||||
return Float.intBitsToFloat(readInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() throws IOException {
|
||||
return Double.longBitsToDouble(readLong());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int skipBytes(int n) throws IOException {
|
||||
// Callers should read data piecemeal
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readLine() throws IOException {
|
||||
// Callers should read data piecemeal
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.annotation.NonNull;
|
||||
|
||||
import dalvik.system.VMRuntime;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Optimized implementation of {@link DataOutput} which buffers data in memory
|
||||
* before flushing to the underlying {@link OutputStream}.
|
||||
* <p>
|
||||
* Benchmarks have demonstrated this class is 2x more efficient than using a
|
||||
* {@link DataOutputStream} with a {@link BufferedOutputStream}.
|
||||
*/
|
||||
public class FastDataOutput implements DataOutput, Flushable, Closeable {
|
||||
protected static final int MAX_UNSIGNED_SHORT = 65_535;
|
||||
|
||||
protected static final int DEFAULT_BUFFER_SIZE = 32_768;
|
||||
|
||||
protected final VMRuntime mRuntime;
|
||||
|
||||
protected final byte[] mBuffer;
|
||||
protected final int mBufferCap;
|
||||
|
||||
private OutputStream mOut;
|
||||
protected int mBufferPos;
|
||||
|
||||
/**
|
||||
* Values that have been "interned" by {@link #writeInternedUTF(String)}.
|
||||
*/
|
||||
private final HashMap<String, Integer> mStringRefs = new HashMap<>();
|
||||
|
||||
public FastDataOutput(@NonNull OutputStream out, int bufferSize) {
|
||||
mRuntime = VMRuntime.getRuntime();
|
||||
if (bufferSize < 8) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
|
||||
mBufferCap = mBuffer.length;
|
||||
|
||||
setOutput(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@link FastDataOutput} configured with the given
|
||||
* {@link OutputStream} and which encodes large code-points using 3-byte
|
||||
* sequences.
|
||||
* <p>
|
||||
* This <em>is</em> compatible with the {@link DataOutput} API contract,
|
||||
* which specifies that large code-points must be encoded with 3-byte
|
||||
* sequences.
|
||||
*/
|
||||
public static FastDataOutput obtain(@NonNull OutputStream out) {
|
||||
return new FastDataOutput(out, DEFAULT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a {@link FastDataOutput} to potentially be recycled. You must not
|
||||
* interact with the object after releasing it.
|
||||
*/
|
||||
public void release() {
|
||||
if (mBufferPos > 0) {
|
||||
throw new IllegalStateException("Lingering data, call flush() before releasing.");
|
||||
}
|
||||
|
||||
mOut = null;
|
||||
mBufferPos = 0;
|
||||
mStringRefs.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initializes the object for the new output.
|
||||
*/
|
||||
protected void setOutput(@NonNull OutputStream out) {
|
||||
if (mOut != null) {
|
||||
throw new IllegalStateException("setOutput() called before calling release()");
|
||||
}
|
||||
|
||||
mOut = Objects.requireNonNull(out);
|
||||
mBufferPos = 0;
|
||||
mStringRefs.clear();
|
||||
}
|
||||
|
||||
protected void drain() throws IOException {
|
||||
if (mBufferPos > 0) {
|
||||
mOut.write(mBuffer, 0, mBufferPos);
|
||||
mBufferPos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
drain();
|
||||
mOut.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mOut.close();
|
||||
release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
writeByte(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
write(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
if (mBufferCap < len) {
|
||||
drain();
|
||||
mOut.write(b, off, len);
|
||||
} else {
|
||||
if (mBufferCap - mBufferPos < len) drain();
|
||||
System.arraycopy(b, off, mBuffer, mBufferPos, len);
|
||||
mBufferPos += len;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeUTF(String s) throws IOException {
|
||||
final int len = (int) ModifiedUtf8.countBytes(s, false);
|
||||
if (len > MAX_UNSIGNED_SHORT) {
|
||||
throw new IOException("Modified UTF-8 length too large: " + len);
|
||||
}
|
||||
|
||||
// Attempt to write directly to buffer space if there's enough room,
|
||||
// otherwise fall back to chunking into place
|
||||
if (mBufferCap >= 2 + len) {
|
||||
if (mBufferCap - mBufferPos < 2 + len) drain();
|
||||
writeShort(len);
|
||||
ModifiedUtf8.encode(mBuffer, mBufferPos, s);
|
||||
mBufferPos += len;
|
||||
} else {
|
||||
final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
|
||||
ModifiedUtf8.encode(tmp, 0, s);
|
||||
writeShort(len);
|
||||
write(tmp, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a {@link String} value with the additional signal that the given
|
||||
* value is a candidate for being canonicalized, similar to
|
||||
* {@link String#intern()}.
|
||||
* <p>
|
||||
* Canonicalization is implemented by writing each unique string value once
|
||||
* the first time it appears, and then writing a lightweight {@code short}
|
||||
* reference when that string is written again in the future.
|
||||
*
|
||||
* @see FastDataInput#readInternedUTF()
|
||||
*/
|
||||
public void writeInternedUTF(@NonNull String s) throws IOException {
|
||||
Integer ref = mStringRefs.get(s);
|
||||
if (ref != null) {
|
||||
writeShort(ref);
|
||||
} else {
|
||||
writeShort(MAX_UNSIGNED_SHORT);
|
||||
writeUTF(s);
|
||||
|
||||
// We can only safely intern when we have remaining values; if we're
|
||||
// full we at least sent the string value above
|
||||
ref = mStringRefs.size();
|
||||
if (ref < MAX_UNSIGNED_SHORT) {
|
||||
mStringRefs.put(s, ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBoolean(boolean v) throws IOException {
|
||||
writeByte(v ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeByte(int v) throws IOException {
|
||||
if (mBufferCap - mBufferPos < 1) drain();
|
||||
mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeShort(int v) throws IOException {
|
||||
if (mBufferCap - mBufferPos < 2) drain();
|
||||
mBuffer[mBufferPos++] = (byte) ((v >> 8) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChar(int v) throws IOException {
|
||||
writeShort((short) v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeInt(int v) throws IOException {
|
||||
if (mBufferCap - mBufferPos < 4) drain();
|
||||
mBuffer[mBufferPos++] = (byte) ((v >> 24) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((v >> 16) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((v >> 8) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((v >> 0) & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLong(long v) throws IOException {
|
||||
if (mBufferCap - mBufferPos < 8) drain();
|
||||
int i = (int) (v >> 32);
|
||||
mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((i >> 8) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((i >> 0) & 0xff);
|
||||
i = (int) v;
|
||||
mBuffer[mBufferPos++] = (byte) ((i >> 24) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((i >> 16) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((i >> 8) & 0xff);
|
||||
mBuffer[mBufferPos++] = (byte) ((i >> 0) & 0xff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFloat(float v) throws IOException {
|
||||
writeInt(Float.floatToIntBits(v));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDouble(double v) throws IOException {
|
||||
writeLong(Double.doubleToLongBits(v));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytes(String s) throws IOException {
|
||||
// Callers should use writeUTF()
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChars(String s) throws IOException {
|
||||
// Callers should use writeUTF()
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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 java.io.UTFDataFormatException;
|
||||
|
||||
public class ModifiedUtf8 {
|
||||
/**
|
||||
* Decodes a byte array containing <i>modified UTF-8</i> bytes into a string.
|
||||
*
|
||||
* <p>Note that although this method decodes the (supposedly impossible) zero byte to U+0000,
|
||||
* that's what the RI does too.
|
||||
*/
|
||||
public static String decode(byte[] in, char[] out, int offset, int utfSize)
|
||||
throws UTFDataFormatException {
|
||||
int count = 0, s = 0, a;
|
||||
while (count < utfSize) {
|
||||
if ((out[s] = (char) in[offset + count++]) < '\u0080') {
|
||||
s++;
|
||||
} else if (((a = out[s]) & 0xe0) == 0xc0) {
|
||||
if (count >= utfSize) {
|
||||
throw new UTFDataFormatException("bad second byte at " + count);
|
||||
}
|
||||
int b = in[offset + count++];
|
||||
if ((b & 0xC0) != 0x80) {
|
||||
throw new UTFDataFormatException("bad second byte at " + (count - 1));
|
||||
}
|
||||
out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
|
||||
} else if ((a & 0xf0) == 0xe0) {
|
||||
if (count + 1 >= utfSize) {
|
||||
throw new UTFDataFormatException("bad third byte at " + (count + 1));
|
||||
}
|
||||
int b = in[offset + count++];
|
||||
int c = in[offset + count++];
|
||||
if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
|
||||
throw new UTFDataFormatException("bad second or third byte at " + (count - 2));
|
||||
}
|
||||
out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
|
||||
} else {
|
||||
throw new UTFDataFormatException("bad byte at " + (count - 1));
|
||||
}
|
||||
}
|
||||
return new String(out, 0, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes the modified UTF-8 representation of 's' would take. Note
|
||||
* that this is just the space for the bytes representing the characters, not the length
|
||||
* which precedes those bytes, because different callers represent the length differently,
|
||||
* as two, four, or even eight bytes. If {@code shortLength} is true, we'll throw an
|
||||
* exception if the string is too long for its length to be represented by a short.
|
||||
*/
|
||||
public static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
|
||||
long result = 0;
|
||||
final int length = s.length();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
char ch = s.charAt(i);
|
||||
if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
|
||||
++result;
|
||||
} else if (ch <= 2047) {
|
||||
result += 2;
|
||||
} else {
|
||||
result += 3;
|
||||
}
|
||||
if (shortLength && result > 65535) {
|
||||
throw new UTFDataFormatException("String more than 65535 UTF bytes long");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the <i>modified UTF-8</i> bytes corresponding to string {@code s} into the
|
||||
* byte array {@code dst}, starting at the given {@code offset}.
|
||||
*/
|
||||
public static void encode(byte[] dst, int offset, String s) {
|
||||
final int length = s.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = s.charAt(i);
|
||||
if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
|
||||
dst[offset++] = (byte) ch;
|
||||
} else if (ch <= 2047) {
|
||||
dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
|
||||
dst[offset++] = (byte) (0x80 | (0x3f & ch));
|
||||
} else {
|
||||
dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
|
||||
dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
|
||||
dst[offset++] = (byte) (0x80 | (0x3f & ch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ModifiedUtf8() {
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.modules.utils;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
/**
|
||||
* Specialization of {@link XmlPullParser} which adds explicit methods to
|
||||
* support consistent and efficient conversion of primitive data types.
|
||||
*/
|
||||
public interface TypedXmlPullParser extends XmlPullParser {
|
||||
/**
|
||||
* @return index of requested attribute, otherwise {@code -1} if undefined
|
||||
*/
|
||||
default int getAttributeIndex(@Nullable String namespace, @NonNull String name) {
|
||||
final boolean namespaceNull = (namespace == null);
|
||||
final int count = getAttributeCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if ((namespaceNull || namespace.equals(getAttributeNamespace(i)))
|
||||
&& name.equals(getAttributeName(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of requested attribute
|
||||
* @throws XmlPullParserException if the value is undefined
|
||||
*/
|
||||
default int getAttributeIndexOrThrow(@Nullable String namespace, @NonNull String name)
|
||||
throws XmlPullParserException {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) {
|
||||
throw new XmlPullParserException("Missing attribute " + name);
|
||||
} else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed
|
||||
*/
|
||||
@NonNull byte[] getAttributeBytesHex(int index) throws XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed
|
||||
*/
|
||||
@NonNull byte[] getAttributeBytesBase64(int index) throws XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed
|
||||
*/
|
||||
int getAttributeInt(int index) throws XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed
|
||||
*/
|
||||
int getAttributeIntHex(int index) throws XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed
|
||||
*/
|
||||
long getAttributeLong(int index) throws XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed
|
||||
*/
|
||||
long getAttributeLongHex(int index) throws XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed
|
||||
*/
|
||||
float getAttributeFloat(int index) throws XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed
|
||||
*/
|
||||
double getAttributeDouble(int index) throws XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed
|
||||
*/
|
||||
boolean getAttributeBoolean(int index) throws XmlPullParserException;
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed or undefined
|
||||
*/
|
||||
default @NonNull byte[] getAttributeBytesHex(@Nullable String namespace,
|
||||
@NonNull String name) throws XmlPullParserException {
|
||||
return getAttributeBytesHex(getAttributeIndexOrThrow(namespace, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed or undefined
|
||||
*/
|
||||
default @NonNull byte[] getAttributeBytesBase64(@Nullable String namespace,
|
||||
@NonNull String name) throws XmlPullParserException {
|
||||
return getAttributeBytesBase64(getAttributeIndexOrThrow(namespace, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed or undefined
|
||||
*/
|
||||
default int getAttributeInt(@Nullable String namespace, @NonNull String name)
|
||||
throws XmlPullParserException {
|
||||
return getAttributeInt(getAttributeIndexOrThrow(namespace, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed or undefined
|
||||
*/
|
||||
default int getAttributeIntHex(@Nullable String namespace, @NonNull String name)
|
||||
throws XmlPullParserException {
|
||||
return getAttributeIntHex(getAttributeIndexOrThrow(namespace, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed or undefined
|
||||
*/
|
||||
default long getAttributeLong(@Nullable String namespace, @NonNull String name)
|
||||
throws XmlPullParserException {
|
||||
return getAttributeLong(getAttributeIndexOrThrow(namespace, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed or undefined
|
||||
*/
|
||||
default long getAttributeLongHex(@Nullable String namespace, @NonNull String name)
|
||||
throws XmlPullParserException {
|
||||
return getAttributeLongHex(getAttributeIndexOrThrow(namespace, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed or undefined
|
||||
*/
|
||||
default float getAttributeFloat(@Nullable String namespace, @NonNull String name)
|
||||
throws XmlPullParserException {
|
||||
return getAttributeFloat(getAttributeIndexOrThrow(namespace, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed or undefined
|
||||
*/
|
||||
default double getAttributeDouble(@Nullable String namespace, @NonNull String name)
|
||||
throws XmlPullParserException {
|
||||
return getAttributeDouble(getAttributeIndexOrThrow(namespace, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}
|
||||
* @throws XmlPullParserException if the value is malformed or undefined
|
||||
*/
|
||||
default boolean getAttributeBoolean(@Nullable String namespace, @NonNull String name)
|
||||
throws XmlPullParserException {
|
||||
return getAttributeBoolean(getAttributeIndexOrThrow(namespace, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
|
||||
* default value if the value is malformed or undefined
|
||||
*/
|
||||
default @Nullable byte[] getAttributeBytesHex(@Nullable String namespace,
|
||||
@NonNull String name, @Nullable byte[] defaultValue) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) return defaultValue;
|
||||
try {
|
||||
return getAttributeBytesHex(index);
|
||||
} catch (Exception ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
|
||||
* default value if the value is malformed or undefined
|
||||
*/
|
||||
default @Nullable byte[] getAttributeBytesBase64(@Nullable String namespace,
|
||||
@NonNull String name, @Nullable byte[] defaultValue) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) return defaultValue;
|
||||
try {
|
||||
return getAttributeBytesBase64(index);
|
||||
} catch (Exception ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
|
||||
* default value if the value is malformed or undefined
|
||||
*/
|
||||
default int getAttributeInt(@Nullable String namespace, @NonNull String name,
|
||||
int defaultValue) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) return defaultValue;
|
||||
try {
|
||||
return getAttributeInt(index);
|
||||
} catch (Exception ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
|
||||
* default value if the value is malformed or undefined
|
||||
*/
|
||||
default int getAttributeIntHex(@Nullable String namespace, @NonNull String name,
|
||||
int defaultValue) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) return defaultValue;
|
||||
try {
|
||||
return getAttributeIntHex(index);
|
||||
} catch (Exception ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
|
||||
* default value if the value is malformed or undefined
|
||||
*/
|
||||
default long getAttributeLong(@Nullable String namespace, @NonNull String name,
|
||||
long defaultValue) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) return defaultValue;
|
||||
try {
|
||||
return getAttributeLong(index);
|
||||
} catch (Exception ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
|
||||
* default value if the value is malformed or undefined
|
||||
*/
|
||||
default long getAttributeLongHex(@Nullable String namespace, @NonNull String name,
|
||||
long defaultValue) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) return defaultValue;
|
||||
try {
|
||||
return getAttributeLongHex(index);
|
||||
} catch (Exception ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
|
||||
* default value if the value is malformed or undefined
|
||||
*/
|
||||
default float getAttributeFloat(@Nullable String namespace, @NonNull String name,
|
||||
float defaultValue) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) return defaultValue;
|
||||
try {
|
||||
return getAttributeFloat(index);
|
||||
} catch (Exception ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
|
||||
* default value if the value is malformed or undefined
|
||||
*/
|
||||
default double getAttributeDouble(@Nullable String namespace, @NonNull String name,
|
||||
double defaultValue) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) return defaultValue;
|
||||
try {
|
||||
return getAttributeDouble(index);
|
||||
} catch (Exception ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return decoded strongly-typed {@link #getAttributeValue}, otherwise
|
||||
* default value if the value is malformed or undefined
|
||||
*/
|
||||
default boolean getAttributeBoolean(@Nullable String namespace, @NonNull String name,
|
||||
boolean defaultValue) {
|
||||
final int index = getAttributeIndex(namespace, name);
|
||||
if (index == -1) return defaultValue;
|
||||
try {
|
||||
return getAttributeBoolean(index);
|
||||
} catch (Exception ignored) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.modules.utils;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Specialization of {@link XmlSerializer} which adds explicit methods to
|
||||
* support consistent and efficient conversion of primitive data types.
|
||||
*/
|
||||
public interface TypedXmlSerializer extends XmlSerializer {
|
||||
/**
|
||||
* Functionally equivalent to {@link #attribute(String, String, String)} but
|
||||
* with the additional signal that the given value is a candidate for being
|
||||
* canonicalized, similar to {@link String#intern()}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeInterned(@Nullable String namespace, @NonNull String name,
|
||||
@NonNull String value) throws IOException;
|
||||
|
||||
/**
|
||||
* Encode the given strongly-typed value and serialize using
|
||||
* {@link #attribute(String, String, String)}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeBytesHex(@Nullable String namespace, @NonNull String name,
|
||||
@NonNull byte[] value) throws IOException;
|
||||
|
||||
/**
|
||||
* Encode the given strongly-typed value and serialize using
|
||||
* {@link #attribute(String, String, String)}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeBytesBase64(@Nullable String namespace, @NonNull String name,
|
||||
@NonNull byte[] value) throws IOException;
|
||||
|
||||
/**
|
||||
* Encode the given strongly-typed value and serialize using
|
||||
* {@link #attribute(String, String, String)}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeInt(@Nullable String namespace, @NonNull String name,
|
||||
int value) throws IOException;
|
||||
|
||||
/**
|
||||
* Encode the given strongly-typed value and serialize using
|
||||
* {@link #attribute(String, String, String)}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeIntHex(@Nullable String namespace, @NonNull String name,
|
||||
int value) throws IOException;
|
||||
|
||||
/**
|
||||
* Encode the given strongly-typed value and serialize using
|
||||
* {@link #attribute(String, String, String)}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeLong(@Nullable String namespace, @NonNull String name,
|
||||
long value) throws IOException;
|
||||
|
||||
/**
|
||||
* Encode the given strongly-typed value and serialize using
|
||||
* {@link #attribute(String, String, String)}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeLongHex(@Nullable String namespace, @NonNull String name,
|
||||
long value) throws IOException;
|
||||
|
||||
/**
|
||||
* Encode the given strongly-typed value and serialize using
|
||||
* {@link #attribute(String, String, String)}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeFloat(@Nullable String namespace, @NonNull String name,
|
||||
float value) throws IOException;
|
||||
|
||||
/**
|
||||
* Encode the given strongly-typed value and serialize using
|
||||
* {@link #attribute(String, String, String)}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeDouble(@Nullable String namespace, @NonNull String name,
|
||||
double value) throws IOException;
|
||||
|
||||
/**
|
||||
* Encode the given strongly-typed value and serialize using
|
||||
* {@link #attribute(String, String, String)}.
|
||||
*/
|
||||
@NonNull XmlSerializer attributeBoolean(@Nullable String namespace, @NonNull String name,
|
||||
boolean value) throws IOException;
|
||||
}
|
||||
@@ -23,6 +23,9 @@ import static org.junit.Assert.fail;
|
||||
import android.annotation.NonNull;
|
||||
import android.util.ExceptionUtils;
|
||||
|
||||
import com.android.modules.utils.FastDataInput;
|
||||
import com.android.modules.utils.FastDataOutput;
|
||||
|
||||
import libcore.util.HexEncoding;
|
||||
|
||||
import org.junit.Assume;
|
||||
|
||||
Reference in New Issue
Block a user