Home | History | Annotate | Download | only in impl
      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 
     57     public LayoutParserWrapper(XmlPullParser delegate) {
     58         mDelegate = delegate;
     59     }
     60 
     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 
    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 
    126     private void reset() {
    127         mAttributes = null;
    128         mText = null;
    129         mName = null;
    130         mPeeked = false;
    131     }
    132 
    133     @Override
    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
    154     public int getEventType() throws XmlPullParserException {
    155         return mPeeked ? mEventType : mDelegate.getEventType();
    156     }
    157 
    158     @Override
    159     public int getDepth() {
    160         return mPeeked ? mDepth : mDelegate.getDepth();
    161     }
    162 
    163     @Override
    164     public String getName() {
    165         return mPeeked ? mName : mDelegate.getName();
    166     }
    167 
    168     @Override
    169     public String getText() {
    170         return mPeeked ? mText : mDelegate.getText();
    171     }
    172 
    173     @Override
    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 
    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
    222     public void setFeature(String s, boolean b) throws XmlPullParserException {
    223         mDelegate.setFeature(s, b);
    224     }
    225 
    226     @Override
    227     public void setProperty(String s, Object o) throws XmlPullParserException {
    228         mDelegate.setProperty(s, o);
    229     }
    230 
    231     @Override
    232     public void setInput(InputStream inputStream, String s) throws XmlPullParserException {
    233         mDelegate.setInput(inputStream, s);
    234     }
    235 
    236     @Override
    237     public void setInput(Reader reader) throws XmlPullParserException {
    238         mDelegate.setInput(reader);
    239     }
    240 
    241     @Override
    242     public String getInputEncoding() {
    243         return mDelegate.getInputEncoding();
    244     }
    245 
    246     @Override
    247     public String getNamespace(String s) {
    248         return mDelegate.getNamespace(s);
    249     }
    250 
    251     @Override
    252     public String getPositionDescription() {
    253         return mDelegate.getPositionDescription();
    254     }
    255 
    256     @Override
    257     public int getLineNumber() {
    258         return mDelegate.getLineNumber();
    259     }
    260 
    261     @Override
    262     public String getNamespace() {
    263         return mDelegate.getNamespace();
    264     }
    265 
    266     @Override
    267     public int getColumnNumber() {
    268         return mDelegate.getColumnNumber();
    269     }
    270 
    271     // -- We don't care much about the methods that follow.
    272 
    273     @Override
    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
    279     public boolean getFeature(String s) {
    280         throw new UnsupportedOperationException("Only few parser methods are supported.");
    281     }
    282 
    283     @Override
    284     public void defineEntityReplacementText(String s, String s1) throws XmlPullParserException {
    285         throw new UnsupportedOperationException("Only few parser methods are supported.");
    286     }
    287 
    288     @Override
    289     public Object getProperty(String s) {
    290         throw new UnsupportedOperationException("Only few parser methods are supported.");
    291     }
    292 
    293     @Override
    294     public int nextToken() throws XmlPullParserException, IOException {
    295         throw new UnsupportedOperationException("Only few parser methods are supported.");
    296     }
    297 
    298     @Override
    299     public int getNamespaceCount(int i) throws XmlPullParserException {
    300         throw new UnsupportedOperationException("Only few parser methods are supported.");
    301     }
    302 
    303     @Override
    304     public String getNamespacePrefix(int i) throws XmlPullParserException {
    305         throw new UnsupportedOperationException("Only few parser methods are supported.");
    306     }
    307 
    308     @Override
    309     public String getNamespaceUri(int i) throws XmlPullParserException {
    310         throw new UnsupportedOperationException("Only few parser methods are supported.");
    311     }
    312 
    313     @Override
    314     public boolean isWhitespace() throws XmlPullParserException {
    315         throw new UnsupportedOperationException("Only few parser methods are supported.");
    316     }
    317 
    318     @Override
    319     public char[] getTextCharacters(int[] ints) {
    320         throw new UnsupportedOperationException("Only few parser methods are supported.");
    321     }
    322 
    323     @Override
    324     public String getPrefix() {
    325         throw new UnsupportedOperationException("Only few parser methods are supported.");
    326     }
    327 
    328     @Override
    329     public boolean isEmptyElementTag() throws XmlPullParserException {
    330         throw new UnsupportedOperationException("Only few parser methods are supported.");
    331     }
    332 
    333     @Override
    334     public int getAttributeCount() {
    335         throw new UnsupportedOperationException("Only few parser methods are supported.");
    336     }
    337 
    338     @Override
    339     public String getAttributeNamespace(int i) {
    340         throw new UnsupportedOperationException("Only few parser methods are supported.");
    341     }
    342 
    343     @Override
    344     public String getAttributeName(int i) {
    345         throw new UnsupportedOperationException("Only few parser methods are supported.");
    346     }
    347 
    348     @Override
    349     public String getAttributePrefix(int i) {
    350         throw new UnsupportedOperationException("Only few parser methods are supported.");
    351     }
    352 
    353     @Override
    354     public String getAttributeType(int i) {
    355         throw new UnsupportedOperationException("Only few parser methods are supported.");
    356     }
    357 
    358     @Override
    359     public boolean isAttributeDefault(int i) {
    360         throw new UnsupportedOperationException("Only few parser methods are supported.");
    361     }
    362 
    363     @Override
    364     public String getAttributeValue(int i) {
    365         throw new UnsupportedOperationException("Only few parser methods are supported.");
    366     }
    367 
    368     @Override
    369     public String nextText() throws XmlPullParserException, IOException {
    370         throw new UnsupportedOperationException("Only few parser methods are supported.");
    371     }
    372 
    373     @Override
    374     public int nextTag() throws XmlPullParserException, IOException {
    375         throw new UnsupportedOperationException("Only few parser methods are supported.");
    376     }
    377 }
    378