XmlBlock.java revision d4a4729c0cac582a2dcec7c8cfb316b81885a0f0
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content.res;
18
19import android.util.TypedValue;
20import com.android.common.XmlUtils;
21
22import org.xmlpull.v1.XmlPullParserException;
23
24import java.io.IOException;
25import java.io.InputStream;
26import java.io.Reader;
27
28/**
29 * Wrapper around a compiled XML file.
30 *
31 * {@hide}
32 */
33final class XmlBlock {
34    private static final boolean DEBUG=false;
35
36    public XmlBlock(byte[] data) {
37        mAssets = null;
38        mNative = nativeCreate(data, 0, data.length);
39        mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
40    }
41
42    public XmlBlock(byte[] data, int offset, int size) {
43        mAssets = null;
44        mNative = nativeCreate(data, offset, size);
45        mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
46    }
47
48    public void close() {
49        synchronized (this) {
50            if (mOpen) {
51                mOpen = false;
52                decOpenCountLocked();
53            }
54        }
55    }
56
57    private void decOpenCountLocked() {
58        mOpenCount--;
59        if (mOpenCount == 0) {
60            nativeDestroy(mNative);
61            if (mAssets != null) {
62                mAssets.xmlBlockGone();
63            }
64        }
65    }
66
67    public XmlResourceParser newParser() {
68        synchronized (this) {
69            if (mNative != 0) {
70                return new Parser(nativeCreateParseState(mNative), this);
71            }
72            return null;
73        }
74    }
75
76    /*package*/ final class Parser implements XmlResourceParser {
77        Parser(int parseState, XmlBlock block) {
78            mParseState = parseState;
79            mBlock = block;
80            block.mOpenCount++;
81        }
82
83        public void setFeature(String name, boolean state) throws XmlPullParserException {
84            if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
85                return;
86            }
87            if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
88                return;
89            }
90            throw new XmlPullParserException("Unsupported feature: " + name);
91        }
92        public boolean getFeature(String name) {
93            if (FEATURE_PROCESS_NAMESPACES.equals(name)) {
94                return true;
95            }
96            if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) {
97                return true;
98            }
99            return false;
100        }
101        public void setProperty(String name, Object value) throws XmlPullParserException {
102            throw new XmlPullParserException("setProperty() not supported");
103        }
104        public Object getProperty(String name) {
105            return null;
106        }
107        public void setInput(Reader in) throws XmlPullParserException {
108            throw new XmlPullParserException("setInput() not supported");
109        }
110        public void setInput(InputStream inputStream, String inputEncoding) throws XmlPullParserException {
111            throw new XmlPullParserException("setInput() not supported");
112        }
113        public void defineEntityReplacementText(String entityName, String replacementText) throws XmlPullParserException {
114            throw new XmlPullParserException("defineEntityReplacementText() not supported");
115        }
116        public String getNamespacePrefix(int pos) throws XmlPullParserException {
117            throw new XmlPullParserException("getNamespacePrefix() not supported");
118        }
119        public String getInputEncoding() {
120            return null;
121        }
122        public String getNamespace(String prefix) {
123            throw new RuntimeException("getNamespace() not supported");
124        }
125        public int getNamespaceCount(int depth) throws XmlPullParserException {
126            throw new XmlPullParserException("getNamespaceCount() not supported");
127        }
128        public String getPositionDescription() {
129            return "Binary XML file line #" + getLineNumber();
130        }
131        public String getNamespaceUri(int pos) throws XmlPullParserException {
132            throw new XmlPullParserException("getNamespaceUri() not supported");
133        }
134        public int getColumnNumber() {
135            return -1;
136        }
137        public int getDepth() {
138            return mDepth;
139        }
140        public String getText() {
141            int id = nativeGetText(mParseState);
142            return id >= 0 ? mStrings.get(id).toString() : null;
143        }
144        public int getLineNumber() {
145            return nativeGetLineNumber(mParseState);
146        }
147        public int getEventType() throws XmlPullParserException {
148            return mEventType;
149        }
150        public boolean isWhitespace() throws XmlPullParserException {
151            // whitespace was stripped by aapt.
152            return false;
153        }
154        public String getPrefix() {
155            throw new RuntimeException("getPrefix not supported");
156        }
157        public char[] getTextCharacters(int[] holderForStartAndLength) {
158            String txt = getText();
159            char[] chars = null;
160            if (txt != null) {
161                holderForStartAndLength[0] = 0;
162                holderForStartAndLength[1] = txt.length();
163                chars = new char[txt.length()];
164                txt.getChars(0, txt.length(), chars, 0);
165            }
166            return chars;
167        }
168        public String getNamespace() {
169            int id = nativeGetNamespace(mParseState);
170            return id >= 0 ? mStrings.get(id).toString() : "";
171        }
172        public String getName() {
173            int id = nativeGetName(mParseState);
174            return id >= 0 ? mStrings.get(id).toString() : null;
175        }
176        public String getAttributeNamespace(int index) {
177            int id = nativeGetAttributeNamespace(mParseState, index);
178            if (DEBUG) System.out.println("getAttributeNamespace of " + index + " = " + id);
179            if (id >= 0) return mStrings.get(id).toString();
180            else if (id == -1) return "";
181            throw new IndexOutOfBoundsException(String.valueOf(index));
182        }
183        public String getAttributeName(int index) {
184            int id = nativeGetAttributeName(mParseState, index);
185            if (DEBUG) System.out.println("getAttributeName of " + index + " = " + id);
186            if (id >= 0) return mStrings.get(id).toString();
187            throw new IndexOutOfBoundsException(String.valueOf(index));
188        }
189        public String getAttributePrefix(int index) {
190            throw new RuntimeException("getAttributePrefix not supported");
191        }
192        public boolean isEmptyElementTag() throws XmlPullParserException {
193            // XXX Need to detect this.
194            return false;
195        }
196        public int getAttributeCount() {
197            return mEventType == START_TAG ? nativeGetAttributeCount(mParseState) : -1;
198        }
199        public String getAttributeValue(int index) {
200            int id = nativeGetAttributeStringValue(mParseState, index);
201            if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id);
202            if (id >= 0) return mStrings.get(id).toString();
203
204            // May be some other type...  check and try to convert if so.
205            int t = nativeGetAttributeDataType(mParseState, index);
206            if (t == TypedValue.TYPE_NULL) {
207                throw new IndexOutOfBoundsException(String.valueOf(index));
208            }
209
210            int v = nativeGetAttributeData(mParseState, index);
211            return TypedValue.coerceToString(t, v);
212        }
213        public String getAttributeType(int index) {
214            return "CDATA";
215        }
216        public boolean isAttributeDefault(int index) {
217            return false;
218        }
219        public int nextToken() throws XmlPullParserException,IOException {
220            return next();
221        }
222        public String getAttributeValue(String namespace, String name) {
223            int idx = nativeGetAttributeIndex(mParseState, namespace, name);
224            if (idx >= 0) {
225                if (DEBUG) System.out.println("getAttributeName of "
226                        + namespace + ":" + name + " index = " + idx);
227                if (DEBUG) System.out.println(
228                        "Namespace=" + getAttributeNamespace(idx)
229                        + "Name=" + getAttributeName(idx)
230                        + ", Value=" + getAttributeValue(idx));
231                return getAttributeValue(idx);
232            }
233            return null;
234        }
235        public int next() throws XmlPullParserException,IOException {
236            if (!mStarted) {
237                mStarted = true;
238                return START_DOCUMENT;
239            }
240            if (mParseState == 0) {
241                return END_DOCUMENT;
242            }
243            int ev = nativeNext(mParseState);
244            if (mDecNextDepth) {
245                mDepth--;
246                mDecNextDepth = false;
247            }
248            switch (ev) {
249            case START_TAG:
250                mDepth++;
251                break;
252            case END_TAG:
253                mDecNextDepth = true;
254                break;
255            }
256            mEventType = ev;
257            if (ev == END_DOCUMENT) {
258                // Automatically close the parse when we reach the end of
259                // a document, since the standard XmlPullParser interface
260                // doesn't have such an API so most clients will leave us
261                // dangling.
262                close();
263            }
264            return ev;
265        }
266        public void require(int type, String namespace, String name) throws XmlPullParserException,IOException {
267            if (type != getEventType()
268                || (namespace != null && !namespace.equals( getNamespace () ) )
269                || (name != null && !name.equals( getName() ) ) )
270                throw new XmlPullParserException( "expected "+ TYPES[ type ]+getPositionDescription());
271        }
272        public String nextText() throws XmlPullParserException,IOException {
273            if(getEventType() != START_TAG) {
274               throw new XmlPullParserException(
275                 getPositionDescription()
276                 + ": parser must be on START_TAG to read next text", this, null);
277            }
278            int eventType = next();
279            if(eventType == TEXT) {
280               String result = getText();
281               eventType = next();
282               if(eventType != END_TAG) {
283                 throw new XmlPullParserException(
284                    getPositionDescription()
285                    + ": event TEXT it must be immediately followed by END_TAG", this, null);
286                }
287                return result;
288            } else if(eventType == END_TAG) {
289               return "";
290            } else {
291               throw new XmlPullParserException(
292                 getPositionDescription()
293                 + ": parser must be on START_TAG or TEXT to read text", this, null);
294            }
295        }
296        public int nextTag() throws XmlPullParserException,IOException {
297            int eventType = next();
298            if(eventType == TEXT && isWhitespace()) {   // skip whitespace
299               eventType = next();
300            }
301            if (eventType != START_TAG && eventType != END_TAG) {
302               throw new XmlPullParserException(
303                   getPositionDescription()
304                   + ": expected start or end tag", this, null);
305            }
306            return eventType;
307        }
308
309        public int getAttributeNameResource(int index) {
310            return nativeGetAttributeResource(mParseState, index);
311        }
312
313        public int getAttributeListValue(String namespace, String attribute,
314                String[] options, int defaultValue) {
315            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
316            if (idx >= 0) {
317                return getAttributeListValue(idx, options, defaultValue);
318            }
319            return defaultValue;
320        }
321        public boolean getAttributeBooleanValue(String namespace, String attribute,
322                boolean defaultValue) {
323            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
324            if (idx >= 0) {
325                return getAttributeBooleanValue(idx, defaultValue);
326            }
327            return defaultValue;
328        }
329        public int getAttributeResourceValue(String namespace, String attribute,
330                int defaultValue) {
331            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
332            if (idx >= 0) {
333                return getAttributeResourceValue(idx, defaultValue);
334            }
335            return defaultValue;
336        }
337        public int getAttributeIntValue(String namespace, String attribute,
338                int defaultValue) {
339            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
340            if (idx >= 0) {
341                return getAttributeIntValue(idx, defaultValue);
342            }
343            return defaultValue;
344        }
345        public int getAttributeUnsignedIntValue(String namespace, String attribute,
346                                                int defaultValue)
347        {
348            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
349            if (idx >= 0) {
350                return getAttributeUnsignedIntValue(idx, defaultValue);
351            }
352            return defaultValue;
353        }
354        public float getAttributeFloatValue(String namespace, String attribute,
355                float defaultValue) {
356            int idx = nativeGetAttributeIndex(mParseState, namespace, attribute);
357            if (idx >= 0) {
358                return getAttributeFloatValue(idx, defaultValue);
359            }
360            return defaultValue;
361        }
362
363        public int getAttributeListValue(int idx,
364                String[] options, int defaultValue) {
365            int t = nativeGetAttributeDataType(mParseState, idx);
366            int v = nativeGetAttributeData(mParseState, idx);
367            if (t == TypedValue.TYPE_STRING) {
368                return XmlUtils.convertValueToList(
369                    mStrings.get(v), options, defaultValue);
370            }
371            return v;
372        }
373        public boolean getAttributeBooleanValue(int idx,
374                boolean defaultValue) {
375            int t = nativeGetAttributeDataType(mParseState, idx);
376            // Note: don't attempt to convert any other types, because
377            // we want to count on appt doing the conversion for us.
378            if (t >= TypedValue.TYPE_FIRST_INT &&
379                t <= TypedValue.TYPE_LAST_INT) {
380                return nativeGetAttributeData(mParseState, idx) != 0;
381            }
382            return defaultValue;
383        }
384        public int getAttributeResourceValue(int idx, int defaultValue) {
385            int t = nativeGetAttributeDataType(mParseState, idx);
386            // Note: don't attempt to convert any other types, because
387            // we want to count on appt doing the conversion for us.
388            if (t == TypedValue.TYPE_REFERENCE) {
389                return nativeGetAttributeData(mParseState, idx);
390            }
391            return defaultValue;
392        }
393        public int getAttributeIntValue(int idx, int defaultValue) {
394            int t = nativeGetAttributeDataType(mParseState, idx);
395            // Note: don't attempt to convert any other types, because
396            // we want to count on appt doing the conversion for us.
397            if (t >= TypedValue.TYPE_FIRST_INT &&
398                t <= TypedValue.TYPE_LAST_INT) {
399                return nativeGetAttributeData(mParseState, idx);
400            }
401            return defaultValue;
402        }
403        public int getAttributeUnsignedIntValue(int idx, int defaultValue) {
404            int t = nativeGetAttributeDataType(mParseState, idx);
405            // Note: don't attempt to convert any other types, because
406            // we want to count on appt doing the conversion for us.
407            if (t >= TypedValue.TYPE_FIRST_INT &&
408                t <= TypedValue.TYPE_LAST_INT) {
409                return nativeGetAttributeData(mParseState, idx);
410            }
411            return defaultValue;
412        }
413        public float getAttributeFloatValue(int idx, float defaultValue) {
414            int t = nativeGetAttributeDataType(mParseState, idx);
415            // Note: don't attempt to convert any other types, because
416            // we want to count on appt doing the conversion for us.
417            if (t == TypedValue.TYPE_FLOAT) {
418                return Float.intBitsToFloat(
419                    nativeGetAttributeData(mParseState, idx));
420            }
421            throw new RuntimeException("not a float!");
422        }
423
424        public String getIdAttribute() {
425            int id = nativeGetIdAttribute(mParseState);
426            return id >= 0 ? mStrings.get(id).toString() : null;
427        }
428        public String getClassAttribute() {
429            int id = nativeGetClassAttribute(mParseState);
430            return id >= 0 ? mStrings.get(id).toString() : null;
431        }
432
433        public int getIdAttributeResourceValue(int defaultValue) {
434            //todo: create and use native method
435            return getAttributeResourceValue(null, "id", defaultValue);
436        }
437
438        public int getStyleAttribute() {
439            return nativeGetStyleAttribute(mParseState);
440        }
441
442        public void close() {
443            synchronized (mBlock) {
444                if (mParseState != 0) {
445                    nativeDestroyParseState(mParseState);
446                    mParseState = 0;
447                    mBlock.decOpenCountLocked();
448                }
449            }
450        }
451
452        protected void finalize() throws Throwable {
453            close();
454        }
455
456        /*package*/ final CharSequence getPooledString(int id) {
457            return mStrings.get(id);
458        }
459
460        /*package*/ int mParseState;
461        private final XmlBlock mBlock;
462        private boolean mStarted = false;
463        private boolean mDecNextDepth = false;
464        private int mDepth = 0;
465        private int mEventType = START_DOCUMENT;
466    }
467
468    protected void finalize() throws Throwable {
469        close();
470    }
471
472    /**
473     * Create from an existing xml block native object.  This is
474     * -extremely- dangerous -- only use it if you absolutely know what you
475     *  are doing!  The given native object must exist for the entire lifetime
476     *  of this newly creating XmlBlock.
477     */
478    XmlBlock(AssetManager assets, int xmlBlock) {
479        mAssets = assets;
480        mNative = xmlBlock;
481        mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
482    }
483
484    private final AssetManager mAssets;
485    private final int mNative;
486    private final StringBlock mStrings;
487    private boolean mOpen = true;
488    private int mOpenCount = 1;
489
490    private static final native int nativeCreate(byte[] data,
491                                                 int offset,
492                                                 int size);
493    private static final native int nativeGetStringBlock(int obj);
494
495    private static final native int nativeCreateParseState(int obj);
496    private static final native int nativeNext(int state);
497    private static final native int nativeGetNamespace(int state);
498    private static final native int nativeGetName(int state);
499    private static final native int nativeGetText(int state);
500    private static final native int nativeGetLineNumber(int state);
501    private static final native int nativeGetAttributeCount(int state);
502    private static final native int nativeGetAttributeNamespace(int state, int idx);
503    private static final native int nativeGetAttributeName(int state, int idx);
504    private static final native int nativeGetAttributeResource(int state, int idx);
505    private static final native int nativeGetAttributeDataType(int state, int idx);
506    private static final native int nativeGetAttributeData(int state, int idx);
507    private static final native int nativeGetAttributeStringValue(int state, int idx);
508    private static final native int nativeGetIdAttribute(int state);
509    private static final native int nativeGetClassAttribute(int state);
510    private static final native int nativeGetStyleAttribute(int state);
511    private static final native int nativeGetAttributeIndex(int state, String namespace, String name);
512    private static final native void nativeDestroyParseState(int state);
513
514    private static final native void nativeDestroy(int obj);
515}
516