content: /*
   * Copyright (C) 2011 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 android.tests.sigtest;
  
  import android.content.res.Resources;
  import android.test.AndroidTestCase;
  import android.tests.sigtest.JDiffClassDescription.JDiffConstructor;
  import android.tests.sigtest.JDiffClassDescription.JDiffField;
  import android.tests.sigtest.JDiffClassDescription.JDiffMethod;
  import android.tests.sigtest.SignatureTestActivity.FAILURE_TYPE;
  
  import org.xmlpull.v1.XmlPullParser;
  import org.xmlpull.v1.XmlPullParserException;
  
  import java.io.IOException;
  import java.lang.reflect.Modifier;
  import java.util.Arrays;
  import java.util.HashSet;
  
  /**
   * A simpler version of {@link SignatureTest} that performs the signature check via a JUnit test.
   * <p/>
   * Eventually the existing {@link SignatureTest} and {@link SignatureTestActivity} will be deleted
   * once the move to a tradefederation based CTS harness is complete.
   */
  public class RcsApiSignatureTest extends AndroidTestCase {
  
      private static final String TAG_ROOT = "api";
      private static final String TAG_PACKAGE = "package";
      private static final String TAG_CLASS = "class";
      private static final String TAG_INTERFACE = "interface";
      private static final String TAG_IMPLEMENTS = "implements";
      private static final String TAG_CONSTRUCTOR = "constructor";
      private static final String TAG_METHOD = "method";
      private static final String TAG_PARAM = "parameter";
      private static final String TAG_EXCEPTION = "exception";
      private static final String TAG_FIELD = "field";
  
      private static final String MODIFIER_ABSTRACT = "abstract";
      private static final String MODIFIER_FINAL = "final";
      private static final String MODIFIER_NATIVE = "native";
      private static final String MODIFIER_PRIVATE = "private";
      private static final String MODIFIER_PROTECTED = "protected";
      private static final String MODIFIER_PUBLIC = "public";
      private static final String MODIFIER_STATIC = "static";
      private static final String MODIFIER_SYNCHRONIZED = "synchronized";
      private static final String MODIFIER_TRANSIENT = "transient";
      private static final String MODIFIER_VOLATILE = "volatile";
      private static final String MODIFIER_VISIBILITY = "visibility";
  
      private static final String ATTRIBUTE_NAME = "name";
      private static final String ATTRIBUTE_EXTENDS = "extends";
      private static final String ATTRIBUTE_TYPE = "type";
      private static final String ATTRIBUTE_RETURN = "return";
  
      private HashSet<String> mKeyTagSet;
      private TestResultObserver mResultObserver;
  
      private class TestResultObserver implements ResultObserver {
          boolean mDidFail = false;
          StringBuilder mErrorString = new StringBuilder();
  
          public void notifyFailure(FAILURE_TYPE type, String name, String errorMessage) {
              mDidFail = true;
              mErrorString.append("\n");
              mErrorString.append(type.toString().toLowerCase());
              mErrorString.append(":\t");
              mErrorString.append(name);
          }
      }
  
      @Override
      protected void setUp() throws Exception {
          super.setUp();
          mKeyTagSet = new HashSet<>();
          mKeyTagSet.addAll(Arrays.asList(TAG_PACKAGE, TAG_CLASS, TAG_INTERFACE, TAG_IMPLEMENTS,
                  TAG_CONSTRUCTOR, TAG_METHOD, TAG_PARAM, TAG_EXCEPTION, TAG_FIELD));
          mResultObserver = new TestResultObserver();
      }
  
      /**
       * Tests that the device's API matches the expected set defined in xml.
       * <p/>
       * Will check the entire API, and then report the complete list of failures
       */
      public void testSignature() {
          Resources r = getContext().getResources();
          try {
              start(r.getXml(ToTest.VERSION));
          } catch (Exception e) {
              mResultObserver.notifyFailure(FAILURE_TYPE.CAUGHT_EXCEPTION, e.getMessage(),
                      e.getMessage());
          }
          if (mResultObserver.mDidFail) {
              fail(mResultObserver.mErrorString.toString());
          }
      }
  
      private void beginDocument(XmlPullParser parser, String firstElementName)
              throws XmlPullParserException, IOException {
          int type;
          while ((type = parser.next()) != XmlPullParser.START_TAG
                  && type != XmlPullParser.END_DOCUMENT) {
          }
  
          if (type != XmlPullParser.START_TAG) {
              throw new XmlPullParserException("No start tag found");
          }
  
          if (!parser.getName().equals(firstElementName)) {
              throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
                      + ", expected " + firstElementName);
          }
      }
  
      /**
       * Signature test entry point.
       */
      private void start(XmlPullParser parser) throws XmlPullParserException, IOException {
          JDiffClassDescription currentClass = null;
          String currentPackage = "";
          JDiffMethod currentMethod = null;
  
          beginDocument(parser, TAG_ROOT);
          int type;
          while (true) {
              while ((type = parser.next()) != XmlPullParser.START_TAG
                      && type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.END_TAG) {
  
              }
  
              if (type == XmlPullParser.END_TAG) {
                  if (TAG_CLASS.equals(parser.getName()) || TAG_INTERFACE.equals(parser.getName())) {
                      currentClass.checkSignatureCompliance();
                  } else if (TAG_PACKAGE.equals(parser.getName())) {
                      currentPackage = "";
                  }
                  continue;
              }
  
              if (type == XmlPullParser.END_DOCUMENT) {
                  break;
              }
  
              String tagname = parser.getName();
              if (!mKeyTagSet.contains(tagname)) {
                  continue;
              }
  
              switch (tagname) {
                  case TAG_PACKAGE:
                      currentPackage = parser.getAttributeValue(null, ATTRIBUTE_NAME);
                      break;
                  case TAG_CLASS:
                      currentClass = loadClassInfo(parser, false, currentPackage);
                      break;
                  case TAG_INTERFACE:
                      currentClass = loadClassInfo(parser, true, currentPackage);
                      break;
                  case TAG_IMPLEMENTS:
                      currentClass.addImplInterface(parser.getAttributeValue(null, ATTRIBUTE_NAME));
                      break;
                  case TAG_CONSTRUCTOR:
                      JDiffConstructor constructor = loadConstructorInfo(parser, currentClass);
                      currentClass.addConstructor(constructor);
                      currentMethod = constructor;
                      break;
                  case TAG_METHOD:
                      currentMethod = loadMethodInfo(currentClass.getClassName(), parser);
                      currentClass.addMethod(currentMethod);
                      break;
                  case TAG_PARAM:
                      currentMethod.addParam(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
                      break;
                  case TAG_EXCEPTION:
                      currentMethod.addException(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
                      break;
                  case TAG_FIELD:
                      JDiffField field = loadFieldInfo(currentClass.getClassName(), parser);
                      currentClass.addField(field);
                      break;
                  default:
                      throw new RuntimeException("unknow tag exception:" + tagname);
              }
          }
      }
  
      /**
       * Load field information from xml to memory.
       * 
       * @param className of the class being examined which will be shown in error messages
       * @param parser The XmlPullParser which carries the xml information.
       * @return the new field
       */
      private JDiffField loadFieldInfo(String className, XmlPullParser parser) {
          String fieldName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
          String fieldType = parser.getAttributeValue(null, ATTRIBUTE_TYPE);
          int modifier = jdiffModifierToReflectionFormat(className, parser);
          return new JDiffField(fieldName, fieldType, modifier);
      }
  
      /**
       * Load method information from xml to memory.
       * 
       * @param className of the class being examined which will be shown in error messages
       * @param parser The XmlPullParser which carries the xml information.
       * @return the newly loaded method.
       */
      private JDiffMethod loadMethodInfo(String className, XmlPullParser parser) {
          String methodName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
          String returnType = parser.getAttributeValue(null, ATTRIBUTE_RETURN);
          int modifier = jdiffModifierToReflectionFormat(className, parser);
          return new JDiffMethod(methodName, modifier, returnType);
      }
  
      /**
       * Load constructor information from xml to memory.
       * 
       * @param parser The XmlPullParser which carries the xml information.
       * @param currentClass the current class being loaded.
       * @return the new constructor
       */
      private JDiffConstructor loadConstructorInfo(XmlPullParser parser,
              JDiffClassDescription currentClass) {
          String name = currentClass.getClassName();
          int modifier = jdiffModifierToReflectionFormat(name, parser);
          return new JDiffConstructor(name, modifier);
      }
  
      /**
       * Load class or interface information to memory.
       * 
       * @param parser The XmlPullParser which carries the xml information.
       * @param isInterface true if the current class is an interface, otherwise is false.
       * @param pkg the name of the java package this class can be found in.
       * @return the new class description.
       */
      private JDiffClassDescription loadClassInfo(XmlPullParser parser, boolean isInterface,
              String pkg) {
          String className = parser.getAttributeValue(null, ATTRIBUTE_NAME);
          JDiffClassDescription currentClass = new JDiffClassDescription(pkg, className,
                  mResultObserver);
          currentClass.setModifier(jdiffModifierToReflectionFormat(className, parser));
          currentClass.setType(isInterface ? JDiffClassDescription.JDiffType.INTERFACE
                  : JDiffClassDescription.JDiffType.CLASS);
          currentClass.setExtendsClass(parser.getAttributeValue(null, ATTRIBUTE_EXTENDS));
          return currentClass;
      }
  
      /**
       * Convert string modifier to int modifier.
       * 
       * @param name of the class/method/field being examined which will be shown in error messages
       * @param key modifier name
       * @param value modifier value
       * @return converted modifier value
       */
      private static int modifierDescriptionToReflectedType(String name, String key, String value) {
          switch (key) {
              case MODIFIER_ABSTRACT:
                  return value.equals("true") ? Modifier.ABSTRACT : 0;
              case MODIFIER_FINAL:
                  return value.equals("true") ? Modifier.FINAL : 0;
              case MODIFIER_NATIVE:
                  return value.equals("true") ? Modifier.NATIVE : 0;
              case MODIFIER_STATIC:
                  return value.equals("true") ? Modifier.STATIC : 0;
              case MODIFIER_SYNCHRONIZED:
                  return value.equals("true") ? Modifier.SYNCHRONIZED : 0;
              case MODIFIER_TRANSIENT:
                  return value.equals("true") ? Modifier.TRANSIENT : 0;
              case MODIFIER_VOLATILE:
                  return value.equals("true") ? Modifier.VOLATILE : 0;
              case MODIFIER_VISIBILITY:
                  switch (value) {
                      case MODIFIER_PRIVATE:
                          throw new RuntimeException("Private visibility found in API spec: " + name);
                      case MODIFIER_PROTECTED:
                          return Modifier.PROTECTED;
                      case MODIFIER_PUBLIC:
                          return Modifier.PUBLIC;
                      case "":
                          // If the visibility is "", it means it has no modifier.
                          // which is package private. We should return 0 for this modifier.
                          return 0;
                      default:
                          throw new RuntimeException("Unknown modifier found in API spec: " + value);
                  }
          }
          return 0;
      }
  
      /**
       * Transfer string modifier to int one.
       * 
       * @param name of the class/method/field being examined which will be shown in error messages
       * @param parser XML resource parser
       * @return converted modifier
       */
      private static int jdiffModifierToReflectionFormat(String name, XmlPullParser parser) {
          int modifier = 0;
          for (int i = 0; i < parser.getAttributeCount(); i++) {
              modifier |= modifierDescriptionToReflectedType(name, parser.getAttributeName(i),
                      parser.getAttributeValue(i));
          }
          return modifier;
      }
  }
  