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