All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.doclava.apicheck.ApiCheck Maven / Gradle / Ivy

/*
 * Copyright (C) 2010 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.google.doclava.apicheck;

import com.google.doclava.AnnotationInstanceInfo;
import com.google.doclava.ClassInfo;
import com.google.doclava.ConstructorInfo;
import com.google.doclava.Converter;
import com.google.doclava.ErrorReport;
import com.google.doclava.Errors;
import com.google.doclava.FieldInfo;
import com.google.doclava.MethodInfo;
import com.google.doclava.PackageInfo;
import com.google.doclava.ParameterInfo;
import com.google.doclava.SourcePositionInfo;
import com.google.doclava.TypeInfo;
import com.sun.javadoc.ClassDoc;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

public class ApiCheck {
  // parse out and consume the -whatever command line flags
  private static ArrayList parseFlags(ArrayList allArgs) {
    ArrayList ret = new ArrayList();

    int i;
    for (i = 0; i < allArgs.size(); i++) {
      // flags with one value attached
      String flag = allArgs.get(i);
      if (flag.equals("-error") || flag.equals("-warning") || flag.equals("-hide")) {
        String[] arg = new String[2];
        arg[0] = flag;
        arg[1] = allArgs.get(++i);
        ret.add(arg);
      } else {
        // we've consumed all of the -whatever args, so we're done
        break;
      }
    }

    // i now points to the first non-flag arg; strip what came before
    for (; i > 0; i--) {
      allArgs.remove(0);
    }
    return ret;
  }

  public static void main(String[] originalArgs) {
    ApiCheck acheck = new ApiCheck();
    ErrorReport report = acheck.checkApi(originalArgs);
   
    Errors.printErrors(report.getErrors());
    System.exit(report.getCode());
  }
  
  /**
   * Compares two api xml files for consistency.
   */
  public ErrorReport checkApi(String[] originalArgs) {
    // translate to an ArrayList for munging
    ArrayList args = new ArrayList(originalArgs.length);
    for (String a : originalArgs) {
      args.add(a);
    }

    ArrayList flags = ApiCheck.parseFlags(args);
    for (String[] a : flags) {
      if (a[0].equals("-error") || a[0].equals("-warning") || a[0].equals("-hide")) {
        try {
          int level = -1;
          if (a[0].equals("-error")) {
            level = Errors.ERROR;
          } else if (a[0].equals("-warning")) {
            level = Errors.WARNING;
          } else if (a[0].equals("-hide")) {
            level = Errors.HIDDEN;
          }
          Errors.setErrorLevel(Integer.parseInt(a[1]), level);
        } catch (NumberFormatException e) {
          System.err.println("Bad argument: " + a[0] + " " + a[1]);
          return new ErrorReport(Errors.EXIT_BAD_ARGUMENTS, Errors.getErrors());
        }
      }
    }

    ApiInfo oldApi;
    ApiInfo newApi;
    
    try {
      oldApi = parseApi(args.get(0));
      newApi = parseApi(args.get(1));
    } catch (ApiParseException e) {
      e.printStackTrace();
      System.err.println("Error parsing API");
      return new ErrorReport(Errors.EXIT_PARSE_ERROR, Errors.getErrors());
    }

    // only run the consistency check if we haven't had XML parse errors
    if (!Errors.hadError) {
      oldApi.isConsistent(newApi);
    }

    return new ErrorReport(Errors.hadError ? Errors.EXIT_ERROR : Errors.EXIT_NORMAL,
        Errors.getErrors());
  }

  private InputStream getInputStreamForFile(String filename) throws IOException {
    try {
      URL url = new URL(filename);
      return url.openStream();
    } catch (MalformedURLException e) {
      return new FileInputStream(filename);
    }
  }
  
  public ApiInfo parseApi(String xmlFile) throws ApiParseException {
    InputStream inStream = null;
    try {
      inStream = getInputStreamForFile(xmlFile);
      return parseApi(inStream);
    } catch (IOException e) {
      throw new ApiParseException("Error parsing xml: " + xmlFile, e);
    } finally {
      if (inStream != null) {
        try {
          inStream.close();
        } catch (IOException ignored) {}
      }
    }
  }
  
  public ApiInfo parseApi(URL xmlURL) throws ApiParseException {
    InputStream xmlStream = null;
    try {
      xmlStream = xmlURL.openStream();
      return parseApi(xmlStream);
    } catch (IOException e) {
      throw new ApiParseException("Could not open stream for parsing: " + xmlURL,e);
    } finally {
      if (xmlStream != null) {
        try {
          xmlStream.close();
        } catch (IOException ignored) {}
      }
    }
  }
  
  public ApiInfo parseApi(InputStream xmlStream) throws ApiParseException {
    try {
      XMLReader xmlreader = XMLReaderFactory.createXMLReader();
      MakeHandler handler = new MakeHandler();
      xmlreader.setContentHandler(handler);
      xmlreader.setErrorHandler(handler);
      xmlreader.parse(new InputSource(xmlStream));
      ApiInfo apiInfo = handler.getApi();
      apiInfo.resolveSuperclasses();
      apiInfo.resolveInterfaces();
      return apiInfo;
    } catch (Exception e) {
      throw new ApiParseException("Error parsing API", e);
    }
  }
  
  private class MakeHandler extends DefaultHandler {

    private ApiInfo mApi;
    private PackageInfo mCurrentPackage;
    private ClassInfo mCurrentClass;
    private AbstractMethodInfo mCurrentMethod;
    private Stack mClassScope = new Stack();
    

    public MakeHandler() {
      super();
      mApi = new ApiInfo();
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
      if (qName.equals("package")) {
        mCurrentPackage =
            new PackageInfo(attributes.getValue("name"), SourcePositionInfo.fromXml(attributes
                .getValue("source")));
      } else if (qName.equals("class") || qName.equals("interface")) {
        // push the old outer scope for later recovery, then set
        // up the new current class object
        mClassScope.push(mCurrentClass);
        
        ClassDoc classDoc = null;
        String rawCommentText = "";
        SourcePositionInfo position = SourcePositionInfo.fromXml(attributes.getValue("source"));
        String visibility = attributes.getValue("visibility");
        boolean isPublic = "public".equals(visibility);
        boolean isProtected = "protected".equals(visibility);
        boolean isPrivate = "private".equals(visibility); 
        boolean isPackagePrivate = !isPublic && !isPrivate && !isProtected;
        boolean isStatic = Boolean.valueOf(attributes.getValue("static"));
        boolean isInterface = qName.equals("interface");
        boolean isAbstract = Boolean.valueOf(attributes.getValue("abstract"));
        boolean isOrdinaryClass = qName.equals("class");
        boolean isException = false; // TODO: check hierarchy for java.lang.Exception
        boolean isError = false; // TODO: not sure.
        boolean isEnum = false; // TODO: not sure.
        boolean isAnnotation = false; // TODO: not sure.
        boolean isFinal = Boolean.valueOf(attributes.getValue("final"));
        boolean isIncluded = false;
        String name = attributes.getValue("name");
        String qualifiedName = qualifiedName(mCurrentPackage.name(), name, mCurrentClass);
        String qualifiedTypeName = null; // TODO: not sure
        boolean isPrimitive = false;
        
        mCurrentClass =
            new ClassInfo(classDoc, rawCommentText, position, isPublic, isProtected, 
            isPackagePrivate, isPrivate, isStatic, isInterface, isAbstract, isOrdinaryClass, 
            isException, isError, isEnum, isAnnotation, isFinal, isIncluded, name, qualifiedName,
            qualifiedTypeName, isPrimitive);
        
        mCurrentClass.setContainingPackage(mCurrentPackage);
        String superclass = attributes.getValue("extends");
        if (superclass == null && !isInterface && !"java.lang.Object".equals(qualifiedName)) {
          throw new AssertionError("no superclass known for class " + name);
        }
        
        // Resolve superclass after .xml completely parsed.
        mApi.mapClassToSuper(mCurrentClass, superclass);
        
        TypeInfo typeInfo = Converter.obtainTypeFromString(qualifiedName) ;
        mCurrentClass.setTypeInfo(typeInfo);
        mCurrentClass.setAnnotations(new AnnotationInstanceInfo[] {});
      } else if (qName.equals("method")) {
        String rawCommentText = "";
        TypeInfo[] typeParameters = new TypeInfo[0];
        String name = attributes.getValue("name");
        String signature = null; // TODO
        ClassInfo containingClass = mCurrentClass;
        ClassInfo realContainingClass = mCurrentClass;
        String visibility = attributes.getValue("visibility");
        boolean isPublic = "public".equals(visibility);
        boolean isProtected = "protected".equals(visibility);
        boolean isPrivate = "private".equals(visibility); 
        boolean isPackagePrivate = !isPublic && !isPrivate && !isProtected;
        boolean isFinal = Boolean.valueOf(attributes.getValue("final"));
        boolean isStatic = Boolean.valueOf(attributes.getValue("static"));
        boolean isSynthetic = false; // TODO
        boolean isAbstract = Boolean.valueOf(attributes.getValue("abstract"));
        boolean isSynchronized = Boolean.valueOf(attributes.getValue("synchronized"));
        boolean isNative = Boolean.valueOf(attributes.getValue("native"));
        boolean isAnnotationElement = false; // TODO
        String kind = qName;
        String flatSignature = null; // TODO
        MethodInfo overriddenMethod = null; // TODO
        TypeInfo returnType = Converter.obtainTypeFromString(attributes.getValue("return"));
        ParameterInfo[] parameters = new ParameterInfo[0];
        ClassInfo[] thrownExceptions = new ClassInfo[0];
        SourcePositionInfo position = SourcePositionInfo.fromXml(attributes.getValue("source"));
        AnnotationInstanceInfo[] annotations = new AnnotationInstanceInfo[] {}; // TODO
        
        mCurrentMethod = 
            new MethodInfo(rawCommentText, typeParameters, name, signature, containingClass,
            realContainingClass, isPublic, isProtected, isPackagePrivate, isPrivate, isFinal,
            isStatic, isSynthetic, isAbstract, isSynchronized, isNative, isAnnotationElement, kind,
            flatSignature, overriddenMethod, returnType, parameters, thrownExceptions, position,
            annotations);
        
        mCurrentMethod.setDeprecated("deprecated".equals(attributes.getValue("deprecated")));
      } else if (qName.equals("constructor")) {
        mCurrentMethod =
            new ConstructorInfo(attributes.getValue("name"), attributes.getValue("type"), Boolean
                .valueOf(attributes.getValue("static")), Boolean.valueOf(attributes
                .getValue("final")), attributes.getValue("deprecated"), attributes
                .getValue("visibility"), SourcePositionInfo.fromXml(attributes.getValue("source")),
                mCurrentClass);
      } else if (qName.equals("field")) {
        String visibility = attributes.getValue("visibility");
        boolean isPublic = visibility.equals("public");
        boolean isProtected = visibility.equals("protected");
        boolean isPrivate = visibility.equals("private");
        boolean isPackagePrivate = visibility.equals("");
        String typeName = attributes.getValue("type");
        TypeInfo type = Converter.obtainTypeFromString(typeName);
        
        FieldInfo fInfo =
            new FieldInfo(attributes.getValue("name"), mCurrentClass, mCurrentClass, isPublic,
            isProtected, isPackagePrivate, isPrivate, Boolean.valueOf(attributes.getValue("final")),
            Boolean.valueOf(attributes.getValue("static")), Boolean.valueOf(attributes.
            getValue("transient")), Boolean.valueOf(attributes.getValue("volatile")), false,
            type, "", attributes.getValue("value"), SourcePositionInfo
            .fromXml(attributes.getValue("source")), new AnnotationInstanceInfo[] {});
        
        fInfo.setDeprecated("deprecated".equals(attributes.getValue("deprecated")));
        mCurrentClass.addField(fInfo);
      } else if (qName.equals("parameter")) {
        String name = attributes.getValue("name");
        String typeName = attributes.getValue("type");
        TypeInfo type = Converter.obtainTypeFromString(typeName);
        boolean isVarArg = typeName.endsWith("...");
        SourcePositionInfo position = null;
        
        mCurrentMethod.addParameter(new ParameterInfo(name, typeName, type, isVarArg, position));
        mCurrentMethod.setVarargs(isVarArg);
      } else if (qName.equals("exception")) {
        mCurrentMethod.addException(attributes.getValue("type"));
      } else if (qName.equals("implements")) {
        // Resolve interfaces after .xml completely parsed.
        mApi.mapClassToInterface(mCurrentClass, attributes.getValue("name"));
      }
    }

    @Override
    public void endElement(String uri, String localName, String qName) {
      if (qName.equals("method")) {
        mCurrentClass.addMethod((MethodInfo) mCurrentMethod);
      } else if (qName.equals("constructor")) {
        mCurrentClass.addConstructor((ConstructorInfo) mCurrentMethod);
      } else if (qName.equals("class") || qName.equals("interface")) {
        mCurrentPackage.addClass(mCurrentClass);
        mCurrentClass = mClassScope.pop();
      } else if (qName.equals("package")) {
        mApi.addPackage(mCurrentPackage);
      }
    }

    public ApiInfo getApi() {
      return mApi;
    }
    
    private String qualifiedName(String pkg, String className, ClassInfo parent) {
      String parentQName = (parent != null) ? (parent.qualifiedName() + ".") : "";
      return pkg + "." + parentQName + className;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy