/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content.res;
import android.util.Errors;
import android.util.TypedValue;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import com.android.internal.util.XmlUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
/**
* Wrapper around a compiled XML file.
*
*/
public final class XmlBlock {
private static final boolean DEBUG = false;
/**
* Create from an existing xml block native object. This is
* -extremely- dangerous -- only use it if you absolutely know what you
* are doing! The given native object must exist for the entire lifetime
* of this newly creating XmlBlock.
*/
public XmlBlock(AssetManager assets, ResXMLTree xmlBlock) {
mAssets = assets;
mNative = xmlBlock;
mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
}
public XmlBlock(byte[] data) {
mAssets = null;
mNative = nativeCreate(data, 0, data.length);
mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
}
public XmlBlock(byte[] data, int offset, int size) {
mAssets = null;
mNative = nativeCreate(data, offset, size);
mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
}
public void close() {
if (mOpen) {
mOpen = false;
decOpenCountLocked();
}
}
private void decOpenCountLocked() {
mOpenCount--;
if (mOpenCount == 0) {
nativeDestroy(mNative);
// if (mAssets != null) {
// mAssets.xmlBlockGone(hashCode());
// }
}
}
public XmlResourceParser newParser() {
if (mNative != null) {
return new Parser(nativeCreateParseState(mNative), this);
}
return null;
}
final class Parser implements XmlResourceParser {
Parser(ResXMLParser parseState, XmlBlock block) {
mParseState = parseState;
mBlock = block;
block.mOpenCount++;
}
public void setFeature(String name, boolean state)
throws XmlPullParserException {
if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
return;
}
if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
return;
}
throw new XmlPullParserException("Unsupported feature: " + name);
}
public boolean getFeature(String name) {
if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
return true;
}
if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
return true;
}
return false;
}
public void setProperty(String name, Object value)
throws XmlPullParserException {
throw new XmlPullParserException("setProperty() not supported");
}
public Object getProperty(String name) {
return null;
}
public void setInput(Reader in) throws XmlPullParserException {
throw new XmlPullParserException("setInput() not supported");
}
public void setInput(InputStream inputStream, String inputEncoding)
throws XmlPullParserException {
throw new XmlPullParserException("setInput() not supported");
}
public void defineEntityReplacementText(String entityName,
String replacementText) throws XmlPullParserException {
throw new XmlPullParserException(
"defineEntityReplacementText() not supported");
}
public String getNamespacePrefix(int pos) throws XmlPullParserException {
throw new XmlPullParserException(
"getNamespacePrefix() not supported");
}
public String getInputEncoding() {
return null;
}
public String getNamespace(String prefix) {
throw new RuntimeException("getNamespace() not supported");
}
public int getNamespaceCount(int depth) throws XmlPullParserException {
throw new XmlPullParserException(
"getNamespaceCount() not supported");
}
public String getPositionDescription() {
return "Binary XML file line #" + getLineNumber();
}
public String getNamespaceUri(int pos) throws XmlPullParserException {
throw new XmlPullParserException("getNamespaceUri() not supported");
}
public int getColumnNumber() {
return -1;
}
public int getDepth() {
return mDepth;
}
public String getText() {
int id = nativeGetText(mParseState);
return id >= 0 ? mStrings.get(id).toString() : null;
}
public int getLineNumber() {
return nativeGetLineNumber(mParseState);
}
public int getEventType() throws XmlPullParserException {
return mEventType;
}
public boolean isWhitespace() throws XmlPullParserException {
// whitespace was stripped by aapt.
return false;
}
public String getPrefix() {
throw new RuntimeException("getPrefix not supported");
}
public char[] getTextCharacters(int[] holderForStartAndLength) {
String txt = getText();
char[] chars = null;
if (txt != null) {
holderForStartAndLength[0] = 0;
holderForStartAndLength[1] = txt.length();
chars = new char[txt.length()];
txt.getChars(0, txt.length(), chars, 0);
}
return chars;
}
public String getNamespace() {
int id = nativeGetNamespace(mParseState);
return id >= 0 ? mStrings.get(id).toString() : "";
}
public String getName() {
int id = nativeGetName(mParseState);
return id >= 0 ? mStrings.get(id).toString() : null;
}
public String getAttributeNamespace(int index) {
int id = nativeGetAttributeNamespace(mParseState, index);
if (DEBUG)
System.out.println("getAttributeNamespace of " + index + " = "
+ id);
if (id >= 0)
return mStrings.get(id).toString();
else if (id == -1)
return "";
throw new IndexOutOfBoundsException(String.valueOf(index));
}
public String getAttributeName(int index) {
int id = nativeGetAttributeName(mParseState, index);
if (DEBUG)
System.out.println("getAttributeName of " + index + " = " + id);
if (id >= 0)
return mStrings.get(id).toString();
throw new IndexOutOfBoundsException(String.valueOf(index));
}
public String getAttributePrefix(int index) {
throw new RuntimeException("getAttributePrefix not supported");
}
public boolean isEmptyElementTag() throws XmlPullParserException {
// XXX Need to detect this.
return false;
}
public int getAttributeCount() {
return mEventType == START_TAG ? nativeGetAttributeCount(mParseState)
: -1;
}
public String getAttributeValue(int index) {
int id = nativeGetAttributeStringValue(mParseState, index);
if (DEBUG)
System.out
.println("getAttributeValue of " + index + " = " + id);
if (id >= 0)
return mStrings.get(id).toString();
// May be some other type... check and try to convert if so.
int t = nativeGetAttributeDataType(mParseState, index);
if (t == TypedValue.TYPE_NULL) {
throw new IndexOutOfBoundsException(String.valueOf(index));
}
int v = nativeGetAttributeData(mParseState, index);
return TypedValue.coerceToString(t, v);
}
public String getAttributeType(int index) {
return "CDATA";
}
public boolean isAttributeDefault(int index) {
return false;
}
public int nextToken() throws XmlPullParserException, IOException {
return next();
}
public String getAttributeValue(String namespace, String name) {
int idx = nativeGetAttributeIndex(mParseState, namespace, name);
// System.out.println("idx = " + idx);
if (idx >= 0) {
if (DEBUG)
System.out.println("getAttributeName of " + namespace + ":"
+ name + " index = " + idx);
if (DEBUG)
System.out.println("Namespace="
+ getAttributeNamespace(idx) + "Name="
+ getAttributeName(idx) + ", Value="
+ getAttributeValue(idx));
return getAttributeValue(idx);
}
return null;
}
public int next() throws XmlPullParserException, IOException {
if (!mStarted) {
mStarted = true;
return START_DOCUMENT;
}
if (mParseState == null) {
return END_DOCUMENT;
}
int ev = nativeNext(mParseState);
if (mDecNextDepth) {
mDepth--;
mDecNextDepth = false;
}
switch (ev) {
case START_TAG:
mDepth++;
break;
case END_TAG:
mDecNextDepth = true;
break;
}
mEventType = ev;
if (ev == END_DOCUMENT) {
// Automatically close the parse when we reach the end of
// a document, since the standard XmlPullParser interface
// doesn't have such an API so most clients will leave us
// dangling.
close();
}
return ev;
}
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("expected " + TYPES[type]
+ getPositionDescription());
}
public String nextText() throws XmlPullParserException, IOException {
if (getEventType() != START_TAG) {
throw new XmlPullParserException(getPositionDescription()
+ ": 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(
getPositionDescription()
+ ": event TEXT it must be immediately followed by END_TAG",
this, null);
}
return result;
} else if (eventType == END_TAG) {
return "";
} else {
throw new XmlPullParserException(getPositionDescription()
+ ": parser must be on START_TAG or TEXT to read text",
this, null);
}
}
public int nextTag() throws XmlPullParserException, IOException {
int eventType = next();
if (eventType == TEXT && isWhitespace()) { // skip whitespace
eventType = next();
}
if (eventType != START_TAG && eventType != END_TAG) {
throw new XmlPullParserException(getPositionDescription()
+ ": expected start or end tag", this, null);
}
return eventType;
}
public int getAttributeNameResource(int index) {
return nativeGetAttributeResource(mParseState, index);
}
public int getAttributeListValue(String namespace, String attribute,
String[] options, int defaultValue) {
int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
if (idx >= 0) {
return getAttributeListValue(idx, options, defaultValue);
}
return defaultValue;
}
public boolean getAttributeBooleanValue(String namespace,
String attribute, boolean defaultValue) {
int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
if (idx >= 0) {
return getAttributeBooleanValue(idx, defaultValue);
}
return defaultValue;
}
public int getAttributeResourceValue(String namespace,
String attribute, int defaultValue) {
int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
if (idx >= 0) {
return getAttributeResourceValue(idx, defaultValue);
}
return defaultValue;
}
public int getAttributeIntValue(String namespace, String attribute,
int defaultValue) {
int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
if (idx >= 0) {
return getAttributeIntValue(idx, defaultValue);
}
return defaultValue;
}
public int getAttributeUnsignedIntValue(String namespace,
String attribute, int defaultValue) {
int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
if (idx >= 0) {
return getAttributeUnsignedIntValue(idx, defaultValue);
}
return defaultValue;
}
public float getAttributeFloatValue(String namespace, String attribute,
float defaultValue) {
int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
if (idx >= 0) {
return getAttributeFloatValue(idx, defaultValue);
}
return defaultValue;
}
public int getAttributeListValue(int idx, String[] options,
int defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
int v = nativeGetAttributeData(mParseState, idx);
if (t == TypedValue.TYPE_STRING) {
return XmlUtils.convertValueToList(mStrings.get(v), options,
defaultValue);
}
return v;
}
public boolean getAttributeBooleanValue(int idx, boolean defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
// we want to count on appt doing the conversion for us.
if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) {
return nativeGetAttributeData(mParseState, idx) != 0;
}
return defaultValue;
}
public int getAttributeResourceValue(int idx, int defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
// we want to count on appt doing the conversion for us.
if (t == TypedValue.TYPE_REFERENCE) {
return nativeGetAttributeData(mParseState, idx);
}
return defaultValue;
}
public int getAttributeIntValue(int idx, int defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
// we want to count on appt doing the conversion for us.
if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) {
return nativeGetAttributeData(mParseState, idx);
}
return defaultValue;
}
public int getAttributeUnsignedIntValue(int idx, int defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
// we want to count on appt doing the conversion for us.
if (t >= TypedValue.TYPE_FIRST_INT && t <= TypedValue.TYPE_LAST_INT) {
return nativeGetAttributeData(mParseState, idx);
}
return defaultValue;
}
public float getAttributeFloatValue(int idx, float defaultValue) {
int t = nativeGetAttributeDataType(mParseState, idx);
// Note: don't attempt to convert any other types, because
// we want to count on appt doing the conversion for us.
if (t == TypedValue.TYPE_FLOAT) {
return Float.intBitsToFloat(nativeGetAttributeData(mParseState,
idx));
}
throw new RuntimeException("not a float!");
}
public String getIdAttribute() {
int id = nativeGetIdAttribute(mParseState);
return id >= 0 ? mStrings.get(id).toString() : null;
}
public String getClassAttribute() {
int id = nativeGetClassAttribute(mParseState);
return id >= 0 ? mStrings.get(id).toString() : null;
}
public int getIdAttributeResourceValue(int defaultValue) {
//todo: create and use native method
return getAttributeResourceValue(null, "id", defaultValue);
}
public int getStyleAttribute() {
return nativeGetStyleAttribute(mParseState);
}
public void close() {
if (mParseState != null) {
nativeDestroyParseState(mParseState);
mParseState = null;
mBlock.decOpenCountLocked();
}
}
protected void finalize() throws Throwable {
close();
}
/*package*/final CharSequence getPooledString(int id) {
return mStrings.get(id);
}
/*package*/ResXMLParser mParseState;
private final XmlBlock mBlock;
private boolean mStarted = false;
private boolean mDecNextDepth = false;
private int mDepth = 0;
private int mEventType = START_DOCUMENT;
public int getAttributeValueType(int idx) {
return nativeGetAttributeValueType(mParseState, idx);
}
public int getAttributeValueData(int idx) {
return nativeGetAttributeValueData(mParseState, idx);
}
}
protected void finalize() throws Throwable {
close();
}
@SuppressWarnings("unused")
private AssetManager mAssets = null;
private ResXMLTree mNative = null;
private StringBlock mStrings = null;
private boolean mOpen = true;
private int mOpenCount = 1;
private static final ResXMLTree nativeCreate(byte[] data, int offset,
int size) {
ResXMLTree osb = new ResXMLTree(data, offset, size, true);
if (osb == null || osb.getError() != Errors.NO_ERROR) {
return null;
}
return osb;
}
private static final ResStringPool nativeGetStringBlock(ResXMLTree obj) {
if (obj == null)
return null;
return obj.getStrings();
}
private static final ResXMLParser nativeCreateParseState(ResXMLTree obj) {
ResXMLParser st = new ResXMLParser(obj);
st.restart();
return st;
}
private static final int nativeNext(ResXMLParser state) {
if (state == null)
return ResXMLParser.END_DOCUMENT;
do {
int code = state.next();
switch (code) {
case ResXMLParser.START_TAG:
return XmlPullParser.START_TAG;
case ResXMLParser.END_TAG:
return XmlPullParser.END_TAG;
case ResXMLParser.TEXT:
return XmlPullParser.TEXT;
case ResXMLParser.START_DOCUMENT:
return XmlPullParser.START_DOCUMENT;
case ResXMLParser.END_DOCUMENT:
return XmlPullParser.END_DOCUMENT;
case ResXMLParser.BAD_DOCUMENT:
return ResXMLParser.BAD_DOCUMENT;
}
} while (true);
}
private static final int nativeGetNamespace(ResXMLParser state) {
if (state == null)
return -1;
return state.getElementNamespaceID();
}
private static final int nativeGetName(ResXMLParser state) {
if (state == null)
return -1;
return state.getElementNameID();
}
private static final int nativeGetText(ResXMLParser state) {
if (state == null)
return -1;
return state.getTextID();
}
private static final int nativeGetLineNumber(ResXMLParser state) {
if (state == null)
return 0;
return state.getLineNumber();
}
private static final int nativeGetAttributeCount(ResXMLParser state) {
if (state == null)
return 0;
return state.getAttributeCount();
}
private static final int nativeGetAttributeNamespace(ResXMLParser state,
int idx) {
if (state == null)
return 0;
return state.getAttributeNamespaceID(idx);
}
private static final int nativeGetAttributeName(ResXMLParser state, int idx) {
if (state == null)
return 0;
return state.getAttributeNameID(idx);
}
private static final int nativeGetAttributeResource(ResXMLParser state,
int idx) {
if (state == null)
return 0;
return state.getAttributeNameResID(idx);
}
private static final int nativeGetAttributeDataType(ResXMLParser state,
int idx) {
if (state == null)
return 0;
return state.getAttributeDataType(idx);
}
private static final int nativeGetAttributeData(ResXMLParser state, int idx) {
if (state == null)
return 0;
return state.getAttributeData(idx);
}
private static final int nativeGetAttributeStringValue(ResXMLParser state,
int idx) {
if (state == null)
return 0;
return state.getAttributeValueStringID(idx);
}
private static final int nativeGetAttributeIndex(ResXMLParser state,
String namespace, String name) {
if (state == null || name == null)
return 0;
int idx = state.indexOfAttribute(namespace, name);
return idx;
}
private static final int nativeGetIdAttribute(ResXMLParser state) {
if (state == null)
return 0;
int idx = state.indexOfID();
return idx >= 0 ? state.getAttributeValueStringID(idx) : -1;
}
private static final int nativeGetClassAttribute(ResXMLParser state) {
if (state == null)
return 0;
int idx = state.indexOfClass();
return idx >= 0 ? state.getAttributeValueStringID(idx) : -1;
}
private static final int nativeGetStyleAttribute(ResXMLParser state) {
if (state == null)
return 0;
int idx = state.indexOfStyle();
return idx >= 0 ? state.getAttributeValueStringID(idx) : -1;
}
private static final void nativeDestroyParseState(ResXMLParser state) {
state = null;
}
private static final void nativeDestroy(ResXMLTree obj) {
// FIXME can't native destroy an object in java?
obj = null;
}
private static final int nativeGetAttributeValueType(ResXMLParser state,
int idx) {
if (state == null)
return TypedValue.TYPE_NULL;
return idx >= 0 ? state.getAttributeDataType(idx)
: TypedValue.TYPE_NULL;
}
private static final int nativeGetAttributeValueData(ResXMLParser state,
int idx) {
// TODO -1 is a good non-sense value?
if (state == null)
return -1;
return idx >= 0 ? state.getAttributeData(idx) : -1;
}
}