content: /*
   * Copyright (C) 2008 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.android;
  
  import com.android.ide.common.rendering.api.ILayoutPullParser;
  import com.android.ide.common.rendering.api.ResourceNamespace;
  import com.android.ide.common.rendering.api.ResourceValue;
  import com.android.layoutlib.bridge.impl.ParserFactory;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.res.XmlResourceParser;
  import android.util.AttributeSet;
  import android.util.BridgeXmlPullAttributes;
  import android.util.ResolvingAttributeSet;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.Reader;
  
  /**
   * {@link BridgeXmlBlockParser} reimplements most of android.xml.XmlBlock.Parser.
   * It delegates to both an instance of {@link XmlPullParser} and an instance of
   * XmlPullAttributes (for the {@link AttributeSet} part).
   */
  public class BridgeXmlBlockParser implements XmlResourceParser, ResolvingAttributeSet {
      @NonNull private final XmlPullParser mParser;
      @NonNull private final ResolvingAttributeSet mAttrib;
      @Nullable private final BridgeContext mContext;
      @NonNull private final ResourceNamespace mFileResourceNamespace;
  
      private boolean mStarted = false;
      private int mEventType = START_DOCUMENT;
  
      private boolean mPopped = true; // default to true in case it's not pushed.
  
      /**
       * Builds a {@link BridgeXmlBlockParser}.
       * @param parser XmlPullParser to get the content from.
       * @param context the Context.
       * @param fileNamespace namespace of the file being parsed.
       */
      public BridgeXmlBlockParser(
              @NonNull XmlPullParser parser,
              @Nullable BridgeContext context,
              @NonNull ResourceNamespace fileNamespace) {
          if (ParserFactory.LOG_PARSER) {
              System.out.println("CRTE " + parser.toString());
          }
  
          mParser = parser;
          mContext = context;
          mFileResourceNamespace = fileNamespace;
  
          if (mContext != null) {
              mAttrib = new BridgeXmlPullAttributes(parser, context, mFileResourceNamespace);
              mContext.pushParser(this);
              mPopped = false;
          }
          else {
              mAttrib = new NopAttributeSet();
          }
      }
  
      public XmlPullParser getParser() {
          return mParser;
      }
  
      @NonNull
      public ResourceNamespace getFileResourceNamespace() {
          return mFileResourceNamespace;
      }
  
      public Object getViewCookie() {
          if (mParser instanceof ILayoutPullParser) {
              return ((ILayoutPullParser)mParser).getViewCookie();
          }
  
          return null;
      }
  
      public void ensurePopped() {
          if (mContext != null && !mPopped) {
              mContext.popParser();
              mPopped = true;
          }
      }
  
      // ------- XmlResourceParser implementation
  
      @Override
      public void setFeature(String name, boolean state)
              throws XmlPullParserException {
          if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) {
              return;
          }
          if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) {
              return;
          }
          throw new XmlPullParserException("Unsupported feature: " + name);
      }
  
      @Override
      public boolean getFeature(String name) {
          return FEATURE_PROCESS_NAMESPACES.equals(name) ||
                  FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name);
      }
  
      @Override
      public void setProperty(String name, Object value) throws XmlPullParserException {
          throw new XmlPullParserException("setProperty() not supported");
      }
  
      @Override
      public Object getProperty(String name) {
          return null;
      }
  
      @Override
      public void setInput(Reader in) throws XmlPullParserException {
          mParser.setInput(in);
      }
  
      @Override
      public void setInput(InputStream inputStream, String inputEncoding)
              throws XmlPullParserException {
          mParser.setInput(inputStream, inputEncoding);
      }
  
      @Override
      public void defineEntityReplacementText(String entityName,
              String replacementText) throws XmlPullParserException {
          throw new XmlPullParserException(
                  "defineEntityReplacementText() not supported");
      }
  
      @Override
      public String getNamespacePrefix(int pos) throws XmlPullParserException {
          throw new XmlPullParserException("getNamespacePrefix() not supported");
      }
  
      @Override
      public String getInputEncoding() {
          return null;
      }
  
      @Override
      public String getNamespace(String prefix) {
          return mParser.getNamespace(prefix);
      }
  
      @Override
      public int getNamespaceCount(int depth) throws XmlPullParserException {
          throw new XmlPullParserException("getNamespaceCount() not supported");
      }
  
      @Override
      public String getPositionDescription() {
          return "Binary XML file line #" + getLineNumber();
      }
  
      @Override
      public String getNamespaceUri(int pos) throws XmlPullParserException {
          throw new XmlPullParserException("getNamespaceUri() not supported");
      }
  
      @Override
      public int getColumnNumber() {
          return -1;
      }
  
      @Override
      public int getDepth() {
          return mParser.getDepth();
      }
  
      @Override
      public String getText() {
          return mParser.getText();
      }
  
      @Override
      public int getLineNumber() {
          return mParser.getLineNumber();
      }
  
      @Override
      public int getEventType() {
          return mEventType;
      }
  
      @Override
      public boolean isWhitespace() throws XmlPullParserException {
          // Original comment: whitespace was stripped by aapt.
          return mParser.isWhitespace();
      }
  
      @Override
      public String getPrefix() {
          throw new RuntimeException("getPrefix not supported");
      }
  
      @Override
      public char[] getTextCharacters(int[] holderForStartAndLength) {
          String txt = getText();
          char[] chars = null;
          if (txt != null) {
              holderForStartAndLength[0] = 0;
              holderForStartAndLength[1] = txt.length();
              chars = new char[txt.length()];
              txt.getChars(0, txt.length(), chars, 0);
          }
          return chars;
      }
  
      @Override
      public String getNamespace() {
          return mParser.getNamespace();
      }
  
      @Override
      public String getName() {
          return mParser.getName();
      }
  
      @Override
      public String getAttributeNamespace(int index) {
          return mParser.getAttributeNamespace(index);
      }
  
      @Override
      public String getAttributeName(int index) {
          return mParser.getAttributeName(index);
      }
  
      @Override
      public String getAttributePrefix(int index) {
          throw new RuntimeException("getAttributePrefix not supported");
      }
  
      @Override
      public boolean isEmptyElementTag() {
          // XXX Need to detect this.
          return false;
      }
  
      @Override
      public int getAttributeCount() {
          return mParser.getAttributeCount();
      }
  
      @Override
      public String getAttributeValue(int index) {
          return mParser.getAttributeValue(index);
      }
  
      @Override
      public String getAttributeType(int index) {
          return "CDATA";
      }
  
      @Override
      public boolean isAttributeDefault(int index) {
          return false;
      }
  
      @Override
      public int nextToken() throws XmlPullParserException, IOException {
          return next();
      }
  
      @Override
      public String getAttributeValue(String namespace, String name) {
          return mParser.getAttributeValue(namespace, name);
      }
  
      @Override
      public int next() throws XmlPullParserException, IOException {
          if (!mStarted) {
              mStarted = true;
  
              if (ParserFactory.LOG_PARSER) {
                  System.out.println("STRT " + mParser.toString());
              }
  
              return START_DOCUMENT;
          }
  
          int ev = mParser.next();
  
          if (ParserFactory.LOG_PARSER) {
              System.out.println("NEXT " + mParser.toString() + " " +
                      eventTypeToString(mEventType) + " -> " + eventTypeToString(ev));
          }
  
          if (ev == END_TAG && mParser.getDepth() == 1) {
              // done with parser remove it from the context stack.
              ensurePopped();
          }
  
          mEventType = ev;
          return ev;
      }
  
      private static String eventTypeToString(int eventType) {
          switch (eventType) {
              case START_DOCUMENT:
                  return "START_DOC";
              case END_DOCUMENT:
                  return "END_DOC";
              case START_TAG:
                  return "START_TAG";
              case END_TAG:
                  return "END_TAG";
              case TEXT:
                  return "TEXT";
              case CDSECT:
                  return "CDSECT";
              case ENTITY_REF:
                  return "ENTITY_REF";
              case IGNORABLE_WHITESPACE:
                  return "IGNORABLE_WHITESPACE";
              case PROCESSING_INSTRUCTION:
                  return "PROCESSING_INSTRUCTION";
              case COMMENT:
                  return "COMMENT";
              case DOCDECL:
                  return "DOCDECL";
          }
  
          return "????";
      }
  
      @Override
      public void require(int type, String namespace, String name)
              throws XmlPullParserException {
          if (type != getEventType()
                  || (namespace != null && !namespace.equals(getNamespace()))
                  || (name != null && !name.equals(getName())))
              throw new XmlPullParserException("expected " + TYPES[type]
                      + getPositionDescription());
      }
  
      @Override
      public String nextText() throws XmlPullParserException, IOException {
          if (getEventType() != START_TAG) {
              throw new XmlPullParserException(getPositionDescription()
                      + ": parser must be on START_TAG to read next text", this,
                      null);
          }
          int eventType = next();
          if (eventType == TEXT) {
              String result = getText();
              eventType = next();
              if (eventType != END_TAG) {
                  throw new XmlPullParserException(
                          getPositionDescription()
                                  + ": event TEXT it must be immediately followed by END_TAG",
                          this, null);
              }
              return result;
          } else if (eventType == END_TAG) {
              return "";
          } else {
              throw new XmlPullParserException(getPositionDescription()
                      + ": parser must be on START_TAG or TEXT to read text",
                      this, null);
          }
      }
  
      @Override
      public int nextTag() throws XmlPullParserException, IOException {
          int eventType = next();
          if (eventType == TEXT && isWhitespace()) { // skip whitespace
              eventType = next();
          }
          if (eventType != START_TAG && eventType != END_TAG) {
              throw new XmlPullParserException(getPositionDescription()
                      + ": expected start or end tag", this, null);
          }
          return eventType;
      }
  
      // AttributeSet implementation
  
  
      @Override
      public void close() {
          // pass
      }
  
      @Override
      public boolean getAttributeBooleanValue(int index, boolean defaultValue) {
          return mAttrib.getAttributeBooleanValue(index, defaultValue);
      }
  
      @Override
      public boolean getAttributeBooleanValue(String namespace, String attribute,
              boolean defaultValue) {
          return mAttrib.getAttributeBooleanValue(namespace, attribute, defaultValue);
      }
  
      @Override
      public float getAttributeFloatValue(int index, float defaultValue) {
          return mAttrib.getAttributeFloatValue(index, defaultValue);
      }
  
      @Override
      public float getAttributeFloatValue(String namespace, String attribute, float defaultValue) {
          return mAttrib.getAttributeFloatValue(namespace, attribute, defaultValue);
      }
  
      @Override
      public int getAttributeIntValue(int index, int defaultValue) {
          return mAttrib.getAttributeIntValue(index, defaultValue);
      }
  
      @Override
      public int getAttributeIntValue(String namespace, String attribute, int defaultValue) {
          return mAttrib.getAttributeIntValue(namespace, attribute, defaultValue);
      }
  
      @Override
      public int getAttributeListValue(int index, String[] options, int defaultValue) {
          return mAttrib.getAttributeListValue(index, options, defaultValue);
      }
  
      @Override
      public int getAttributeListValue(String namespace, String attribute,
              String[] options, int defaultValue) {
          return mAttrib.getAttributeListValue(namespace, attribute, options, defaultValue);
      }
  
      @Override
      public int getAttributeNameResource(int index) {
          return mAttrib.getAttributeNameResource(index);
      }
  
      @Override
      public int getAttributeResourceValue(int index, int defaultValue) {
          return mAttrib.getAttributeResourceValue(index, defaultValue);
      }
  
      @Override
      public int getAttributeResourceValue(String namespace, String attribute, int defaultValue) {
          return mAttrib.getAttributeResourceValue(namespace, attribute, defaultValue);
      }
  
      @Override
      public int getAttributeUnsignedIntValue(int index, int defaultValue) {
          return mAttrib.getAttributeUnsignedIntValue(index, defaultValue);
      }
  
      @Override
      public int getAttributeUnsignedIntValue(String namespace, String attribute, int defaultValue) {
          return mAttrib.getAttributeUnsignedIntValue(namespace, attribute, defaultValue);
      }
  
      @Override
      public String getClassAttribute() {
          return mAttrib.getClassAttribute();
      }
  
      @Override
      public String getIdAttribute() {
          return mAttrib.getIdAttribute();
      }
  
      @Override
      public int getIdAttributeResourceValue(int defaultValue) {
          return mAttrib.getIdAttributeResourceValue(defaultValue);
      }
  
      @Override
      public int getStyleAttribute() {
          return mAttrib.getStyleAttribute();
      }
  
      @Override
      @Nullable
      public ResourceValue getResolvedAttributeValue(@Nullable String namespace,
              @NonNull String name) {
          return mAttrib.getResolvedAttributeValue(namespace, name);
      }
  }
  