| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.layoutlib.bridge.impl; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import android.annotation.Nullable; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * A wrapper around XmlPullParser that can peek forward to inspect if the file is a data-binding |
| * layout and some parts need to be stripped. |
| */ |
| public class LayoutParserWrapper implements XmlPullParser { |
| |
| // Data binding constants. |
| private static final String TAG_LAYOUT = "layout"; |
| private static final String TAG_DATA = "data"; |
| private static final String DEFAULT = "default="; |
| |
| private final XmlPullParser mDelegate; |
| |
| // Storage for peeked values. |
| private boolean mPeeked; |
| private int mEventType; |
| private int mDepth; |
| private int mNext; |
| private List<Attribute> mAttributes; |
| private String mText; |
| private String mName; |
| |
| // Used to end the document before the actual parser ends. |
| private int mFinalDepth = -1; |
| private boolean mEndNow; |
| |
| public LayoutParserWrapper(XmlPullParser delegate) { |
| mDelegate = delegate; |
| } |
| |
| public LayoutParserWrapper peekTillLayoutStart() throws IOException, XmlPullParserException { |
| final int STATE_LAYOUT_NOT_STARTED = 0; // <layout> tag not encountered yet. |
| final int STATE_ROOT_NOT_STARTED = 1; // the main view root not found yet. |
| final int STATE_INSIDE_DATA = 2; // START_TAG for <data> found, but not END_TAG. |
| |
| int state = STATE_LAYOUT_NOT_STARTED; |
| int dataDepth = -1; // depth of the <data> tag. Should be two. |
| while (true) { |
| int peekNext = peekNext(); |
| switch (peekNext) { |
| case START_TAG: |
| if (state == STATE_LAYOUT_NOT_STARTED) { |
| if (mName.equals(TAG_LAYOUT)) { |
| state = STATE_ROOT_NOT_STARTED; |
| } else { |
| return this; // no layout tag in the file. |
| } |
| } else if (state == STATE_ROOT_NOT_STARTED) { |
| if (mName.equals(TAG_DATA)) { |
| state = STATE_INSIDE_DATA; |
| dataDepth = mDepth; |
| } else { |
| mFinalDepth = mDepth; |
| return this; |
| } |
| } |
| break; |
| case END_TAG: |
| if (state == STATE_INSIDE_DATA) { |
| if (mDepth <= dataDepth) { |
| state = STATE_ROOT_NOT_STARTED; |
| } |
| } |
| break; |
| case END_DOCUMENT: |
| // No layout start found. |
| return this; |
| } |
| // consume the peeked tag. |
| next(); |
| } |
| } |
| |
| private int peekNext() throws IOException, XmlPullParserException { |
| if (mPeeked) { |
| return mNext; |
| } |
| mEventType = mDelegate.getEventType(); |
| mNext = mDelegate.next(); |
| if (mEventType == START_TAG) { |
| int count = mDelegate.getAttributeCount(); |
| mAttributes = count > 0 ? new ArrayList<Attribute>(count) : |
| Collections.<Attribute>emptyList(); |
| for (int i = 0; i < count; i++) { |
| mAttributes.add(new Attribute(mDelegate.getAttributeNamespace(i), |
| mDelegate.getAttributeName(i), mDelegate.getAttributeValue(i))); |
| } |
| } |
| mDepth = mDelegate.getDepth(); |
| mText = mDelegate.getText(); |
| mName = mDelegate.getName(); |
| mPeeked = true; |
| return mNext; |
| } |
| |
| private void reset() { |
| mAttributes = null; |
| mText = null; |
| mName = null; |
| mPeeked = false; |
| } |
| |
| @Override |
| public int next() throws XmlPullParserException, IOException { |
| int returnValue; |
| int depth; |
| if (mPeeked) { |
| returnValue = mNext; |
| depth = mDepth; |
| reset(); |
| } else if (mEndNow) { |
| return END_DOCUMENT; |
| } else { |
| returnValue = mDelegate.next(); |
| depth = getDepth(); |
| } |
| if (returnValue == END_TAG && depth <= mFinalDepth) { |
| mEndNow = true; |
| } |
| return returnValue; |
| } |
| |
| @Override |
| public int getEventType() throws XmlPullParserException { |
| return mPeeked ? mEventType : mDelegate.getEventType(); |
| } |
| |
| @Override |
| public int getDepth() { |
| return mPeeked ? mDepth : mDelegate.getDepth(); |
| } |
| |
| @Override |
| public String getName() { |
| return mPeeked ? mName : mDelegate.getName(); |
| } |
| |
| @Override |
| public String getText() { |
| return mPeeked ? mText : mDelegate.getText(); |
| } |
| |
| @Override |
| public String getAttributeValue(@Nullable String namespace, String name) { |
| String returnValue = null; |
| if (mPeeked) { |
| if (mAttributes == null) { |
| if (mEventType != START_TAG) { |
| throw new IndexOutOfBoundsException("getAttributeValue() called when not at START_TAG."); |
| } else { |
| return null; |
| } |
| } else { |
| for (Attribute attribute : mAttributes) { |
| //noinspection StringEquality for nullness check. |
| if (attribute.name.equals(name) && (attribute.namespace == namespace || |
| attribute.namespace != null && attribute.namespace.equals(namespace))) { |
| returnValue = attribute.value; |
| break; |
| } |
| } |
| } |
| } else { |
| returnValue = mDelegate.getAttributeValue(namespace, name); |
| } |
| // Check if the value is bound via data-binding, if yes get the default value. |
| if (returnValue != null && mFinalDepth >= 0 && returnValue.startsWith("@{")) { |
| // TODO: Improve the detection of default keyword. |
| int i = returnValue.lastIndexOf(DEFAULT); |
| return i > 0 ? returnValue.substring(i + DEFAULT.length(), returnValue.length() - 1) |
| : null; |
| } |
| return returnValue; |
| } |
| |
| private static class Attribute { |
| @Nullable |
| public final String namespace; |
| public final String name; |
| public final String value; |
| |
| public Attribute(@Nullable String namespace, String name, String value) { |
| this.namespace = namespace; |
| this.name = name; |
| this.value = value; |
| } |
| } |
| |
| // Not affected by peeking. |
| |
| @Override |
| public void setFeature(String s, boolean b) throws XmlPullParserException { |
| mDelegate.setFeature(s, b); |
| } |
| |
| @Override |
| public void setProperty(String s, Object o) throws XmlPullParserException { |
| mDelegate.setProperty(s, o); |
| } |
| |
| @Override |
| public void setInput(InputStream inputStream, String s) throws XmlPullParserException { |
| mDelegate.setInput(inputStream, s); |
| } |
| |
| @Override |
| public void setInput(Reader reader) throws XmlPullParserException { |
| mDelegate.setInput(reader); |
| } |
| |
| @Override |
| public String getInputEncoding() { |
| return mDelegate.getInputEncoding(); |
| } |
| |
| @Override |
| public String getNamespace(String s) { |
| return mDelegate.getNamespace(s); |
| } |
| |
| @Override |
| public String getPositionDescription() { |
| return mDelegate.getPositionDescription(); |
| } |
| |
| @Override |
| public int getLineNumber() { |
| return mDelegate.getLineNumber(); |
| } |
| |
| @Override |
| public String getNamespace() { |
| return mDelegate.getNamespace(); |
| } |
| |
| @Override |
| public int getColumnNumber() { |
| return mDelegate.getColumnNumber(); |
| } |
| |
| // -- We don't care much about the methods that follow. |
| |
| @Override |
| public void require(int i, String s, String s1) throws XmlPullParserException, IOException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public boolean getFeature(String s) { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public void defineEntityReplacementText(String s, String s1) throws XmlPullParserException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public Object getProperty(String s) { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public int nextToken() throws XmlPullParserException, IOException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public int getNamespaceCount(int i) throws XmlPullParserException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public String getNamespacePrefix(int i) throws XmlPullParserException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public String getNamespaceUri(int i) throws XmlPullParserException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public boolean isWhitespace() throws XmlPullParserException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public char[] getTextCharacters(int[] ints) { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public String getPrefix() { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public boolean isEmptyElementTag() throws XmlPullParserException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public int getAttributeCount() { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public String getAttributeNamespace(int i) { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public String getAttributeName(int i) { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public String getAttributePrefix(int i) { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public String getAttributeType(int i) { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public boolean isAttributeDefault(int i) { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public String getAttributeValue(int i) { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public String nextText() throws XmlPullParserException, IOException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| |
| @Override |
| public int nextTag() throws XmlPullParserException, IOException { |
| throw new UnsupportedOperationException("Only few parser methods are supported."); |
| } |
| } |