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