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