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