blob: 71e7fd2c5eea68c46993b0d40ada3698b7f7a4b8 [file] [log] [blame]
/*
* 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.");
}
}