• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 
17 package com.android.layoutlib.bridge.impl;
18 
19 import org.xmlpull.v1.XmlPullParser;
20 import org.xmlpull.v1.XmlPullParserException;
21 
22 import android.annotation.Nullable;
23 
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.Reader;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 
31 /**
32  * A wrapper around XmlPullParser that can peek forward to inspect if the file is a data-binding
33  * layout and some parts need to be stripped.
34  */
35 public class LayoutParserWrapper implements XmlPullParser {
36 
37     // Data binding constants.
38     private static final String TAG_LAYOUT = "layout";
39     private static final String TAG_DATA = "data";
40     private static final String DEFAULT = "default=";
41 
42     private final XmlPullParser mDelegate;
43 
44     // Storage for peeked values.
45     private boolean mPeeked;
46     private int mEventType;
47     private int mDepth;
48     private int mNext;
49     private List<Attribute> mAttributes;
50     private String mText;
51     private String mName;
52     private boolean mIsWhitespace;
53 
54     // Used to end the document before the actual parser ends.
55     private int mFinalDepth = -1;
56     private boolean mEndNow;
57 
LayoutParserWrapper(XmlPullParser delegate)58     public LayoutParserWrapper(XmlPullParser delegate) {
59         mDelegate = delegate;
60     }
61 
peekTillLayoutStart()62     public LayoutParserWrapper peekTillLayoutStart() throws IOException, XmlPullParserException {
63         final int STATE_LAYOUT_NOT_STARTED = 0;  // <layout> tag not encountered yet.
64         final int STATE_ROOT_NOT_STARTED = 1;    // the main view root not found yet.
65         final int STATE_INSIDE_DATA = 2;         // START_TAG for <data> found, but not END_TAG.
66 
67         int state = STATE_LAYOUT_NOT_STARTED;
68         int dataDepth = -1;    // depth of the <data> tag. Should be two.
69         while (true) {
70             int peekNext = peekNext();
71             switch (peekNext) {
72                 case START_TAG:
73                     if (state == STATE_LAYOUT_NOT_STARTED) {
74                         if (mName.equals(TAG_LAYOUT)) {
75                             state = STATE_ROOT_NOT_STARTED;
76                         } else {
77                             return this; // no layout tag in the file.
78                         }
79                     } else if (state == STATE_ROOT_NOT_STARTED) {
80                         if (mName.equals(TAG_DATA)) {
81                             state = STATE_INSIDE_DATA;
82                             dataDepth = mDepth;
83                         } else {
84                             mFinalDepth = mDepth;
85                             return this;
86                         }
87                     }
88                     break;
89                 case END_TAG:
90                     if (state == STATE_INSIDE_DATA) {
91                         if (mDepth <= dataDepth) {
92                             state = STATE_ROOT_NOT_STARTED;
93                         }
94                     }
95                     break;
96                 case END_DOCUMENT:
97                     // No layout start found.
98                     return this;
99             }
100             // consume the peeked tag.
101             next();
102         }
103     }
104 
peekNext()105     private int peekNext() throws IOException, XmlPullParserException {
106         if (mPeeked) {
107             return mNext;
108         }
109         mEventType = mDelegate.getEventType();
110         mNext = mDelegate.next();
111         if (mEventType == START_TAG) {
112             int count = mDelegate.getAttributeCount();
113             mAttributes = count > 0 ? new ArrayList<Attribute>(count) :
114                     Collections.<Attribute>emptyList();
115             for (int i = 0; i < count; i++) {
116                 mAttributes.add(new Attribute(mDelegate.getAttributeNamespace(i),
117                         mDelegate.getAttributeName(i), mDelegate.getAttributeValue(i)));
118             }
119         }
120         mDepth = mDelegate.getDepth();
121         mText = mDelegate.getText();
122         mName = mDelegate.getName();
123         mIsWhitespace = mNext == TEXT && mDelegate.isWhitespace();
124         mPeeked = true;
125         return mNext;
126     }
127 
reset()128     private void reset() {
129         mAttributes = null;
130         mText = null;
131         mName = null;
132         mPeeked = false;
133     }
134 
135     @Override
next()136     public int next() throws XmlPullParserException, IOException {
137         int returnValue;
138         int depth;
139         if (mPeeked) {
140             returnValue = mNext;
141             depth = mDepth;
142             reset();
143         } else if (mEndNow) {
144             return END_DOCUMENT;
145         } else {
146             returnValue = mDelegate.next();
147             depth = getDepth();
148         }
149         if (returnValue == END_TAG && depth <= mFinalDepth) {
150             mEndNow = true;
151         }
152         return returnValue;
153     }
154 
155     @Override
getEventType()156     public int getEventType() throws XmlPullParserException {
157         return mPeeked ? mEventType : mDelegate.getEventType();
158     }
159 
160     @Override
getDepth()161     public int getDepth() {
162         return mPeeked ? mDepth : mDelegate.getDepth();
163     }
164 
165     @Override
getName()166     public String getName() {
167         return mPeeked ? mName : mDelegate.getName();
168     }
169 
170     @Override
getText()171     public String getText() {
172         return mPeeked ? mText : mDelegate.getText();
173     }
174 
175     @Override
getAttributeValue(@ullable String namespace, String name)176     public String getAttributeValue(@Nullable String namespace, String name) {
177         String returnValue = null;
178         if (mPeeked) {
179             if (mAttributes == null) {
180                 if (mEventType != START_TAG) {
181                     throw new IndexOutOfBoundsException("getAttributeValue() called when not at START_TAG.");
182                 } else {
183                     return null;
184                 }
185             } else {
186                 for (Attribute attribute : mAttributes) {
187                     //noinspection StringEquality for nullness check.
188                     if (attribute.name.equals(name) && (attribute.namespace == namespace ||
189                             attribute.namespace != null && attribute.namespace.equals(namespace))) {
190                         returnValue = attribute.value;
191                         break;
192                     }
193                 }
194             }
195         } else {
196             returnValue = mDelegate.getAttributeValue(namespace, name);
197         }
198         // Check if the value is bound via data-binding, if yes get the default value.
199         if (returnValue != null && mFinalDepth >= 0 && returnValue.startsWith("@{")) {
200             // TODO: Improve the detection of default keyword.
201             int i = returnValue.lastIndexOf(DEFAULT);
202             return i > 0 ? returnValue.substring(i + DEFAULT.length(), returnValue.length() - 1)
203                     : null;
204         }
205         return returnValue;
206     }
207 
208     @Override
isWhitespace()209     public boolean isWhitespace() throws XmlPullParserException {
210         return mPeeked ? mIsWhitespace : mDelegate.isWhitespace();
211     }
212 
213     private static class Attribute {
214         @Nullable
215         public final String namespace;
216         public final String name;
217         public final String value;
218 
Attribute(@ullable String namespace, String name, String value)219         public Attribute(@Nullable String namespace, String name, String value) {
220             this.namespace = namespace;
221             this.name = name;
222             this.value = value;
223         }
224     }
225 
226     // Not affected by peeking.
227 
228     @Override
setFeature(String s, boolean b)229     public void setFeature(String s, boolean b) throws XmlPullParserException {
230         mDelegate.setFeature(s, b);
231     }
232 
233     @Override
setProperty(String s, Object o)234     public void setProperty(String s, Object o) throws XmlPullParserException {
235         mDelegate.setProperty(s, o);
236     }
237 
238     @Override
setInput(InputStream inputStream, String s)239     public void setInput(InputStream inputStream, String s) throws XmlPullParserException {
240         mDelegate.setInput(inputStream, s);
241     }
242 
243     @Override
setInput(Reader reader)244     public void setInput(Reader reader) throws XmlPullParserException {
245         mDelegate.setInput(reader);
246     }
247 
248     @Override
getInputEncoding()249     public String getInputEncoding() {
250         return mDelegate.getInputEncoding();
251     }
252 
253     @Override
getNamespace(String s)254     public String getNamespace(String s) {
255         return mDelegate.getNamespace(s);
256     }
257 
258     @Override
getPositionDescription()259     public String getPositionDescription() {
260         return mDelegate.getPositionDescription();
261     }
262 
263     @Override
getLineNumber()264     public int getLineNumber() {
265         return mDelegate.getLineNumber();
266     }
267 
268     @Override
getNamespace()269     public String getNamespace() {
270         return mDelegate.getNamespace();
271     }
272 
273     @Override
getColumnNumber()274     public int getColumnNumber() {
275         return mDelegate.getColumnNumber();
276     }
277 
278     // -- We don't care much about the methods that follow.
279 
280     @Override
require(int i, String s, String s1)281     public void require(int i, String s, String s1) throws XmlPullParserException, IOException {
282         throw new UnsupportedOperationException("Only few parser methods are supported.");
283     }
284 
285     @Override
getFeature(String s)286     public boolean getFeature(String s) {
287         throw new UnsupportedOperationException("Only few parser methods are supported.");
288     }
289 
290     @Override
defineEntityReplacementText(String s, String s1)291     public void defineEntityReplacementText(String s, String s1) throws XmlPullParserException {
292         throw new UnsupportedOperationException("Only few parser methods are supported.");
293     }
294 
295     @Override
getProperty(String s)296     public Object getProperty(String s) {
297         throw new UnsupportedOperationException("Only few parser methods are supported.");
298     }
299 
300     @Override
nextToken()301     public int nextToken() throws XmlPullParserException, IOException {
302         throw new UnsupportedOperationException("Only few parser methods are supported.");
303     }
304 
305     @Override
getNamespaceCount(int i)306     public int getNamespaceCount(int i) throws XmlPullParserException {
307         throw new UnsupportedOperationException("Only few parser methods are supported.");
308     }
309 
310     @Override
getNamespacePrefix(int i)311     public String getNamespacePrefix(int i) throws XmlPullParserException {
312         throw new UnsupportedOperationException("Only few parser methods are supported.");
313     }
314 
315     @Override
getNamespaceUri(int i)316     public String getNamespaceUri(int i) throws XmlPullParserException {
317         throw new UnsupportedOperationException("Only few parser methods are supported.");
318     }
319 
320     @Override
getTextCharacters(int[] ints)321     public char[] getTextCharacters(int[] ints) {
322         throw new UnsupportedOperationException("Only few parser methods are supported.");
323     }
324 
325     @Override
getPrefix()326     public String getPrefix() {
327         throw new UnsupportedOperationException("Only few parser methods are supported.");
328     }
329 
330     @Override
isEmptyElementTag()331     public boolean isEmptyElementTag() throws XmlPullParserException {
332         throw new UnsupportedOperationException("Only few parser methods are supported.");
333     }
334 
335     @Override
getAttributeCount()336     public int getAttributeCount() {
337         throw new UnsupportedOperationException("Only few parser methods are supported.");
338     }
339 
340     @Override
getAttributeNamespace(int i)341     public String getAttributeNamespace(int i) {
342         throw new UnsupportedOperationException("Only few parser methods are supported.");
343     }
344 
345     @Override
getAttributeName(int i)346     public String getAttributeName(int i) {
347         throw new UnsupportedOperationException("Only few parser methods are supported.");
348     }
349 
350     @Override
getAttributePrefix(int i)351     public String getAttributePrefix(int i) {
352         throw new UnsupportedOperationException("Only few parser methods are supported.");
353     }
354 
355     @Override
getAttributeType(int i)356     public String getAttributeType(int i) {
357         throw new UnsupportedOperationException("Only few parser methods are supported.");
358     }
359 
360     @Override
isAttributeDefault(int i)361     public boolean isAttributeDefault(int i) {
362         throw new UnsupportedOperationException("Only few parser methods are supported.");
363     }
364 
365     @Override
getAttributeValue(int i)366     public String getAttributeValue(int i) {
367         throw new UnsupportedOperationException("Only few parser methods are supported.");
368     }
369 
370     @Override
nextText()371     public String nextText() throws XmlPullParserException, IOException {
372         throw new UnsupportedOperationException("Only few parser methods are supported.");
373     }
374 
375     @Override
nextTag()376     public int nextTag() throws XmlPullParserException, IOException {
377         throw new UnsupportedOperationException("Only few parser methods are supported.");
378     }
379 }
380