/* * 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; } }