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