DO NOT MERGE: Merge commit '2f6f6abe4d05282c82dd56fa897949f49ac3cd6c'
from oc-support-26.0-dev to stage-aosp-master.

Bug: 64219148
Test: build
Change-Id: I3a43d851a95d17df6b33ee416df178fdf494e811
diff --git a/res/assets/templates-sdk/macros_override.cs b/res/assets/templates-sdk/macros_override.cs
index 92be480..453eb59 100644
--- a/res/assets/templates-sdk/macros_override.cs
+++ b/res/assets/templates-sdk/macros_override.cs
@@ -106,7 +106,7 @@
   if subcount(tag.values) > 1 ?> permissions.<?cs
   else ?> permission.<?cs
   /if ?><?cs
-/def ?>
+/def ?><?cs
 
 # Print output for @service tags ?><?cs
 def:dump_service(tag) ?>Instances of this class must be obtained using <?cs
diff --git a/src/com/google/doclava/AnnotationInstanceInfo.java b/src/com/google/doclava/AnnotationInstanceInfo.java
index d353426..07ffd9d 100644
--- a/src/com/google/doclava/AnnotationInstanceInfo.java
+++ b/src/com/google/doclava/AnnotationInstanceInfo.java
@@ -20,6 +20,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 
 public class AnnotationInstanceInfo implements Resolvable {
   private ClassInfo mType;
@@ -146,19 +147,40 @@
 
   /**
    * Get a new list containing the set of annotations that are shared between
-   * the input annotations collection and the names of annotations passed in
-   * the showAnnotations parameter
+   * the input annotations collection and the set of allowed annotations.
    */
-  public static ArrayList<AnnotationInstanceInfo> getShowAnnotationsIntersection(
-          ArrayList<AnnotationInstanceInfo> annotations) {
+  public static ArrayList<AnnotationInstanceInfo> getAnnotationsIntersection(
+          Collection<String> allowedAnnotations,
+          Collection<? extends AnnotationInstanceInfo> allAnnotations) {
     ArrayList<AnnotationInstanceInfo> list = new ArrayList<AnnotationInstanceInfo>();
-    if (annotations != null) {
-      for (AnnotationInstanceInfo info : annotations) {
-        if (Doclava.showAnnotations.contains(info.type().qualifiedName())) {
+    java.util.Objects.requireNonNull(allowedAnnotations);
+    if (allAnnotations != null) {
+      for (AnnotationInstanceInfo info : allAnnotations) {
+        if (allowedAnnotations.contains(info.type().qualifiedName())) {
           list.add(info);
         }
       }
     }
     return list;
   }
+
+  /**
+   * Get a new list containing the set of annotations that are shared between
+   * the input annotations collection and the names of annotations passed in
+   * the showAnnotations parameter
+   */
+  public static ArrayList<AnnotationInstanceInfo> getShowAnnotationsIntersection(
+          Collection<? extends AnnotationInstanceInfo> annotations) {
+    return getAnnotationsIntersection(Doclava.showAnnotations, annotations);
+  }
+
+  /**
+   * Get a new list containing the set of annotations that are shared between
+   * the input annotations collection and the names of annotations passed in
+   * the hideAnnotations parameter
+   */
+  public static ArrayList<AnnotationInstanceInfo> getHideAnnotationsIntersection(
+          Collection<? extends AnnotationInstanceInfo> annotations) {
+    return getAnnotationsIntersection(Doclava.hideAnnotations, annotations);
+  }
 }
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
index 20addc6..b152508 100644
--- a/src/com/google/doclava/ClassInfo.java
+++ b/src/com/google/doclava/ClassInfo.java
@@ -135,6 +135,7 @@
     mIsPrimitive = isPrimitive;
     mAnnotations = annotations;
     mShowAnnotations = AnnotationInstanceInfo.getShowAnnotationsIntersection(annotations);
+    mHideAnnotations = AnnotationInstanceInfo.getHideAnnotationsIntersection(annotations);
   }
 
   public void init(TypeInfo typeInfo, ArrayList<ClassInfo> interfaces,
@@ -167,6 +168,7 @@
     mRealSuperclassType = superclassType;
     mAnnotations = annotations;
     mShowAnnotations = AnnotationInstanceInfo.getShowAnnotationsIntersection(annotations);
+    mHideAnnotations = AnnotationInstanceInfo.getHideAnnotationsIntersection(annotations);
 
     // after providing new methods and new superclass info,clear any cached
     // lists of self + superclass methods, ctors, etc.
@@ -1471,8 +1473,8 @@
   }
 
   /**
-   * @return true if the containing package has @hide comment, or an ancestor
-   * class of this class is hidden, or this class has @hide comment.
+   * @return true if the containing package has @hide comment, a hide annotaion,
+   * or a containing class of this class is hidden.
    */
   public boolean isHiddenImpl() {
     ClassInfo cl = this;
@@ -1484,7 +1486,7 @@
       if (pkg != null && pkg.hasHideComment()) {
         return true;
       }
-      if (cl.comment().isHidden()) {
+      if (cl.comment().isHidden() || cl.hasHideAnnotation()) {
         return true;
       }
       cl = cl.containingClass();
@@ -1536,6 +1538,14 @@
     return mShowAnnotations;
   }
 
+  public boolean hasHideAnnotation() {
+    return mHideAnnotations != null && mHideAnnotations.size() > 0;
+  }
+
+  public ArrayList<AnnotationInstanceInfo> hideAnnotations() {
+    return mHideAnnotations;
+  }
+
   public ArrayList<AnnotationInstanceInfo> getShowAnnotationsIncludeOuters() {
     ArrayList<AnnotationInstanceInfo> allAnnotations = new ArrayList<AnnotationInstanceInfo>();
     ClassInfo cl = this;
@@ -1823,6 +1833,7 @@
   private ClassInfo mSuperclass;
   private ArrayList<AnnotationInstanceInfo> mAnnotations;
   private ArrayList<AnnotationInstanceInfo> mShowAnnotations;
+  private ArrayList<AnnotationInstanceInfo> mHideAnnotations;
   private boolean mSuperclassInit;
   private boolean mDeprecatedKnown;
 
diff --git a/src/com/google/doclava/Converter.java b/src/com/google/doclava/Converter.java
index ee91960..0d4e455 100644
--- a/src/com/google/doclava/Converter.java
+++ b/src/com/google/doclava/Converter.java
@@ -16,7 +16,7 @@
 
 package com.google.doclava;
 
-
+import com.google.doclava.apicheck.ApiInfo;
 import com.sun.javadoc.AnnotationDesc;
 import com.sun.javadoc.AnnotationTypeDoc;
 import com.sun.javadoc.AnnotationTypeElementDoc;
@@ -42,12 +42,15 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 
 public class Converter {
   private static RootDoc root;
+  private static List<ApiInfo> apis;
 
   public static void makeInfo(RootDoc r) {
     root = r;
+    apis = new ArrayList<>();
 
     // create the objects
     ClassDoc[] classes = getClasses(r);
@@ -78,6 +81,15 @@
     mRootClasses = Converter.convertClasses(classes);
   }
 
+  /**
+   * Adds additional APIs to be available from calls to obtain() methods.
+   *
+   * @param api the APIs to add, must be non-{@code null}
+   */
+  public static void addApiInfo(ApiInfo api) {
+    apis.add(api);
+  }
+
   private static ClassDoc[] getClasses(RootDoc r) {
     ClassDoc[] classDocs = r.classes();
     ArrayList<ClassDoc> filtered = new ArrayList<ClassDoc>(classDocs.length);
@@ -155,12 +167,54 @@
                     Converter.convertClasses(c.innerClasses(false)))));
   }
 
+  /**
+   * Obtains a {@link ClassInfo} describing the specified class. If the class
+   * was not specified on the source path, this method will attempt to locate
+   * the class within the list of federated APIs.
+   *
+   * @param className the fully-qualified class name to search for
+   * @return info for the specified class, or {@code null} if not available
+   * @see #addApiInfo(ApiInfo)
+   */
   public static ClassInfo obtainClass(String className) {
-    return Converter.obtainClass(root.classNamed(className));
+    ClassInfo result = Converter.obtainClass(root.classNamed(className));
+    if (result != null) {
+      return result;
+    }
+
+    for (ApiInfo api : apis) {
+      result = api.findClass(className);
+      if (result != null) {
+        return result;
+      }
+    }
+
+    return null;
   }
 
+  /**
+   * Obtains a {@link PackageInfo} describing the specified package. If the
+   * package was not specified on the source path, this method will attempt to
+   * locate the package within the list of federated APIs.
+   *
+   * @param packageName the fully-qualified package name to search for
+   * @return info for the specified package, or {@code null} if not available
+   * @see #addApiInfo(ApiInfo)
+   */
   public static PackageInfo obtainPackage(String packageName) {
-    return Converter.obtainPackage(root.packageNamed(packageName));
+    PackageInfo result = Converter.obtainPackage(root.packageNamed(packageName));
+    if (result != null) {
+      return result;
+    }
+
+    for (ApiInfo api : apis) {
+      result = api.getPackages().get(packageName);
+      if (result != null) {
+        return result;
+      }
+    }
+
+    return null;
   }
 
   private static TagInfo convertTag(Tag tag) {
@@ -456,7 +510,7 @@
         }
         // End of workaround.
         MethodInfo result =
-            new MethodInfo(m.getRawCommentText(), new ArrayList<TypeInfo>(Arrays.asList(Converter.convertTypes(m.typeParameters()))), 
+            new MethodInfo(m.getRawCommentText(), new ArrayList<TypeInfo>(Arrays.asList(Converter.convertTypes(m.typeParameters()))),
                 name, m.signature(), Converter.obtainClass(m.containingClass()), Converter
                 .obtainClass(m.containingClass()), m.isPublic(), m.isProtected(), m
                 .isPackagePrivate(), m.isPrivate(), m.isFinal(), m.isStatic(), m.isSynthetic(),
diff --git a/src/com/google/doclava/Doclava.java b/src/com/google/doclava/Doclava.java
index 6d26d80..15789e5 100644
--- a/src/com/google/doclava/Doclava.java
+++ b/src/com/google/doclava/Doclava.java
@@ -104,6 +104,7 @@
   public static HashSet<String> knownTags = new HashSet<String>();
   public static FederationTagger federationTagger = new FederationTagger();
   public static Set<String> showAnnotations = new HashSet<String>();
+  public static Set<String> hideAnnotations = new HashSet<String>();
   public static boolean showAnnotationOverridesVisibility = false;
   public static Set<String> hiddenPackages = new HashSet<String>();
   public static boolean includeAssets = true;
@@ -182,6 +183,8 @@
     String exactApiFile = null;
     String debugStubsFile = "";
     HashSet<String> stubPackages = null;
+    HashSet<String> stubImportPackages = null;
+    boolean stubSourceOnly = false;
     ArrayList<String> knownTagsFiles = new ArrayList<String>();
 
     root = r;
@@ -251,6 +254,8 @@
         keepListFile = a[1];
       } else if (a[0].equals("-showAnnotation")) {
         showAnnotations.add(a[1]);
+      } else if (a[0].equals("-hideAnnotation")) {
+        hideAnnotations.add(a[1]);
       } else if (a[0].equals("-showAnnotationOverridesVisibility")) {
         showAnnotationOverridesVisibility = true;
       } else if (a[0].equals("-hidePackage")) {
@@ -278,6 +283,14 @@
         for (String pkg : a[1].split(":")) {
           stubPackages.add(pkg);
         }
+      } else if (a[0].equals("-stubimportpackages")) {
+        stubImportPackages = new HashSet<String>();
+        for (String pkg : a[1].split(":")) {
+          stubImportPackages.add(pkg);
+          hiddenPackages.add(pkg);
+        }
+      } else if (a[0].equals("-stubsourceonly")) {
+        stubSourceOnly = true;
       } else if (a[0].equals("-sdkvalues")) {
         sdkValuePath = a[1];
       } else if (a[0].equals("-api")) {
@@ -412,6 +425,11 @@
 
       // don't do ref doc tasks in devsite static-only builds
       if (!DEVSITE_STATIC_ONLY) {
+        // Load additional data structures from federated sites.
+        for(FederatedSite site : federationTagger.getSites()) {
+          Converter.addApiInfo(site.apiInfo());
+        }
+
         // Apply @since tags from the XML file
         sinceTagger.tagAll(Converter.rootClasses());
 
@@ -509,7 +527,9 @@
     if (stubsDir != null || apiFile != null || proguardFile != null || removedApiFile != null
         || exactApiFile != null) {
       Stubs.writeStubsAndApi(stubsDir, apiFile, proguardFile, removedApiFile, exactApiFile,
-          stubPackages);
+          stubPackages,
+          stubImportPackages,
+          stubSourceOnly);
     }
 
     Errors.printErrors();
@@ -780,6 +800,12 @@
     if (option.equals("-stubpackages")) {
       return 2;
     }
+    if (option.equals("-stubimportpackages")) {
+      return 2;
+    }
+    if (option.equals("-stubsourceonly")) {
+      return 1;
+    }
     if (option.equals("-sdkvalues")) {
       return 2;
     }
diff --git a/src/com/google/doclava/FederationTagger.java b/src/com/google/doclava/FederationTagger.java
index f3603a5..a83bb20 100644
--- a/src/com/google/doclava/FederationTagger.java
+++ b/src/com/google/doclava/FederationTagger.java
@@ -29,22 +29,46 @@
  * against when overlapping content is discovered.
  */
 public final class FederationTagger {
-  private final Map<String, URL> federatedUrls = new HashMap<String, URL>();
-  private final Map<String, String> federatedXmls = new HashMap<String, String>();
-  private final List<FederatedSite> federatedSites = new ArrayList<FederatedSite>();
+  private final Map<String, URL> federatedUrls = new HashMap<>();
+  private final Map<String, String> federatedXmls = new HashMap<>();
+  private final List<FederatedSite> federatedSites = new ArrayList<>();
+
   private boolean initialized = false;
+
   /**
    * Adds a Doclava documentation site for federation. Accepts the base URL of
    * the remote API.
+   * <p>
+   * If {@link #addSiteApi(String, String)} is not called, this will default to
+   * reading the API from "/xml/current.xml" within the site's base URL.
+   * <p>
+   * <strong>Note:</strong> Must be called before calling tag() or get() methods.
+   *
+   * @param name internally-used name for federation site
    */
   public void addSiteUrl(String name, URL site) {
+    if (initialized) {
+      throw new IllegalStateException("Cannot add sites after calling tag() or get() methods.");
+    }
     federatedUrls.put(name, site);
   }
-  
+
+  /**
+   * Adds an explicit Doclava-generated API file for the specified site.
+   * <p>
+   * <strong>Note:</strong> Must be called before calling tag() or get() methods.
+   *
+   * @param name internally-used name for federation site (must match name used
+   *             for {@link #addSiteUrl(String, URL)})
+   * @param file path to a Doclava-generated API file
+   */
   public void addSiteApi(String name, String file) {
+    if (initialized) {
+      throw new IllegalStateException("Cannot add sites after calling tag() or get() methods.");
+    }
     federatedXmls.put(name, file);
   }
-  
+
   public void tag(ClassInfo classDoc) {
     initialize();
     for (FederatedSite site : federatedSites) {
@@ -58,19 +82,28 @@
       applyFederation(site, classDocs);
     }
   }
-  
+
+  /**
+   * Returns a non-{@code null} list of {@link FederatedSite} objects, one for
+   * each unique {@code name} added using {@link #addSiteUrl(String, URL)}.
+   */
+  public List<FederatedSite> getSites() {
+    initialize();
+    return federatedSites;
+  }
+
   private void initialize() {
     if (initialized) {
       return;
     }
-    
+
     for (String name : federatedXmls.keySet()) {
       if (!federatedUrls.containsKey(name)) {
         Errors.error(Errors.NO_FEDERATION_DATA, (SourcePositionInfo) null,
             "Unknown documentation site for " + name);
       }
     }
-    
+
     for (String name : federatedUrls.keySet()) {
       try {
         if (federatedXmls.containsKey(name)) {
@@ -87,10 +120,10 @@
         Errors.error(Errors.NO_FEDERATION_DATA, (SourcePositionInfo) null, error);
       }
     }
-    
+
     initialized = true;
   }
-  
+
   private void applyFederation(FederatedSite federationSource, ClassInfo[] classDocs) {
     for (ClassInfo classDoc : classDocs) {
       PackageInfo packageSpec
@@ -101,11 +134,11 @@
       }
 
       ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name());
-      
+
       if (classSpec == null) {
         continue;
       }
-      
+
       federateMethods(federationSource, classSpec, classDoc);
       federateConstructors(federationSource, classSpec, classDoc);
       federateFields(federationSource, classSpec, classDoc);
@@ -124,7 +157,7 @@
       }
     }
   }
-  
+
   private void federateConstructors(FederatedSite site, ClassInfo federatedClass,
       ClassInfo localClass) {
     for (MethodInfo constructor : localClass.constructors()) {
@@ -133,7 +166,7 @@
       }
     }
   }
-  
+
   private void federateFields(FederatedSite site, ClassInfo federatedClass, ClassInfo localClass) {
     for (FieldInfo field : localClass.fields()) {
       if (federatedClass.allFields().containsKey(field.name())) {
@@ -141,12 +174,12 @@
       }
     }
   }
-  
+
   private void federateClass(FederatedSite source, ClassInfo doc) {
     doc.addFederatedReference(source);
   }
-  
+
   private void federatePackage(FederatedSite source, PackageInfo pkg) {
     pkg.addFederatedReference(source);
   }
-}
\ No newline at end of file
+}
diff --git a/src/com/google/doclava/MemberInfo.java b/src/com/google/doclava/MemberInfo.java
index 6c5aad3..8c88648 100644
--- a/src/com/google/doclava/MemberInfo.java
+++ b/src/com/google/doclava/MemberInfo.java
@@ -39,6 +39,7 @@
     mKind = kind;
     mAnnotations = annotations;
     mShowAnnotations = AnnotationInstanceInfo.getShowAnnotationsIntersection(annotations);
+    mHideAnnotations = AnnotationInstanceInfo.getHideAnnotationsIntersection(annotations);
   }
 
   public abstract boolean isExecutable();
@@ -48,7 +49,7 @@
     if (mShowAnnotations.size() > 0) {
       return false;
     }
-    return super.isHidden();
+    return super.isHidden() || mHideAnnotations.size() > 0;
   }
 
   @Override
@@ -177,6 +178,10 @@
     return mShowAnnotations;
   }
 
+  public ArrayList<AnnotationInstanceInfo> hideAnnotations() {
+    return mHideAnnotations;
+  }
+
   ClassInfo mContainingClass;
   ClassInfo mRealContainingClass;
   String mName;
@@ -191,5 +196,6 @@
   String mKind;
   private ArrayList<AnnotationInstanceInfo> mAnnotations;
   private ArrayList<AnnotationInstanceInfo> mShowAnnotations;
+  private ArrayList<AnnotationInstanceInfo> mHideAnnotations;
 
 }
diff --git a/src/com/google/doclava/MethodInfo.java b/src/com/google/doclava/MethodInfo.java
index 47b1978..e5761c1 100644
--- a/src/com/google/doclava/MethodInfo.java
+++ b/src/com/google/doclava/MethodInfo.java
@@ -28,6 +28,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Queue;
+import java.util.function.Predicate;
 
 public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolvable {
   public static final Comparator<MethodInfo> comparator = new Comparator<MethodInfo>() {
@@ -108,7 +109,7 @@
     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
     if (containingClass().realSuperclass() != null
         && containingClass().realSuperclass().isAbstract()) {
-      queue.add(containingClass());
+      queue.add(containingClass().realSuperclass());
     }
     addInterfaces(containingClass().realInterfaces(), queue);
     for (ClassInfo iface : queue) {
@@ -123,17 +124,13 @@
     return null;
   }
 
-  public MethodInfo findSuperclassImplementation(HashSet notStrippable) {
+  public MethodInfo findPredicateOverriddenMethod(Predicate<MethodInfo> predicate) {
     if (mReturnType == null) {
       // ctor
       return null;
     }
     if (mOverriddenMethod != null) {
-      // Even if we're told outright that this was the overridden method, we want to
-      // be conservative and ignore mismatches of parameter types -- they arise from
-      // extending generic specializations, and we want to consider the derived-class
-      // method to be a non-override.
-      if (this.signature().equals(mOverriddenMethod.signature())) {
+      if (predicate.test(mOverriddenMethod)) {
         return mOverriddenMethod;
       }
     }
@@ -141,13 +138,12 @@
     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
     if (containingClass().realSuperclass() != null
         && containingClass().realSuperclass().isAbstract()) {
-      queue.add(containingClass());
+      queue.add(containingClass().realSuperclass());
     }
     addInterfaces(containingClass().realInterfaces(), queue);
     for (ClassInfo iface : queue) {
       for (MethodInfo me : iface.methods()) {
-        if (me.name().equals(this.name()) && me.signature().equals(this.signature())
-            && notStrippable.contains(me.containingClass())) {
+        if (predicate.test(me)) {
           return me;
         }
       }
@@ -167,7 +163,7 @@
     ArrayList<ClassInfo> queue = new ArrayList<ClassInfo>();
     if (containingClass().realSuperclass() != null
         && containingClass().realSuperclass().isAbstract()) {
-      queue.add(containingClass());
+      queue.add(containingClass().realSuperclass());
     }
     addInterfaces(containingClass().realInterfaces(), queue);
     for (ClassInfo iface : queue) {
@@ -893,6 +889,8 @@
           + " to " + mInfo.scope());
     }
 
+    // Changing the deprecated annotation is binary- and source-compatible, but
+    // we still need to log the API change.
     if (!isDeprecated() == mInfo.isDeprecated()) {
       Errors.error(Errors.CHANGED_DEPRECATED, mInfo.position(), "Method "
           + mInfo.prettyQualifiedSignature() + " has changed deprecation state " + isDeprecated()
@@ -900,16 +898,14 @@
       consistent = false;
     }
 
-    // see JLS 3 13.4.20 "Adding or deleting a synchronized modifier of a method does not break "
-    // "compatibility with existing binaries."
-    /*
+    // Changing the synchronized modifier is binary- and source-compatible (see
+    // JLS 3 13.4.20), but we still need to log the API change.
     if (mIsSynchronized != mInfo.mIsSynchronized) {
       Errors.error(Errors.CHANGED_SYNCHRONIZED, mInfo.position(), "Method " + mInfo.qualifiedName()
           + " has changed 'synchronized' qualifier from " + mIsSynchronized + " to "
           + mInfo.mIsSynchronized);
       consistent = false;
     }
-    */
 
     for (ClassInfo exception : thrownExceptions()) {
       if (!mInfo.throwsException(exception)) {
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index fbcff97..59ca505 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -18,6 +18,7 @@
 
 import java.io.BufferedOutputStream;
 import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -27,6 +28,8 @@
 import java.io.InputStreamReader;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -43,7 +46,9 @@
 
 public class Stubs {
   public static void writeStubsAndApi(String stubsDir, String apiFile, String keepListFile,
-      String removedApiFile, String exactApiFile, HashSet<String> stubPackages) {
+      String removedApiFile, String exactApiFile, HashSet<String> stubPackages,
+      HashSet<String> stubImportPackages,
+      boolean stubSourceOnly) {
     // figure out which classes we need
     final HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
     ClassInfo[] all = Converter.allClasses();
@@ -94,11 +99,11 @@
             "Cannot open file for write");
       }
     }
-    // If a class is public or protected, not hidden, and marked as included,
+    // If a class is public or protected, not hidden, not imported and marked as included,
     // then we can't strip it
     for (ClassInfo cl : all) {
       if (cl.checkLevel() && cl.isIncluded()) {
-        cantStripThis(cl, notStrippable, "0:0");
+        cantStripThis(cl, notStrippable, "0:0", stubImportPackages);
       }
     }
 
@@ -117,7 +122,7 @@
                 + m.name() + " is deprecated");
           }
 
-          ClassInfo hiddenClass = findHiddenClasses(m.returnType());
+          ClassInfo hiddenClass = findHiddenClasses(m.returnType(), stubImportPackages);
           if (null != hiddenClass) {
             if (hiddenClass.qualifiedName() == m.returnType().asClassInfo().qualifiedName()) {
               // Return type is hidden
@@ -134,7 +139,7 @@
           for (ParameterInfo p :  m.parameters()) {
             TypeInfo t = p.type();
             if (!t.isPrimitive()) {
-              hiddenClass = findHiddenClasses(t);
+              hiddenClass = findHiddenClasses(t, stubImportPackages);
               if (null != hiddenClass) {
                 if (hiddenClass.qualifiedName() == t.asClassInfo().qualifiedName()) {
                   // Parameter type is hidden
@@ -187,6 +192,9 @@
     final HashSet<Pattern> stubPackageWildcards = extractWildcards(stubPackages);
     for (ClassInfo cl : notStrippable) {
       if (!cl.isDocOnly()) {
+        if (stubSourceOnly && !Files.exists(Paths.get(cl.position().file))) {
+          continue;
+        }
         if (shouldWriteStub(cl.containingPackage().name(), stubPackages, stubPackageWildcards)) {
           // write out the stubs
           if (stubsDir != null) {
@@ -282,15 +290,34 @@
     return wildcards;
   }
 
-  private static ClassInfo findHiddenClasses(TypeInfo ti) {
+  /**
+   * Find references to hidden classes.
+   *
+   * <p>This finds hidden classes that are used by public parts of the API in order to ensure the
+   * API is self consistent and does not reference classes that are not included in
+   * the stubs. Any such references cause an error to be reported.
+   *
+   * <p>A reference to an imported class is not treated as an error, even though imported classes
+   * are hidden from the stub generation. That is because imported classes are, by definition,
+   * excluded from the set of classes for which stubs are required.
+   *
+   * @param ti the type information to examine for references to hidden classes.
+   * @param stubImportPackages the possibly null set of imported package names.
+   * @return a reference to a hidden class or null if there are none
+   */
+  private static ClassInfo findHiddenClasses(TypeInfo ti, HashSet<String> stubImportPackages) {
     ClassInfo ci = ti.asClassInfo();
     if (ci == null) return null;
+    if (stubImportPackages != null
+        && stubImportPackages.contains(ci.containingPackage().qualifiedName())) {
+      return null;
+    }
     if (ci.isHiddenOrRemoved()) return ci;
     if (ti.typeArguments() != null) {
       for (TypeInfo tii : ti.typeArguments()) {
         // Avoid infinite recursion in the case of Foo<T extends Foo>
         if (tii.qualifiedTypeName() != ti.qualifiedTypeName()) {
-          ClassInfo hiddenClass = findHiddenClasses(tii);
+          ClassInfo hiddenClass = findHiddenClasses(tii, stubImportPackages);
           if (hiddenClass != null) return hiddenClass;
         }
       }
@@ -298,7 +325,14 @@
     return null;
   }
 
-  public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why) {
+  public static void cantStripThis(ClassInfo cl, HashSet<ClassInfo> notStrippable, String why,
+      HashSet<String> stubImportPackages) {
+
+    if (stubImportPackages != null
+        && stubImportPackages.contains(cl.containingPackage().qualifiedName())) {
+      // if the package is imported then it does not need stubbing.
+      return;
+    }
 
     if (!notStrippable.add(cl)) {
       // slight optimization: if it already contains cl, it already contains
@@ -318,12 +352,14 @@
       for (FieldInfo fInfo : cl.selfFields()) {
         if (fInfo.type() != null) {
           if (fInfo.type().asClassInfo() != null) {
-            cantStripThis(fInfo.type().asClassInfo(), notStrippable, "2:" + cl.qualifiedName());
+            cantStripThis(fInfo.type().asClassInfo(), notStrippable, "2:" + cl.qualifiedName(),
+                stubImportPackages);
           }
           if (fInfo.type().typeArguments() != null) {
             for (TypeInfo tTypeInfo : fInfo.type().typeArguments()) {
               if (tTypeInfo.asClassInfo() != null) {
-                cantStripThis(tTypeInfo.asClassInfo(), notStrippable, "3:" + cl.qualifiedName());
+                cantStripThis(tTypeInfo.asClassInfo(), notStrippable, "3:" + cl.qualifiedName(),
+                    stubImportPackages);
               }
             }
           }
@@ -335,7 +371,8 @@
       if (cl.asTypeInfo().typeArguments() != null) {
         for (TypeInfo tInfo : cl.asTypeInfo().typeArguments()) {
           if (tInfo.asClassInfo() != null) {
-            cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName());
+            cantStripThis(tInfo.asClassInfo(), notStrippable, "4:" + cl.qualifiedName(),
+                stubImportPackages);
           }
         }
       }
@@ -343,11 +380,12 @@
     // cant strip any of the annotation elements
     // cantStripThis(cl.annotationElements(), notStrippable);
     // take care of methods
-    cantStripThis(cl.allSelfMethods(), notStrippable);
-    cantStripThis(cl.allConstructors(), notStrippable);
+    cantStripThis(cl.allSelfMethods(), notStrippable, stubImportPackages);
+    cantStripThis(cl.allConstructors(), notStrippable, stubImportPackages);
     // blow the outer class open if this is an inner class
     if (cl.containingClass() != null) {
-      cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName());
+      cantStripThis(cl.containingClass(), notStrippable, "5:" + cl.qualifiedName(),
+          stubImportPackages);
     }
     // blow open super class and interfaces
     ClassInfo supr = cl.realSuperclass();
@@ -365,7 +403,8 @@
         Errors.error(Errors.HIDDEN_SUPERCLASS, cl.position(), "Public class " + cl.qualifiedName()
             + " stripped of unavailable superclass " + supr.qualifiedName());
       } else {
-        cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() + cl.qualifiedName());
+        cantStripThis(supr, notStrippable, "6:" + cl.realSuperclass().name() + cl.qualifiedName(),
+            stubImportPackages);
         if (supr.isPrivate()) {
           Errors.error(Errors.PRIVATE_SUPERCLASS, cl.position(), "Public class "
               + cl.qualifiedName() + " extends private class " + supr.qualifiedName());
@@ -374,7 +413,8 @@
     }
   }
 
-  private static void cantStripThis(ArrayList<MethodInfo> mInfos, HashSet<ClassInfo> notStrippable) {
+  private static void cantStripThis(ArrayList<MethodInfo> mInfos, HashSet<ClassInfo> notStrippable,
+      HashSet<String> stubImportPackages) {
     // for each method, blow open the parameters, throws and return types. also blow open their
     // generics
     if (mInfos != null) {
@@ -383,7 +423,8 @@
           for (TypeInfo tInfo : mInfo.getTypeParameters()) {
             if (tInfo.asClassInfo() != null) {
               cantStripThis(tInfo.asClassInfo(), notStrippable, "8:"
-                  + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+                  + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+                  stubImportPackages);
             }
           }
         }
@@ -391,7 +432,8 @@
           for (ParameterInfo pInfo : mInfo.parameters()) {
             if (pInfo.type() != null && pInfo.type().asClassInfo() != null) {
               cantStripThis(pInfo.type().asClassInfo(), notStrippable, "9:"
-                  + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+                  + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+                  stubImportPackages);
               if (pInfo.type().typeArguments() != null) {
                 for (TypeInfo tInfoType : pInfo.type().typeArguments()) {
                   if (tInfoType.asClassInfo() != null) {
@@ -404,7 +446,8 @@
                                   + "()");
                     } else {
                       cantStripThis(tcl, notStrippable, "10:"
-                          + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+                          + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+                          stubImportPackages);
                     }
                   }
                 }
@@ -414,16 +457,18 @@
         }
         for (ClassInfo thrown : mInfo.thrownExceptions()) {
           cantStripThis(thrown, notStrippable, "11:" + mInfo.realContainingClass().qualifiedName()
-              + ":" + mInfo.name());
+              + ":" + mInfo.name(), stubImportPackages);
         }
         if (mInfo.returnType() != null && mInfo.returnType().asClassInfo() != null) {
           cantStripThis(mInfo.returnType().asClassInfo(), notStrippable, "12:"
-              + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+              + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+              stubImportPackages);
           if (mInfo.returnType().typeArguments() != null) {
             for (TypeInfo tyInfo : mInfo.returnType().typeArguments()) {
               if (tyInfo.asClassInfo() != null) {
                 cantStripThis(tyInfo.asClassInfo(), notStrippable, "13:"
-                    + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name());
+                    + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(),
+                    stubImportPackages);
               }
             }
           }
@@ -811,40 +856,32 @@
         || !field.type().dimension().equals("") || field.containingClass().isInterface();
   }
 
-  // Returns 'true' if the method is an @Override of a visible parent
-  // method implementation, and thus does not affect the API.
+  /**
+   * Test if the given method has a concrete implementation in a superclass or
+   * interface that has no differences in its public API representation.
+   *
+   * @return {@code true} if the tested method can be safely elided from the
+   *         public API to conserve space.
+   */
   static boolean methodIsOverride(HashSet<ClassInfo> notStrippable, MethodInfo mi) {
     // Abstract/static/final methods are always listed in the API description
     if (mi.isAbstract() || mi.isStatic() || mi.isFinal()) {
       return false;
     }
 
-    // Find any relevant ancestor declaration and inspect it
-    MethodInfo om = mi;
-    do {
-      MethodInfo superMethod = om.findSuperclassImplementation(notStrippable);
-      if (om.equals(superMethod)) {
-        break;
-      }
-      om = superMethod;
-    } while (om != null && (om.isHiddenOrRemoved() || om.containingClass().isHiddenOrRemoved()));
-    if (om != null) {
-      // Visibility mismatch is an API change, so check for it
-      if (mi.mIsPrivate == om.mIsPrivate && mi.mIsPublic == om.mIsPublic
-          && mi.mIsProtected == om.mIsProtected) {
-        // Look only for overrides of an ancestor class implementation,
-        // not of e.g. an abstract or interface method declaration
-        if (!om.isAbstract()) {
-          // If the only "override" turns out to be in our own class
-          // (which sometimes happens in concrete subclasses of
-          // abstract base classes), it's not really an override
-          if (!mi.mContainingClass.equals(om.mContainingClass)) {
-                return true;
-          }
+    final String api = writeMethodApiWithoutDefault(mi);
+    final MethodInfo overridden = mi.findPredicateOverriddenMethod(new Predicate<MethodInfo>() {
+      @Override
+      public boolean test(MethodInfo test) {
+        if (test.isHiddenOrRemoved() || test.containingClass().isHiddenOrRemoved()) {
+          return false;
         }
+
+        final String testApi = writeMethodApiWithoutDefault(test);
+        return api.equals(testApi);
       }
-    }
-    return false;
+    });
+    return (overridden != null);
   }
 
   static boolean canCallMethod(ClassInfo from, MethodInfo m) {
@@ -1573,10 +1610,20 @@
     apiWriter.print(";\n");
   }
 
+  static String writeMethodApiWithoutDefault(MethodInfo mi) {
+    final ByteArrayOutputStream out = new ByteArrayOutputStream();
+    writeMethodApi(new PrintStream(out), mi, false);
+    return out.toString();
+  }
+
   static void writeMethodApi(PrintStream apiWriter, MethodInfo mi) {
+    writeMethodApi(apiWriter, mi, true);
+  }
+
+  static void writeMethodApi(PrintStream apiWriter, MethodInfo mi, boolean withDefault) {
     apiWriter.print("    method ");
     apiWriter.print(mi.scope());
-    if (mi.isDefault()) {
+    if (mi.isDefault() && withDefault) {
       apiWriter.print(" default");
     }
     if (mi.isStatic()) {
diff --git a/src/com/google/doclava/TypeInfo.java b/src/com/google/doclava/TypeInfo.java
index 7bba560..7e971c4 100644
--- a/src/com/google/doclava/TypeInfo.java
+++ b/src/com/google/doclava/TypeInfo.java
@@ -24,7 +24,7 @@
   public static final Set<String> PRIMITIVE_TYPES = Collections.unmodifiableSet(
       new HashSet<String>(Arrays.asList("boolean", "byte", "char", "double", "float", "int",
       "long", "short", "void")));
-  
+
   public TypeInfo(boolean isPrimitive, String dimension, String simpleTypeName,
       String qualifiedTypeName, ClassInfo cl) {
     mIsPrimitive = isPrimitive;
@@ -107,7 +107,7 @@
       typeString = typeString.substring(0, extendsPos);
     }
 
-    int pos = typeString.indexOf('['); 
+    int pos = typeString.indexOf('[');
     if (pos > -1) {
       mDimension = typeString.substring(pos);
       typeString = typeString.substring(0, pos);
@@ -153,7 +153,17 @@
     mFullName = other.fullName();
   }
 
+  /**
+   * Returns this type as a {@link ClassInfo} if it represents a class or
+   * interface.
+   */
   public ClassInfo asClassInfo() {
+    if (!mResolvedClass) {
+      mResolvedClass = true;
+      if (mClass == null && !mIsPrimitive && !mIsTypeVariable && !mIsWildcard) {
+        mClass = Converter.obtainClass(qualifiedTypeName());
+      }
+    }
     return mClass;
   }
 
@@ -544,6 +554,9 @@
 
   private ArrayList<Resolution> mResolutions;
 
+  /** Whether the value of {@code mClass} has been resolved. */
+  private boolean mResolvedClass;
+
   private boolean mIsPrimitive;
   private boolean mIsTypeVariable;
   private boolean mIsWildcard;
diff --git a/test/doclava/ApiCheckTest.java b/test/doclava/ApiCheckTest.java
index d9f1a07..ce0464a 100644
--- a/test/doclava/ApiCheckTest.java
+++ b/test/doclava/ApiCheckTest.java
@@ -17,26 +17,31 @@
 package doclava;
 
 import com.google.doclava.Errors;
-import com.google.doclava.Errors.Error;
 import com.google.doclava.Errors.ErrorMessage;
 import com.google.doclava.apicheck.ApiCheck;
 import com.google.doclava.apicheck.ApiCheck.Report;
 
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
 
 import java.util.Iterator;
 
-public class ApiCheckTest extends TestCase {
-  /**
-   * Clear all errors and make sure all future errors will be recorded.
-   */
+public class ApiCheckTest {
+
+  @Before
   public void setUp() {
+    // Clear all errors and make sure all future errors will be recorded.
     Errors.clearErrors();
     for (Errors.Error error : Errors.sErrors) {
       Errors.setErrorLevel(error.code, Errors.ERROR);
     }
   }
 
+  @Test
   public void testEquivalentApi() {
     String[] args = { "test/api/medium.xml", "test/api/medium.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -44,6 +49,7 @@
     assertEquals(report.errors().size(), 0);
   }
 
+  @Test
   public void testMethodReturnTypeChanged() {
     String[] args = { "test/api/return-type-changed-1.xml", "test/api/return-type-changed-2.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -52,6 +58,7 @@
     assertEquals(Errors.CHANGED_TYPE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testMethodParameterChanged() {
     String[] args = { "test/api/parameter-changed-1.xml", "test/api/parameter-changed-2.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -66,6 +73,7 @@
     assertTrue(m2.error().equals(Errors.ADDED_METHOD) || m2.error().equals(Errors.REMOVED_METHOD));
   }
 
+  @Test
   public void testConstructorParameterChanged() {
     String[] args = { "test/api/parameter-changed-1.xml", "test/api/parameter-changed-3.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -79,6 +87,7 @@
     assertTrue(m2.error().equals(Errors.ADDED_METHOD) || m2.error().equals(Errors.REMOVED_METHOD));
   }
 
+  @Test
   public void testAddedClass() {
     String[] args = { "test/api/simple.xml", "test/api/added-class.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -87,6 +96,7 @@
     assertEquals(Errors.ADDED_CLASS, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testRemovedClass() {
     String[] args = { "test/api/added-class.xml", "test/api/simple.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -95,6 +105,7 @@
     assertEquals(Errors.REMOVED_CLASS, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testRemovedDeprecatedClass() {
     String[] args = { "test/api/added-deprecated-class.xml", "test/api/simple.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -103,6 +114,7 @@
     assertEquals(Errors.REMOVED_DEPRECATED_CLASS, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedSuper() {
     String[] args = { "test/api/simple.xml", "test/api/changed-super.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -111,13 +123,19 @@
     assertEquals(Errors.CHANGED_SUPERCLASS, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedAssignableReturn() {
-    String[] args = { "test/api/changed-assignable-return-1.xml", "test/api/changed-assignable-return-2.xml" };
+    String[] args = {
+        "test/api/changed-assignable-return-1.xml",
+        "test/api/changed-assignable-return-2.xml"
+    };
     ApiCheck apiCheck = new ApiCheck();
     Report report = apiCheck.checkApi(args);
-    assertEquals(0, report.errors().size());
+    assertEquals(1, report.errors().size());
+    assertEquals(Errors.CHANGED_TYPE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testInsertedSuper() {
     String[] args = { "test/api/inserted-super-1.xml", "test/api/inserted-super-2.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -125,6 +143,7 @@
     assertEquals(0, report.errors().size());
   }
 
+  @Test
   public void testAddedInterface() {
     String[] args = { "test/api/removed-interface.xml", "test/api/medium.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -133,6 +152,7 @@
     assertEquals(Errors.ADDED_INTERFACE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testRemovedInterface() {
     String[] args = { "test/api/medium.xml", "test/api/removed-interface.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -141,6 +161,7 @@
     assertEquals(Errors.REMOVED_INTERFACE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedAbstractClass() {
     String[] args = { "test/api/medium.xml", "test/api/changed-abstract.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -149,6 +170,7 @@
     assertEquals(Errors.CHANGED_ABSTRACT, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedAbstractClass2() {
     String[] args = { "test/api/changed-abstract.xml", "test/api/medium.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -157,6 +179,7 @@
     assertEquals(Errors.CHANGED_ABSTRACT, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedAbstractMethod() {
     String[] args = { "test/api/medium.xml", "test/api/changed-abstract2.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -165,6 +188,7 @@
     assertEquals(Errors.CHANGED_ABSTRACT, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedAbstractMethod2() {
     String[] args = { "test/api/changed-abstract2.xml", "test/api/medium.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -173,6 +197,7 @@
     assertEquals(Errors.CHANGED_ABSTRACT, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testAddedPackage() {
     String[] args = { "test/api/medium.xml", "test/api/added-package.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -181,6 +206,7 @@
     assertEquals(Errors.ADDED_PACKAGE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testRemovedPackage() {
     String[] args = { "test/api/added-package.xml", "test/api/medium.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -189,6 +215,7 @@
     assertEquals(Errors.REMOVED_PACKAGE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedValue() {
     String[] args = { "test/api/constants.xml", "test/api/changed-value.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -197,6 +224,7 @@
     assertEquals(Errors.CHANGED_VALUE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedValue2() {
     String[] args = { "test/api/constants.xml", "test/api/changed-value2.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -205,6 +233,7 @@
     assertEquals(Errors.CHANGED_VALUE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedType() {
     String[] args = { "test/api/constants.xml", "test/api/changed-type.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -213,7 +242,8 @@
     assertEquals(Errors.CHANGED_TYPE, report.errors().iterator().next().error());
   }
 
-  public void testChangedFinalField() {
+  @Test
+  public void testAddedFinalField() {
     String[] args = { "test/api/constants.xml", "test/api/changed-final.xml" };
     ApiCheck apiCheck = new ApiCheck();
     Report report = apiCheck.checkApi(args);
@@ -221,7 +251,8 @@
     assertEquals(Errors.ADDED_FINAL, report.errors().iterator().next().error());
   }
 
-  public void testChangedFinalMethod() {
+  @Test
+  public void testAddedFinalMethod() {
     String[] args = { "test/api/constants.xml", "test/api/changed-final2.xml" };
     ApiCheck apiCheck = new ApiCheck();
     Report report = apiCheck.checkApi(args);
@@ -229,22 +260,27 @@
     assertEquals(Errors.ADDED_FINAL, report.errors().iterator().next().error());
   }
 
-  public void testChangedFinalClass() {
+  @Test
+  public void testAddedFinalClass() {
     String[] args = { "test/api/constants.xml", "test/api/changed-final3.xml" };
     ApiCheck apiCheck = new ApiCheck();
     Report report = apiCheck.checkApi(args);
-    assertEquals(1, report.errors().size());
+    // One error for the class, one for the constructor, one for the method.
+    assertEquals(3, report.errors().size());
     assertEquals(Errors.ADDED_FINAL, report.errors().iterator().next().error());
   }
 
-  public void testChangedFinalClass2() {
+  @Test
+  public void testRemovedFinalClass() {
     String[] args = { "test/api/changed-final3.xml", "test/api/constants.xml" };
     ApiCheck apiCheck = new ApiCheck();
     Report report = apiCheck.checkApi(args);
-    assertEquals(1, report.errors().size());
+    // One error for the class, one for the constructor, one for the method.
+    assertEquals(3, report.errors().size());
     assertEquals(Errors.REMOVED_FINAL, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testAddedField() {
     String[] args = { "test/api/constants.xml", "test/api/added-field.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -253,6 +289,7 @@
     assertEquals(Errors.ADDED_FIELD, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testRemovedField() {
     String[] args = { "test/api/added-field.xml", "test/api/constants.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -261,6 +298,7 @@
     assertEquals(Errors.REMOVED_FIELD, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testRemovedDeprecatedField() {
     String[] args = { "test/api/added-deprecated-field.xml", "test/api/constants.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -269,6 +307,7 @@
     assertEquals(Errors.REMOVED_DEPRECATED_FIELD, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testAddedMethod() {
     String[] args = { "test/api/constants.xml", "test/api/added-method.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -277,6 +316,7 @@
     assertEquals(Errors.ADDED_METHOD, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testRemovedMethod() {
     String[] args = { "test/api/added-method.xml", "test/api/constants.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -285,6 +325,7 @@
     assertEquals(Errors.REMOVED_METHOD, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testRemovedDeprecatedMethod() {
     String[] args = { "test/api/added-deprecated-method.xml", "test/api/constants.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -293,6 +334,7 @@
     assertEquals(Errors.REMOVED_DEPRECATED_METHOD, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedStaticMethod() {
     String[] args = { "test/api/constants.xml", "test/api/changed-static.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -301,6 +343,7 @@
     assertEquals(Errors.CHANGED_STATIC, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedStaticClass() {
     String[] args = { "test/api/constants.xml", "test/api/changed-static2.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -309,6 +352,7 @@
     assertEquals(Errors.CHANGED_STATIC, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedStaticField() {
     String[] args = { "test/api/constants.xml", "test/api/changed-static3.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -317,6 +361,7 @@
     assertEquals(Errors.CHANGED_STATIC, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedTransient() {
     String[] args = { "test/api/constants.xml", "test/api/changed-transient.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -325,13 +370,15 @@
     assertEquals(Errors.CHANGED_TRANSIENT, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedSynchronized() {
     String[] args = { "test/api/constants.xml", "test/api/changed-synchronized.xml" };
     ApiCheck apiCheck = new ApiCheck();
     Report report = apiCheck.checkApi(args);
-    assertEquals(0, report.errors().size());
+    assertEquals(1, report.errors().size());
   }
 
+  @Test
   public void testChangedVolatile() {
     String[] args = { "test/api/constants.xml", "test/api/changed-volatile.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -340,6 +387,7 @@
     assertEquals(Errors.CHANGED_VOLATILE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedNative() {
     String[] args = { "test/api/constants.xml", "test/api/changed-native.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -348,6 +396,7 @@
     assertEquals(Errors.CHANGED_NATIVE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedScopeMethod() {
     String[] args = { "test/api/constants.xml", "test/api/changed-scope.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -356,6 +405,7 @@
     assertEquals(Errors.CHANGED_SCOPE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedScopeClass() {
     String[] args = { "test/api/changed-scope.xml", "test/api/constants.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -364,6 +414,7 @@
     assertEquals(Errors.CHANGED_SCOPE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedScopeClass2() {
     String[] args = { "test/api/constants.xml", "test/api/changed-scope2.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -372,6 +423,7 @@
     assertEquals(Errors.CHANGED_SCOPE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedScopeField() {
     String[] args = { "test/api/constants.xml", "test/api/changed-scope3.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -380,6 +432,7 @@
     assertEquals(Errors.CHANGED_SCOPE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedConstructorScope() {
     String[] args = { "test/api/constants.xml", "test/api/changed-scope4.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -388,6 +441,7 @@
     assertEquals(Errors.CHANGED_SCOPE, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedMethodThrows() {
     String[] args = { "test/api/throws.xml", "test/api/removed-exception.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -396,6 +450,7 @@
     assertEquals(Errors.CHANGED_THROWS, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedMethodThrows2() {
     String[] args = { "test/api/removed-exception.xml", "test/api/throws.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -404,6 +459,7 @@
     assertEquals(Errors.CHANGED_THROWS, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedConstructorThrows() {
     String[] args = { "test/api/throws.xml", "test/api/added-exception.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -412,6 +468,7 @@
     assertEquals(Errors.CHANGED_THROWS, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedConstructorThrows2() {
     String[] args = { "test/api/added-exception.xml", "test/api/throws.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -420,6 +477,7 @@
     assertEquals(Errors.CHANGED_THROWS, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedMethodDeprecated() {
     String[] args = { "test/api/constants.xml", "test/api/changed-deprecated.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -428,6 +486,7 @@
     assertEquals(Errors.CHANGED_DEPRECATED, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedConstructorDeprecated() {
     String[] args = { "test/api/constants.xml", "test/api/changed-deprecated2.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -436,6 +495,7 @@
     assertEquals(Errors.CHANGED_DEPRECATED, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedFieldDeprecated() {
     String[] args = { "test/api/constants.xml", "test/api/changed-deprecated3.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -444,6 +504,7 @@
     assertEquals(Errors.CHANGED_DEPRECATED, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedClassToInterface() {
     String[] args = { "test/api/changed-class-info2.xml", "test/api/changed-class-info.xml" };
     ApiCheck apiCheck = new ApiCheck();
@@ -452,6 +513,7 @@
     assertEquals(Errors.CHANGED_CLASS, report.errors().iterator().next().error());
   }
 
+  @Test
   public void testChangedInterfaceToClass() {
     String[] args = { "test/api/changed-class-info.xml", "test/api/changed-class-info2.xml" };
     ApiCheck apiCheck = new ApiCheck();