/*
* Copyright 2008 Android4ME
*
* 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 androidx.pluginmgr;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.util.TypedValue;
/**
*
* Read xml document from Android's binary xml file.
*/
class XmlManifestReader {
public static final String DEFAULT_XML = "AndroidManifest.xml";
private XmlManifestReader() {
}
public static String getManifestXMLFromAPK(String apkPath) {
ZipFile file = null;
String rs = null;
try {
File apkFile = new File(apkPath);
file = new ZipFile(apkFile, ZipFile.OPEN_READ);
ZipEntry entry = file.getEntry(DEFAULT_XML);
rs = getManifestXMLFromAPK(file, entry);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (file != null) {
try {
file.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return rs;
}
public static String getManifestXMLFromAPK(ZipFile file, ZipEntry entry) {
StringBuilder xmlSb = new StringBuilder(100);
XmlResourceParser parser = null;
try {
parser = new XmlResourceParser();
parser.open(file.getInputStream(entry));
StringBuilder sb = new StringBuilder(10);
final String indentStep = " ";
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
switch (type) {
case XmlPullParser.START_DOCUMENT: {
log(xmlSb, "<?xml version=\"1.0\" encoding=\"utf-8\"?>");
break;
}
case XmlPullParser.START_TAG: {
log(false, xmlSb, "%s<%s%s", sb,
getNamespacePrefix(parser.getPrefix()),
parser.getName());
sb.append(indentStep);
int namespaceCountBefore = parser.getNamespaceCount(parser
.getDepth() - 1);
int namespaceCount = parser.getNamespaceCount(parser
.getDepth());
for (int i = namespaceCountBefore; i != namespaceCount; ++i) {
log(xmlSb, "%sxmlns:%s=\"%s\"",
i == namespaceCountBefore ? " " : sb,
parser.getNamespacePrefix(i),
parser.getNamespaceUri(i));
}
for (int i = 0, size = parser.getAttributeCount(); i != size; ++i) {
log(false,
xmlSb,
"%s%s%s=\"%s\"",
" ",
getNamespacePrefix(parser.getAttributePrefix(i)),
parser.getAttributeName(i),
getAttributeValue(parser, i));
}
// log("%s>",sb);
log(xmlSb, ">");
break;
}
case XmlPullParser.END_TAG: {
sb.setLength(sb.length() - indentStep.length());
log(xmlSb, "%s</%s%s>", sb,
getNamespacePrefix(parser.getPrefix()),
parser.getName());
break;
}
case XmlPullParser.TEXT: {
log(xmlSb, "%s%s", sb, parser.getText());
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
parser.close();
}
return xmlSb.toString();
}
private static String getNamespacePrefix(String prefix) {
if (prefix == null || prefix.length() == 0) {
return "";
}
return prefix + ":";
}
private static String getAttributeValue(XmlResourceParser parser, int index) {
int type = parser.getAttributeValueType(index);
int data = parser.getAttributeValueData(index);
if (type == TypedValue.TYPE_STRING) {
return parser.getAttributeValue(index);
}
if (type == TypedValue.TYPE_ATTRIBUTE) {
return String.format("?%s%08X", getPackage(data), data);
}
if (type == TypedValue.TYPE_REFERENCE) {
return String.format("@%s%08X", getPackage(data), data);
}
if (type == TypedValue.TYPE_FLOAT) {
return String.valueOf(Float.intBitsToFloat(data));
}
if (type == TypedValue.TYPE_INT_HEX) {
return String.format("0x%08X", data);
}
if (type == TypedValue.TYPE_INT_BOOLEAN) {
return data != 0 ? "true" : "false";
}
if (type == TypedValue.TYPE_DIMENSION) {
return Float.toString(complexToFloat(data))
+ DIMENSION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
}
if (type == TypedValue.TYPE_FRACTION) {
return Float.toString(complexToFloat(data))
+ FRACTION_UNITS[data & TypedValue.COMPLEX_UNIT_MASK];
}
if (type >= TypedValue.TYPE_FIRST_COLOR_INT
&& type <= TypedValue.TYPE_LAST_COLOR_INT) {
return String.format("#%08X", data);
}
if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return String.valueOf(data);
}
return String.format("<0x%X, type 0x%02X>", data, type);
}
private static String getPackage(int id) {
if (id >>> 24 == 1) {
return "android:";
}
return "";
}
private static void log(StringBuilder xmlSb, String format,
Object... arguments) {
log(true, xmlSb, format, arguments);
}
private static void log(boolean newLine, StringBuilder xmlSb,
String format, Object... arguments) {
// System.out.printf(format,arguments);
// if(newLine) System.out.println();
xmlSb.append(String.format(format, arguments));
if (newLine)
xmlSb.append("\n");
}
// ///////////////////////////////// ILLEGAL STUFF, DONT LOOK :)
private static float complexToFloat(int complex) {
return (float) (complex & 0xFFFFFF00) * RADIX_MULTS[(complex >> 4) & 3];
}
private static final float RADIX_MULTS[] = { 0.00390625F, 3.051758E-005F,
1.192093E-007F, 4.656613E-010F };
private static final String DIMENSION_UNITS[] = { "px", "dip", "sp", "pt",
"in", "mm", "", "" };
private static final String FRACTION_UNITS[] = { "%", "%p", "", "", "", "",
"", "" };
}
/**
* @author Dmitry Skiba
*
* Binary xml files parser.
*
* Parser has only two states: (1) Operational state, which parser
* obtains after first successful call to next() and retains until
* open(), close(), or failed call to next(). (2) Closed state, which
* parser obtains after open(), close(), or failed call to next(). In
* this state methods return invalid values or throw exceptions.
*
* TODO: * check all methods in closed state
*
*/
class XmlResourceParser implements android.content.res.XmlResourceParser {
public XmlResourceParser() {
resetEventInfo();
}
public void open(InputStream stream) {
close();
if (stream != null) {
m_reader = new IntReader(stream, false);
}
}
public void close() {
if (!m_operational) {
return;
}
m_operational = false;
m_reader.close();
m_reader = null;
m_strings = null;
m_resourceIDs = null;
m_namespaces.reset();
resetEventInfo();
}
public static final void readCheckType(IntReader reader, int expectedType)
throws IOException {
int type = reader.readInt();
if (type != expectedType) {
throw new IOException("Expected chunk of type 0x"
+ Integer.toHexString(expectedType) + ", read 0x"
+ Integer.toHexString(type) + ".");
}
}
// ///////////////////////////////// iteration
public int next() throws XmlPullParserException, IOException {
if (m_reader == null) {
throw new XmlPullParserException("Parser is not opened.", this,
null);
}
try {
doNext();
return m_event;
} catch (IOException e) {
close();
throw e;
}
}
public int nextToken() throws XmlPullParserException, IOException {
return next();
}
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("Expected start or end tag.",
this, null);
}
return eventType;
}
public String nextText() throws XmlPullParserException, IOException {
if (getEventType() != START_TAG) {
throw new XmlPullParserException(
"Parser must be on START_TAG to read next text.", this,
null);
}
int eventType = next();
if (eventType == TEXT) {
String result = getText();
eventType = next();
if (eventType != END_TAG) {
throw new XmlPullParserException(
"Event TEXT must be immediately followed by END_TAG.",
this, null);
}
return result;
} else if (eventType == END_TAG) {
return "";
} else {
throw new XmlPullParserException(
"Parser must be on START_TAG or TEXT to read text.", this,
null);
}
}
public void require(int type, String namespace, String name)
throws XmlPullParserException, IOException {
if (type != getEventType()
|| (namespace != null && !namespace.equals(getNamespace()))
|| (name != null && !name.equals(getName()))) {
throw new XmlPullParserException(TYPES[type] + " is expected.",
this, null);
}
}
public int getDepth() {
return m_namespaces.getDepth() - 1;
}
public int getEventType() throws XmlPullParserException {
return m_event;
}
public int getLineNumber() {
return m_lineNumber;
}
public String getName() {
if (m_name == -1 || (m_event != START_TAG && m_event != END_TAG)) {
return null;
}
return m_strings.getString(m_name);
}
public String getText() {
if (m_name == -1 || m_event != TEXT) {
return null;
}
return m_strings.getString(m_name);
}
public char[] getTextCharacters(int[] holderForStartAndLength) {
String text = getText();
if (text == null) {
return null;
}
holderForStartAndLength[0] = 0;
holderForStartAndLength[1] = text.length();
char[] chars = new char[text.length()];
text.getChars(0, text.length(), chars, 0);
return chars;
}
public String getNamespace() {
return m_strings.getString(m_namespaceUri);
}
public String getPrefix() {
int prefix = m_namespaces.findPrefix(m_namespaceUri);
return m_strings.getString(prefix);
}
public String getPositionDescription() {
return "XML line #" + getLineNumber();
}
public int getNamespaceCount(int depth) throws XmlPullParserException {
return m_namespaces.getAccumulatedCount(depth);
}
public String getNamespacePrefix(int pos) throws XmlPullParserException {
int prefix = m_namespaces.getPrefix(pos);
return m_strings.getString(prefix);
}
public String getNamespaceUri(int pos) throws XmlPullParserException {
int uri = m_namespaces.getUri(pos);
return m_strings.getString(uri);
}
// ///////////////////////////////// attributes
public String getClassAttribute() {
if (m_classAttribute == -1) {
return null;
}
int offset = getAttributeOffset(m_classAttribute);
int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(value);
}
public String getIdAttribute() {
if (m_idAttribute == -1) {
return null;
}
int offset = getAttributeOffset(m_idAttribute);
int value = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(value);
}
public int getIdAttributeResourceValue(int defaultValue) {
if (m_idAttribute == -1) {
return defaultValue;
}
int offset = getAttributeOffset(m_idAttribute);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType != TypedValue.TYPE_REFERENCE) {
return defaultValue;
}
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
public int getStyleAttribute() {
if (m_styleAttribute == -1) {
return 0;
}
int offset = getAttributeOffset(m_styleAttribute);
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
public int getAttributeCount() {
if (m_event != START_TAG) {
return -1;
}
return m_attributes.length / ATTRIBUTE_LENGHT;
}
public String getAttributeNamespace(int index) {
int offset = getAttributeOffset(index);
int namespace = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
if (namespace == -1) {
return "";
}
return m_strings.getString(namespace);
}
public String getAttributePrefix(int index) {
int offset = getAttributeOffset(index);
int uri = m_attributes[offset + ATTRIBUTE_IX_NAMESPACE_URI];
int prefix = m_namespaces.findPrefix(uri);
if (prefix == -1) {
return "";
}
return m_strings.getString(prefix);
}
public String getAttributeName(int index) {
int offset = getAttributeOffset(index);
int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
if (name == -1) {
return "";
}
return m_strings.getString(name);
}
public int getAttributeNameResource(int index) {
int offset = getAttributeOffset(index);
int name = m_attributes[offset + ATTRIBUTE_IX_NAME];
if (m_resourceIDs == null || name < 0 || name >= m_resourceIDs.length) {
return 0;
}
return m_resourceIDs[name];
}
public int getAttributeValueType(int index) {
int offset = getAttributeOffset(index);
return m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
}
public int getAttributeValueData(int index) {
int offset = getAttributeOffset(index);
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
@SuppressWarnings("unused")
public String getAttributeValue(int index) {
int offset = getAttributeOffset(index);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType == TypedValue.TYPE_STRING) {
int valueString = m_attributes[offset + ATTRIBUTE_IX_VALUE_STRING];
return m_strings.getString(valueString);
}
int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
return "";// TypedValue.coerceToString(valueType,valueData);
}
public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
return getAttributeIntValue(index, defaultValue ? 1 : 0) != 0;
}
public float getAttributeFloatValue(int index, float defaultValue) {
int offset = getAttributeOffset(index);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType == TypedValue.TYPE_FLOAT) {
int valueData = m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
return Float.intBitsToFloat(valueData);
}
return defaultValue;
}
public int getAttributeIntValue(int index, int defaultValue) {
int offset = getAttributeOffset(index);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType >= TypedValue.TYPE_FIRST_INT
&& valueType <= TypedValue.TYPE_LAST_INT) {
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
return defaultValue;
}
public int getAttributeUnsignedIntValue(int index, int defaultValue) {
return getAttributeIntValue(index, defaultValue);
}
public int getAttributeResourceValue(int index, int defaultValue) {
int offset = getAttributeOffset(index);
int valueType = m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType == TypedValue.TYPE_REFERENCE) {
return m_attributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
return defaultValue;
}
public String getAttributeValue(String namespace, String attribute) {
int index = findAttribute(namespace, attribute);
if (index == -1) {
return null;
}
return getAttributeValue(index);
}
public boolean getAttributeBooleanValue(String namespace, String attribute,
boolean defaultValue) {
int index = findAttribute(namespace, attribute);
if (index == -1) {
return defaultValue;
}
return getAttributeBooleanValue(index, defaultValue);
}
public float getAttributeFloatValue(String namespace, String attribute,
float defaultValue) {
int index = findAttribute(namespace, attribute);
if (index == -1) {
return defaultValue;
}
return getAttributeFloatValue(index, defaultValue);
}
public int getAttributeIntValue(String namespace, String attribute,
int defaultValue) {
int index = findAttribute(namespace, attribute);
if (index == -1) {
return defaultValue;
}
return getAttributeIntValue(index, defaultValue);
}
public int getAttributeUnsignedIntValue(String namespace, String attribute,
int defaultValue) {
int index = findAttribute(namespace, attribute);
if (index == -1) {
return defaultValue;
}
return getAttributeUnsignedIntValue(index, defaultValue);
}
public int getAttributeResourceValue(String namespace, String attribute,
int defaultValue) {
int index = findAttribute(namespace, attribute);
if (index == -1) {
return defaultValue;
}
return getAttributeResourceValue(index, defaultValue);
}
public int getAttributeListValue(int index, String[] options,
int defaultValue) {
// TODO implement
return 0;
}
public int getAttributeListValue(String namespace, String attribute,
String[] options, int defaultValue) {
// TODO implement
return 0;
}
public String getAttributeType(int index) {
return "CDATA";
}
public boolean isAttributeDefault(int index) {
return false;
}
// ///////////////////////////////// dummies
public void setInput(InputStream stream, String inputEncoding)
throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public void setInput(Reader reader) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public String getInputEncoding() {
return null;
}
public int getColumnNumber() {
return -1;
}
public boolean isEmptyElementTag() throws XmlPullParserException {
return false;
}
public boolean isWhitespace() throws XmlPullParserException {
return false;
}
public void defineEntityReplacementText(String entityName,
String replacementText) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public String getNamespace(String prefix) {
throw new RuntimeException(E_NOT_SUPPORTED);
}
public Object getProperty(String name) {
return null;
}
public void setProperty(String name, Object value)
throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
public boolean getFeature(String feature) {
return false;
}
public void setFeature(String name, boolean value)
throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}
// final void fetchAttributes(int[] styleableIDs,TypedArray result) {
// result.resetIndices();
// if (m_attributes==null || m_resourceIDs==null) {
// return;
// }
// boolean needStrings=false;
// for (int i=0,e=styleableIDs.length;i!=e;++i) {
// int id=styleableIDs[i];
// for (int o=0;o!=m_attributes.length;o+=ATTRIBUTE_LENGHT) {
// int name=m_attributes[o+ATTRIBUTE_IX_NAME];
// if (name>=m_resourceIDs.length ||
// m_resourceIDs[name]!=id)
// {
// continue;
// }
// int valueType=m_attributes[o+ATTRIBUTE_IX_VALUE_TYPE];
// int valueData;
// int assetCookie;
// if (valueType==TypedValue.TYPE_STRING) {
// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_STRING];
// assetCookie=-1;
// needStrings=true;
// } else {
// valueData=m_attributes[o+ATTRIBUTE_IX_VALUE_DATA];
// assetCookie=0;
// }
// result.addValue(i,valueType,valueData,assetCookie,id,0);
// }
// }
// if (needStrings) {
// result.setStrings(m_strings);
// }
// }
final StringBlock getStrings() {
return m_strings;
}
// /////////////////////////////////
private final int getAttributeOffset(int index) {
if (m_event != START_TAG) {
throw new IndexOutOfBoundsException(
"Current event is not START_TAG.");
}
int offset = index * 5;
if (offset >= m_attributes.length) {
throw new IndexOutOfBoundsException("Invalid attribute index ("
+ index + ").");
}
return offset;
}
private final int findAttribute(String namespace, String attribute) {
if (m_strings == null || attribute == null) {
return -1;
}
int name = m_strings.find(attribute);
if (name == -1) {
return -1;
}
int uri = (namespace != null) ? m_strings.find(namespace) : -1;
for (int o = 0; o != m_attributes.length; ++o) {
if (name == m_attributes[o + ATTRIBUTE_IX_NAME]
&& (uri == -1 || uri == m_attributes[o
+ ATTRIBUTE_IX_NAMESPACE_URI])) {
return o / ATTRIBUTE_LENGHT;
}
}
return -1;
}
private final void resetEventInfo() {
m_event = -1;
m_lineNumber = -1;
m_name = -1;
m_namespaceUri = -1;
m_attributes = null;
m_idAttribute = -1;
m_classAttribute = -1;
m_styleAttribute = -1;
}
private final void doNext() throws IOException {
// Delayed initialization.
if (m_strings == null) {
readCheckType(m_reader, CHUNK_AXML_FILE);
/* chunkSize */m_reader.skipInt();
m_strings = StringBlock.read(m_reader);
m_namespaces.increaseDepth();
m_operational = true;
}
if (m_event == END_DOCUMENT) {
return;
}
int event = m_event;
resetEventInfo();
while (true) {
if (m_decreaseDepth) {
m_decreaseDepth = false;
m_namespaces.decreaseDepth();
}
// Fake END_DOCUMENT event.
if (event == END_TAG && m_namespaces.getDepth() == 1
&& m_namespaces.getCurrentCount() == 0) {
m_event = END_DOCUMENT;
break;
}
int chunkType;
if (event == START_DOCUMENT) {
// Fake event, see CHUNK_XML_START_TAG handler.
chunkType = CHUNK_XML_START_TAG;
} else {
chunkType = m_reader.readInt();
}
if (chunkType == CHUNK_RESOURCEIDS) {
int chunkSize = m_reader.readInt();
if (chunkSize < 8 || (chunkSize % 4) != 0) {
throw new IOException("Invalid resource ids size ("
+ chunkSize + ").");
}
m_resourceIDs = m_reader.readIntArray(chunkSize / 4 - 2);
continue;
}
if (chunkType < CHUNK_XML_FIRST || chunkType > CHUNK_XML_LAST) {
throw new IOException("Invalid chunk type (" + chunkType + ").");
}
// Fake START_DOCUMENT event.
if (chunkType == CHUNK_XML_START_TAG && event == -1) {
m_event = START_DOCUMENT;
break;
}
// Common header.
/* chunkSize */m_reader.skipInt();
int lineNumber = m_reader.readInt();
/* 0xFFFFFFFF */m_reader.skipInt();
if (chunkType == CHUNK_XML_START_NAMESPACE
|| chunkType == CHUNK_XML_END_NAMESPACE) {
if (chunkType == CHUNK_XML_START_NAMESPACE) {
int prefix = m_reader.readInt();
int uri = m_reader.readInt();
m_namespaces.push(prefix, uri);
} else {
/* prefix */m_reader.skipInt();
/* uri */m_reader.skipInt();
m_namespaces.pop();
}
continue;
}
m_lineNumber = lineNumber;
if (chunkType == CHUNK_XML_START_TAG) {
m_namespaceUri = m_reader.readInt();
m_name = m_reader.readInt();
/* flags? */m_reader.skipInt();
int attributeCount = m_reader.readInt();
m_idAttribute = (attributeCount >>> 16) - 1;
attributeCount &= 0xFFFF;
m_classAttribute = m_reader.readInt();
m_styleAttribute = (m_classAttribute >>> 16) - 1;
m_classAttribute = (m_classAttribute & 0xFFFF) - 1;
m_attributes = m_reader.readIntArray(attributeCount
* ATTRIBUTE_LENGHT);
for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < m_attributes.length;) {
m_attributes[i] = (m_attributes[i] >>> 24);
i += ATTRIBUTE_LENGHT;
}
m_namespaces.increaseDepth();
m_event = START_TAG;
break;
}
if (chunkType == CHUNK_XML_END_TAG) {
m_namespaceUri = m_reader.readInt();
m_name = m_reader.readInt();
m_event = END_TAG;
m_decreaseDepth = true;
break;
}
if (chunkType == CHUNK_XML_TEXT) {
m_name = m_reader.readInt();
/* ? */m_reader.skipInt();
/* ? */m_reader.skipInt();
m_event = TEXT;
break;
}
}
}
// ///////////////////////////////// data
/*
* All values are essentially indices, e.g. m_name is an index of name in
* m_strings.
*/
private IntReader m_reader;
private boolean m_operational = false;
private StringBlock m_strings;
private int[] m_resourceIDs;
private NamespaceStack m_namespaces = new NamespaceStack();
private boolean m_decreaseDepth;
private int m_event;
private int m_lineNumber;
private int m_name;
private int m_namespaceUri;
private int[] m_attributes;
private int m_idAttribute;
private int m_classAttribute;
private int m_styleAttribute;
private static final String E_NOT_SUPPORTED = "Method is not supported.";
private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0,
ATTRIBUTE_IX_NAME = 1, ATTRIBUTE_IX_VALUE_STRING = 2,
ATTRIBUTE_IX_VALUE_TYPE = 3, ATTRIBUTE_IX_VALUE_DATA = 4,
ATTRIBUTE_LENGHT = 5;
private static final int CHUNK_AXML_FILE = 0x00080003,
CHUNK_RESOURCEIDS = 0x00080180, CHUNK_XML_FIRST = 0x00100100,
CHUNK_XML_START_NAMESPACE = 0x00100100,
CHUNK_XML_END_NAMESPACE = 0x00100101,
CHUNK_XML_START_TAG = 0x00100102, CHUNK_XML_END_TAG = 0x00100103,
CHUNK_XML_TEXT = 0x00100104, CHUNK_XML_LAST = 0x00100104;
}
final class IntReader {
public IntReader() {
}
public IntReader(InputStream stream, boolean bigEndian) {
reset(stream, bigEndian);
}
public final void reset(InputStream stream, boolean bigEndian) {
m_stream = stream;
m_bigEndian = bigEndian;
m_position = 0;
}
public final void close() {
if (m_stream == null) {
return;
}
try {
m_stream.close();
} catch (IOException e) {
}
reset(null, false);
}
public final InputStream getStream() {
return m_stream;
}
public final boolean isBigEndian() {
return m_bigEndian;
}
public final void setBigEndian(boolean bigEndian) {
m_bigEndian = bigEndian;
}
public final int readByte() throws IOException {
return readInt(1);
}
public final int readShort() throws IOException {
return readInt(2);
}
public final int readInt() throws IOException {
return readInt(4);
}
public final int readInt(int length) throws IOException {
if (length < 0 || length > 4) {
throw new IllegalArgumentException();
}
int result = 0;
if (m_bigEndian) {
for (int i = (length - 1) * 8; i >= 0; i -= 8) {
int b = m_stream.read();
if (b == -1) {
throw new EOFException();
}
m_position += 1;
result |= (b << i);
}
} else {
length *= 8;
for (int i = 0; i != length; i += 8) {
int b = m_stream.read();
if (b == -1) {
throw new EOFException();
}
m_position += 1;
result |= (b << i);
}
}
return result;
}
public final int[] readIntArray(int length) throws IOException {
int[] array = new int[length];
readIntArray(array, 0, length);
return array;
}
public final void readIntArray(int[] array, int offset, int length)
throws IOException {
for (; length > 0; length -= 1) {
array[offset++] = readInt();
}
}
public final byte[] readByteArray(int length) throws IOException {
byte[] array = new byte[length];
int read = m_stream.read(array);
m_position += read;
if (read != length) {
throw new EOFException();
}
return array;
}
public final void skip(int bytes) throws IOException {
if (bytes <= 0) {
return;
}
long skipped = m_stream.skip(bytes);
m_position += skipped;
if (skipped != bytes) {
throw new EOFException();
}
}
public final void skipInt() throws IOException {
skip(4);
}
public final int available() throws IOException {
return m_stream.available();
}
public final int getPosition() {
return m_position;
}
// ///////////////////////////////// data
private InputStream m_stream;
private boolean m_bigEndian;
private int m_position;
}
// /////////////////////////////////////////// implementation
/**
* Namespace stack, holds prefix+uri pairs, as well as depth information. All
* information is stored in one int[] array. Array consists of depth frames:
* Data=DepthFrame*; DepthFrame=Count+[Prefix+Uri]*+Count; Count='count of
* Prefix+Uri pairs'; Yes, count is stored twice, to enable bottom-up traversal.
* increaseDepth adds depth frame, decreaseDepth removes it. push/pop operations
* operate only in current depth frame. decreaseDepth removes any remaining (not
* pop'ed) namespace pairs. findXXX methods search all depth frames starting
* from the last namespace pair of current depth frame. All functions that
* operate with int, use -1 as 'invalid value'.
*
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
*
*/
final class NamespaceStack {
public NamespaceStack() {
m_data = new int[32];
}
public final void reset() {
m_dataLength = 0;
m_count = 0;
m_depth = 0;
}
@SuppressWarnings("unused")
public final int getTotalCount() {
return m_count;
}
public final int getCurrentCount() {
if (m_dataLength == 0) {
return 0;
}
int offset = m_dataLength - 1;
return m_data[offset];
}
public final int getAccumulatedCount(int depth) {
if (m_dataLength == 0 || depth < 0) {
return 0;
}
if (depth > m_depth) {
depth = m_depth;
}
int accumulatedCount = 0;
int offset = 0;
for (; depth != 0; --depth) {
int count = m_data[offset];
accumulatedCount += count;
offset += (2 + count * 2);
}
return accumulatedCount;
}
public final void push(int prefix, int uri) {
if (m_depth == 0) {
increaseDepth();
}
ensureDataCapacity(2);
int offset = m_dataLength - 1;
int count = m_data[offset];
m_data[offset - 1 - count * 2] = count + 1;
m_data[offset] = prefix;
m_data[offset + 1] = uri;
m_data[offset + 2] = count + 1;
m_dataLength += 2;
m_count += 1;
}
@SuppressWarnings("unused")
public final boolean pop(int prefix, int uri) {
if (m_dataLength == 0) {
return false;
}
int offset = m_dataLength - 1;
int count = m_data[offset];
for (int i = 0, o = offset - 2; i != count; ++i, o -= 2) {
if (m_data[o] != prefix || m_data[o + 1] != uri) {
continue;
}
count -= 1;
if (i == 0) {
m_data[o] = count;
o -= (1 + count * 2);
m_data[o] = count;
} else {
m_data[offset] = count;
offset -= (1 + 2 + count * 2);
m_data[offset] = count;
System.arraycopy(m_data, o + 2, m_data, o, m_dataLength - o);
}
m_dataLength -= 2;
m_count -= 1;
return true;
}
return false;
}
public final boolean pop() {
if (m_dataLength == 0) {
return false;
}
int offset = m_dataLength - 1;
int count = m_data[offset];
if (count == 0) {
return false;
}
count -= 1;
offset -= 2;
m_data[offset] = count;
offset -= (1 + count * 2);
m_data[offset] = count;
m_dataLength -= 2;
m_count -= 1;
return true;
}
public final int getPrefix(int index) {
return get(index, true);
}
public final int getUri(int index) {
return get(index, false);
}
public final int findPrefix(int uri) {
return find(uri, false);
}
@SuppressWarnings("unused")
public final int findUri(int prefix) {
return find(prefix, true);
}
public final int getDepth() {
return m_depth;
}
public final void increaseDepth() {
ensureDataCapacity(2);
int offset = m_dataLength;
m_data[offset] = 0;
m_data[offset + 1] = 0;
m_dataLength += 2;
m_depth += 1;
}
public final void decreaseDepth() {
if (m_dataLength == 0) {
return;
}
int offset = m_dataLength - 1;
int count = m_data[offset];
if ((offset - 1 - count * 2) == 0) {
return;
}
m_dataLength -= 2 + count * 2;
m_count -= count;
m_depth -= 1;
}
private void ensureDataCapacity(int capacity) {
int available = (m_data.length - m_dataLength);
if (available > capacity) {
return;
}
int newLength = (m_data.length + available) * 2;
int[] newData = new int[newLength];
System.arraycopy(m_data, 0, newData, 0, m_dataLength);
m_data = newData;
}
private final int find(int prefixOrUri, boolean prefix) {
if (m_dataLength == 0) {
return -1;
}
int offset = m_dataLength - 1;
for (int i = m_depth; i != 0; --i) {
int count = m_data[offset];
offset -= 2;
for (; count != 0; --count) {
if (prefix) {
if (m_data[offset] == prefixOrUri) {
return m_data[offset + 1];
}
} else {
if (m_data[offset + 1] == prefixOrUri) {
return m_data[offset];
}
}
offset -= 2;
}
}
return -1;
}
private final int get(int index, boolean prefix) {
if (m_dataLength == 0 || index < 0) {
return -1;
}
int offset = 0;
for (int i = m_depth; i != 0; --i) {
int count = m_data[offset];
if (index >= count) {
index -= count;
offset += (2 + count * 2);
continue;
}
offset += (1 + index * 2);
if (!prefix) {
offset += 1;
}
return m_data[offset];
}
return -1;
}
private int[] m_data;
private int m_dataLength;
private int m_count;
private int m_depth;
}
/**
* @author Dmitry Skiba
*
* Block of strings, used in binary xml and arsc.
*
* TODO: - implement get()
*
*/
class StringBlock {
/**
* Reads whole (including chunk type) string block from stream. Stream must
* be at the chunk type.
*/
public static StringBlock read(IntReader reader) throws IOException {
XmlResourceParser.readCheckType(reader, CHUNK_TYPE);
int chunkSize = reader.readInt();
int stringCount = reader.readInt();
int styleOffsetCount = reader.readInt();
/* ? */reader.readInt();
int stringsOffset = reader.readInt();
int stylesOffset = reader.readInt();
StringBlock block = new StringBlock();
block.m_stringOffsets = reader.readIntArray(stringCount);
if (styleOffsetCount != 0) {
block.m_styleOffsets = reader.readIntArray(styleOffsetCount);
}
{
int size = ((stylesOffset == 0) ? chunkSize : stylesOffset)
- stringsOffset;
if ((size % 4) != 0) {
throw new IOException("String data size is not multiple of 4 ("
+ size + ").");
}
block.m_strings = reader.readIntArray(size / 4);
}
if (stylesOffset != 0) {
int size = (chunkSize - stylesOffset);
if ((size % 4) != 0) {
throw new IOException("Style data size is not multiple of 4 ("
+ size + ").");
}
block.m_styles = reader.readIntArray(size / 4);
}
return block;
}
/**
* Returns number of strings in block.
*/
public int getCount() {
return m_stringOffsets != null ? m_stringOffsets.length : 0;
}
/**
* Returns raw string (without any styling information) at specified index.
*/
public String getString(int index) {
if (index < 0 || m_stringOffsets == null
|| index >= m_stringOffsets.length) {
return null;
}
int offset = m_stringOffsets[index];
int length = getShort(m_strings, offset);
StringBuilder result = new StringBuilder(length);
for (; length != 0; length -= 1) {
offset += 2;
result.append((char) getShort(m_strings, offset));
}
return result.toString();
}
/**
* Not yet implemented.
*
* Returns string with style information (if any).
*/
public CharSequence get(int index) {
return getString(index);
}
/**
* Returns string with style tags (html-like).
*/
public String getHTML(int index) {
String raw = getString(index);
if (raw == null) {
return raw;
}
int[] style = getStyle(index);
if (style == null) {
return raw;
}
StringBuilder html = new StringBuilder(raw.length() + 32);
int offset = 0;
while (true) {
int i = -1;
for (int j = 0; j != style.length; j += 3) {
if (style[j + 1] == -1) {
continue;
}
if (i == -1 || style[i + 1] > style[j + 1]) {
i = j;
}
}
int start = ((i != -1) ? style[i + 1] : raw.length());
for (int j = 0; j != style.length; j += 3) {
int end = style[j + 2];
if (end == -1 || end >= start) {
continue;
}
if (offset <= end) {
html.append(raw, offset, end + 1);
offset = end + 1;
}
style[j + 2] = -1;
html.append('<');
html.append('/');
html.append(getString(style[j]));
html.append('>');
}
if (offset < start) {
html.append(raw, offset, start);
offset = start;
}
if (i == -1) {
break;
}
html.append('<');
html.append(getString(style[i]));
html.append('>');
style[i + 1] = -1;
}
return html.toString();
}
/**
* Finds index of the string. Returns -1 if the string was not found.
*/
public int find(String string) {
if (string == null) {
return -1;
}
for (int i = 0; i != m_stringOffsets.length; ++i) {
int offset = m_stringOffsets[i];
int length = getShort(m_strings, offset);
if (length != string.length()) {
continue;
}
int j = 0;
for (; j != length; ++j) {
offset += 2;
if (string.charAt(j) != getShort(m_strings, offset)) {
break;
}
}
if (j == length) {
return i;
}
}
return -1;
}
// /////////////////////////////////////////// implementation
private StringBlock() {
}
/**
* Returns style information - array of int triplets, where in each triplet:
* * first int is index of tag name ('b','i', etc.) * second int is tag
* start index in string * third int is tag end index in string
*/
private int[] getStyle(int index) {
if (m_styleOffsets == null || m_styles == null
|| index >= m_styleOffsets.length) {
return null;
}
int offset = m_styleOffsets[index] / 4;
int style[];
{
int count = 0;
for (int i = offset; i < m_styles.length; ++i) {
if (m_styles[i] == -1) {
break;
}
count += 1;
}
if (count == 0 || (count % 3) != 0) {
return null;
}
style = new int[count];
}
for (int i = offset, j = 0; i < m_styles.length;) {
if (m_styles[i] == -1) {
break;
}
style[j++] = m_styles[i++];
}
return style;
}
private static final int getShort(int[] array, int offset) {
int value = array[offset / 4];
if ((offset % 4) / 2 == 0) {
return (value & 0xFFFF);
} else {
return (value >>> 16);
}
}
private int[] m_stringOffsets;
private int[] m_strings;
private int[] m_styleOffsets;
private int[] m_styles;
private static final int CHUNK_TYPE = 0x001C0001;
}