resolved conflicts for merge of b4dd76b8 to mnc-dev

Change-Id: I2525c9113664db9c9d3a750347058973e6f74972
diff --git a/build.gradle b/build.gradle
index 50c36d4..0fdc5d1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,9 +13,40 @@
     }
 }
 
-dependencies {
-    compile files(BuildUtils.findToolsJar(project))
-    compile project(path: ':antlr', configuration: 'antlrRuntime')
-    compile project(':jsilver')
-    compile project(':tagsoup')
-}
\ No newline at end of file
+// TODO put this function in a plugin
+// TODO remove when prebuilt's case will always properly work with BuildUtils's version.
+String findToolsJar() {
+    new ByteArrayOutputStream().withStream { os ->
+        project.exec {
+            executable "../../build/core/find-jdk-tools-jar.sh"
+
+            standardOutput = os
+        }
+        return os.toString().trim()
+    }
+}
+
+if (project.hasProperty("usePrebuilts") && project.usePrebuilts == "true") {
+    repositories {
+        maven { url '../../prebuilts/tools/common/m2/repository' }
+    }
+
+    // TODO refactor to allow referencing the "gradle way"
+    dependencies {
+        compile files(findToolsJar())
+        compile files('../../prebuilts/misc/common/antlr/antlr-3.4-complete.jar')
+        compile 'com.google.jsilver:jsilver:1.0.0'
+        // TODO add tagsoup to prebuils to fully support building using prebuilts
+        compile project(':tagsoup')
+        // required by jsilver
+        compile 'com.google.guava:guava:15.0'
+        //compile project(path: ':junit', configuration: 'target')
+    }
+} else {
+    dependencies {
+        compile files(BuildUtils.findToolsJar(project))
+        compile project(path: ':antlr', configuration: 'antlrRuntime')
+        compile project(':jsilver')
+        compile project(':tagsoup')
+    }
+}
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
index 616cf23..8f0199e 100644
--- a/src/com/google/doclava/ClassInfo.java
+++ b/src/com/google/doclava/ClassInfo.java
@@ -2045,7 +2045,12 @@
   }
 
   public boolean isConsistent(ClassInfo cl) {
+    return isConsistent(cl, null, null);
+  }
+
+  public boolean isConsistent(ClassInfo cl, List<MethodInfo> newCtors, List<MethodInfo> newMethods) {
     boolean consistent = true;
+    boolean diffMode = (newCtors != null) && (newMethods != null);
 
     if (isInterface() != cl.isInterface()) {
       Errors.error(Errors.CHANGED_CLASS, cl.position(), "Class " + cl.qualifiedName()
@@ -2092,15 +2097,24 @@
         /*
          * Similarly to the above, do not fail if this "new" method is really an override of an
          * existing superclass method.
+         * But we should fail if this is overriding an abstract method, because method's
+         * abstractness affects how users use it. See also Stubs.methodIsOverride().
          */
         MethodInfo mi = ClassInfo.overriddenMethod(mInfo, this);
-        if (mi == null) {
+        if (mi == null ||
+            mi.isAbstract() != mInfo.isAbstract()) {
           Errors.error(Errors.ADDED_METHOD, mInfo.position(), "Added public method "
               + mInfo.prettyQualifiedSignature());
+          if (diffMode) {
+            newMethods.add(mInfo);
+          }
           consistent = false;
         }
       }
     }
+    if (diffMode) {
+      Collections.sort(newMethods, MethodInfo.comparator);
+    }
 
     for (MethodInfo mInfo : mApiCheckConstructors.values()) {
       if (cl.mApiCheckConstructors.containsKey(mInfo.getHashableName())) {
@@ -2117,9 +2131,15 @@
       if (!mApiCheckConstructors.containsKey(mInfo.getHashableName())) {
         Errors.error(Errors.ADDED_METHOD, mInfo.position(), "Added public constructor "
             + mInfo.prettyQualifiedSignature());
+        if (diffMode) {
+          newCtors.add(mInfo);
+        }
         consistent = false;
       }
     }
+    if (diffMode) {
+      Collections.sort(newCtors, MethodInfo.comparator);
+    }
 
     for (FieldInfo mInfo : mApiCheckFields.values()) {
       if (cl.mApiCheckFields.containsKey(mInfo.name())) {
diff --git a/src/com/google/doclava/Comment.java b/src/com/google/doclava/Comment.java
index 33dee3c..616ccc4 100644
--- a/src/com/google/doclava/Comment.java
+++ b/src/com/google/doclava/Comment.java
@@ -340,7 +340,8 @@
       mInlineTagsList.add(new LiteralTagInfo(text, pos));
     } else if (name.equals("@code")) {
       mInlineTagsList.add(new CodeTagInfo(text, pos));
-    } else if (name.equals("@hide") || name.equals("@pending") || name.equals("@doconly")) {
+    } else if (name.equals("@hide") || name.equals("@removed")
+            || name.equals("@pending") || name.equals("@doconly")) {
       // nothing
     } else if (name.equals("@attr")) {
       AttrTagInfo tag = new AttrTagInfo("@attr", "@attr", text, mBase, pos);
diff --git a/src/com/google/doclava/Errors.java b/src/com/google/doclava/Errors.java
index 84df16b..156de66 100644
--- a/src/com/google/doclava/Errors.java
+++ b/src/com/google/doclava/Errors.java
@@ -172,6 +172,7 @@
   public static final Error INVALID_CONTENT_TYPE = new Error(119, ERROR);
   public static final Error INVALID_SAMPLE_INDEX = new Error(120, ERROR);
   public static final Error HIDDEN_TYPE_PARAMETER = new Error(121, HIDDEN);
+  public static final Error PRIVATE_SUPERCLASS = new Error(122, ERROR);
 
   public static final Error[] ERRORS =
       {UNRESOLVED_LINK, BAD_INCLUDE_TAG, UNKNOWN_TAG, UNKNOWN_PARAM_TAG_NAME,
@@ -183,7 +184,7 @@
           CHANGED_TRANSIENT, CHANGED_VOLATILE, CHANGED_TYPE, CHANGED_VALUE, CHANGED_SUPERCLASS,
           CHANGED_SCOPE, CHANGED_ABSTRACT, CHANGED_THROWS, CHANGED_NATIVE, CHANGED_CLASS,
           CHANGED_DEPRECATED, CHANGED_SYNCHRONIZED, ADDED_FINAL_UNINSTANTIABLE, REMOVED_FINAL,
-          BROKEN_SINCE_FILE, INVALID_CONTENT_TYPE, HIDDEN_TYPE_PARAMETER};
+          BROKEN_SINCE_FILE, INVALID_CONTENT_TYPE, HIDDEN_TYPE_PARAMETER, PRIVATE_SUPERCLASS};
 
   public static boolean setErrorLevel(int code, int level) {
     for (Error e : ERRORS) {
diff --git a/src/com/google/doclava/PackageInfo.java b/src/com/google/doclava/PackageInfo.java
index c0f10da..46b5b8f 100644
--- a/src/com/google/doclava/PackageInfo.java
+++ b/src/com/google/doclava/PackageInfo.java
@@ -18,8 +18,8 @@
 
 import com.google.doclava.apicheck.ApiInfo;
 import com.google.clearsilver.jsilver.data.Data;
-
 import com.sun.javadoc.*;
+
 import java.util.*;
 
 public class PackageInfo extends DocInfo implements ContainerInfo {
@@ -394,9 +394,89 @@
     return mClasses;
   }
 
-  public boolean isConsistent(PackageInfo pInfo, Collection<String> ignoredClasses) {
+  public boolean isConsistent(PackageInfo pInfo) {
+    return isConsistent(pInfo, null);
+  }
+
+  /**
+   * Creates the delta class by copying class signatures from original, but use provided list of
+   * constructors and methods.
+   */
+  private ClassInfo createDeltaClass(ClassInfo original,
+      ArrayList<MethodInfo> constructors, ArrayList<MethodInfo> methods) {
+    ArrayList<FieldInfo> emptyFields = new ArrayList<>();
+    ArrayList<ClassInfo> emptyClasses = new ArrayList<>();
+    ArrayList<TypeInfo> emptyTypes = new ArrayList<>();
+    ArrayList<MethodInfo> emptyMethods = new ArrayList<>();
+    ClassInfo ret = new ClassInfo(null, original.getRawCommentText(), original.position(),
+        original.isPublic(), original.isProtected(), original.isPackagePrivate(),
+        original.isPrivate(), original.isStatic(), original.isInterface(),
+        original.isAbstract(), original.isOrdinaryClass(),
+        original.isException(), original.isError(), original.isEnum(), original.isAnnotation(),
+        original.isFinal(), original.isIncluded(), original.name(), original.qualifiedName(),
+        original.qualifiedTypeName(), original.isPrimitive());
+    ArrayList<ClassInfo> interfaces = original.interfaces();
+    // avoid providing null to init method, replace with empty array list when needed
+    if (interfaces == null) {
+      interfaces = emptyClasses;
+    }
+    ArrayList<TypeInfo> interfaceTypes = original.interfaceTypes();
+    if (interfaceTypes == null) {
+      interfaceTypes = emptyTypes;
+    }
+    ArrayList<ClassInfo> innerClasses = original.innerClasses();
+    if (innerClasses == null) {
+      innerClasses = emptyClasses;
+    }
+    ArrayList<MethodInfo> annotationElements = original.annotationElements();
+    if (annotationElements == null) {
+      annotationElements = emptyMethods;
+    }
+    ArrayList<AnnotationInstanceInfo> annotations = original.annotations();
+    if (annotations == null) {
+      annotations = new ArrayList<>();
+    }
+    ret.init(original.type(), interfaces, interfaceTypes, innerClasses,
+        constructors, methods, annotationElements,
+        emptyFields /* fields */, emptyFields /* enum */,
+        original.containingPackage(), original.containingClass(), original.superclass(),
+        original.superclassType(), annotations);
+    return ret;
+  }
+
+  /**
+   * Check if packages are consistent, also record class deltas.
+   * <p>
+   * <ul>class deltas are:
+   * <li>brand new classes that are not present in current package
+   * <li>stripped existing classes stripped where only newly added methods are kept
+   * @param pInfo
+   * @param clsInfoDiff
+   * @return
+   */
+  public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff) {
+      return isConsistent(pInfo, clsInfoDiff, null);
+  }
+
+  /**
+   * Check if packages are consistent, also record class deltas.
+   * <p>
+   * <ul>class deltas are:
+   * <li>brand new classes that are not present in current package
+   * <li>stripped existing classes stripped where only newly added methods are kept
+   * @param pInfo
+   * @param clsInfoDiff
+   * @param ignoredClasses
+   * @return
+   */
+  public boolean isConsistent(PackageInfo pInfo, List<ClassInfo> clsInfoDiff,
+      Collection<String> ignoredClasses) {
     boolean consistent = true;
+    boolean diffMode = clsInfoDiff != null;
     for (ClassInfo cInfo : mClasses.values()) {
+      ArrayList<MethodInfo> newClsApis = null;
+      ArrayList<MethodInfo> newClsCtors = null;
+
       // TODO: Add support for matching inner classes (e.g, something like
       //  example.Type.* should match example.Type.InnerType)
       if (ignoredClasses != null && ignoredClasses.contains(cInfo.qualifiedName())) {
@@ -404,9 +484,19 @@
           continue;
       }
       if (pInfo.mClasses.containsKey(cInfo.name())) {
-        if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()))) {
+        if (diffMode) {
+          newClsApis = new ArrayList<>();
+          newClsCtors = new ArrayList<>();
+        }
+        if (!cInfo.isConsistent(pInfo.mClasses.get(cInfo.name()), newClsCtors, newClsApis)) {
           consistent = false;
         }
+        // if we are in diff mode, add class to list if there's new ctor or new apis
+        if (diffMode && !(newClsCtors.isEmpty() && newClsApis.isEmpty())) {
+          // generate a "delta" class with only added methods and constructors, but no fields etc
+          ClassInfo deltaClsInfo = createDeltaClass(cInfo, newClsCtors, newClsApis);
+          clsInfoDiff.add(deltaClsInfo);
+        }
       } else {
         Errors.error(Errors.REMOVED_CLASS, cInfo.position(), "Removed public class "
             + cInfo.qualifiedName());
@@ -422,12 +512,15 @@
         Errors.error(Errors.ADDED_CLASS, cInfo.position(), "Added class " + cInfo.name()
             + " to package " + pInfo.name());
         consistent = false;
+        // brand new class, add everything as is
+        if (diffMode) {
+            clsInfoDiff.add(cInfo);
+        }
       }
     }
+    if (diffMode) {
+      Collections.sort(clsInfoDiff, ClassInfo.comparator);
+    }
     return consistent;
   }
-
-  public boolean isConsistent(PackageInfo pInfo) {
-    return isConsistent(pInfo, null);
-  }
 }
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index 647c921..5ae1dd0 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -337,6 +337,10 @@
             + " stripped of unavailable superclass " + supr.qualifiedName());
       } else {
         cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() + cl.qualifiedName());
+        if (supr.isPrivate()) {
+          Errors.error(Errors.PRIVATE_SUPERCLASS, cl.position(), "Public class "
+              + cl.qualifiedName() + " extends private class " + supr.qualifiedName());
+        }
       }
     }
   }
diff --git a/src/com/google/doclava/apicheck/ApiCheck.java b/src/com/google/doclava/apicheck/ApiCheck.java
index 9698c89..47a8b5e 100644
--- a/src/com/google/doclava/apicheck/ApiCheck.java
+++ b/src/com/google/doclava/apicheck/ApiCheck.java
@@ -23,11 +23,13 @@
 import java.io.PrintStream;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.Stack;
 
 import com.google.doclava.Errors;
+import com.google.doclava.PackageInfo;
 import com.google.doclava.Errors.ErrorMessage;
 import com.google.doclava.Stubs;
 
@@ -64,6 +66,10 @@
       System.exit(convertToApi(originalArgs[1], originalArgs[2]));
     } else if (originalArgs.length == 3 && "-convert2xml".equals(originalArgs[0])) {
       System.exit(convertToXml(originalArgs[1], originalArgs[2]));
+    } else if (originalArgs.length == 4 && "-new_api".equals(originalArgs[0])) {
+      // command syntax: -new_api oldapi.txt newapi.txt diff.xml
+      // TODO: Support reading in other options for new_api, such as ignored classes/packages.
+      System.exit(newApi(originalArgs[1], originalArgs[2], originalArgs[3]));
     } else {
       ApiCheck acheck = new ApiCheck();
       Report report = acheck.checkApi(originalArgs);
@@ -138,11 +144,11 @@
 
     // only run the consistency check if we haven't had XML parse errors
     if (!Errors.hadError) {
-      oldApi.isConsistent(newApi, ignoredPackages, ignoredClasses);
+      oldApi.isConsistent(newApi, null, ignoredPackages, ignoredClasses);
     }
 
     if (!Errors.hadError) {
-      oldRemovedApi.isConsistent(newRemovedApi, ignoredPackages, ignoredClasses);
+      oldRemovedApi.isConsistent(newRemovedApi, null, ignoredPackages, ignoredClasses);
     }
 
     return new Report(Errors.hadError ? 1 : 0, Errors.getErrors());
@@ -287,4 +293,41 @@
     return 0;
   }
 
+  /**
+   * Generates a "diff": where new API is trimmed down by removing existing methods found in old API
+   * @param origApiPath path to old API text file
+   * @param newApiPath path to new API text file
+   * @param outputPath output XML path for the generated diff
+   * @return
+   */
+  static int newApi(String origApiPath, String newApiPath, String outputPath) {
+    ApiInfo origApi, newApi;
+    try {
+      origApi = parseApi(origApiPath);
+    } catch (ApiParseException e) {
+      e.printStackTrace();
+      System.err.println("Error parsing API: " + origApiPath);
+      return 1;
+    }
+    try {
+      newApi = parseApi(newApiPath);
+    } catch (ApiParseException e) {
+      e.printStackTrace();
+      System.err.println("Error parsing API: " + newApiPath);
+      return 1;
+    }
+    List<PackageInfo> pkgInfoDiff = new ArrayList<>();
+    if (!origApi.isConsistent(newApi, pkgInfoDiff)) {
+      PrintStream apiWriter = null;
+      try {
+        apiWriter = new PrintStream(outputPath);
+      } catch (FileNotFoundException ex) {
+        System.err.println("can't open file: " + outputPath);
+      }
+      Stubs.writeXml(apiWriter, pkgInfoDiff);
+    } else {
+      System.err.println("No API change detected, not generating diff.");
+    }
+    return 0;
+  }
 }
diff --git a/src/com/google/doclava/apicheck/ApiInfo.java b/src/com/google/doclava/apicheck/ApiInfo.java
index 2752f3a..fa51e8b 100644
--- a/src/com/google/doclava/apicheck/ApiInfo.java
+++ b/src/com/google/doclava/apicheck/ApiInfo.java
@@ -19,9 +19,12 @@
 import com.google.doclava.ClassInfo;
 import com.google.doclava.Errors;
 import com.google.doclava.PackageInfo;
+
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 public class ApiInfo {
@@ -59,16 +62,31 @@
 
   /**
    * Checks to see if this api is consistent with a newer version.
+   */
+  public boolean isConsistent(ApiInfo otherApi) {
+    return isConsistent(otherApi, null);
+  }
+
+  public boolean isConsistent(ApiInfo otherApi, List<PackageInfo> pkgInfoDiff) {
+      return isConsistent(otherApi, pkgInfoDiff, null, null);
+  }
+
+  /**
+   * Checks to see if this api is consistent with a newer version.
    *
    * @param otherApi the other api to test consistency against
+   * @param pkgInfoDiff
    * @param ignoredPackages packages to skip consistency checks (will match by exact name)
    * @param ignoredClasses classes to skip consistency checks (will match by exact fully qualified
    * name)
    */
-  public boolean isConsistent(ApiInfo otherApi,
+  public boolean isConsistent(ApiInfo otherApi, List<PackageInfo> pkgInfoDiff,
       Collection<String> ignoredPackages, Collection<String> ignoredClasses) {
     boolean consistent = true;
+    boolean diffMode = pkgInfoDiff != null;
     for (PackageInfo pInfo : mPackages.values()) {
+      List<ClassInfo> newClsApis = null;
+
       // TODO: Add support for matching subpackages (e.g, something like
       // test.example.* should match test.example.subpackage, and
       // test.example.** should match the above AND test.example.subpackage.more)
@@ -77,9 +95,21 @@
           continue;
       }
       if (otherApi.getPackages().containsKey(pInfo.name())) {
-        if (!pInfo.isConsistent(otherApi.getPackages().get(pInfo.name()), ignoredClasses)) {
+        if (diffMode) {
+          newClsApis = new ArrayList<>();
+        }
+        if (!pInfo.isConsistent(otherApi.getPackages().get(pInfo.name()), newClsApis, ignoredClasses)) {
           consistent = false;
         }
+        if (diffMode && !newClsApis.isEmpty()) {
+          PackageInfo info = new PackageInfo(pInfo.name(), pInfo.position());
+          for (ClassInfo cInfo : newClsApis) {
+            if (ignoredClasses == null || !ignoredClasses.contains(cInfo.qualifiedName())) {
+              info.addClass(cInfo);
+            }
+          }
+          pkgInfoDiff.add(info);
+        }
       } else {
         Errors.error(Errors.REMOVED_PACKAGE, pInfo.position(), "Removed package " + pInfo.name());
         consistent = false;
@@ -93,18 +123,17 @@
       if (!mPackages.containsKey(pInfo.name())) {
         Errors.error(Errors.ADDED_PACKAGE, pInfo.position(), "Added package " + pInfo.name());
         consistent = false;
+        if (diffMode) {
+          pkgInfoDiff.add(pInfo);
+        }
       }
     }
+    if (diffMode) {
+      Collections.sort(pkgInfoDiff, PackageInfo.comparator);
+    }
     return consistent;
   }
 
-  /**
-   * Checks to see if this api is consistent with a newer version.
-   */
-  public boolean isConsistent(ApiInfo otherApi) {
-    return isConsistent(otherApi, null, null);
-  }
-
   public HashMap<String, PackageInfo> getPackages() {
     return mPackages;
   }