diff options
author | Xin Li <delphij@google.com> | 2017-08-01 15:11:17 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2017-08-01 15:13:14 -0700 |
commit | e15c52f95e09391629d4046a1ceb296ae29710cd (patch) | |
tree | 9f43c9f35e3bacbad225375ccd341c436cd59c04 | |
parent | 875a77b6e1c6b9bd6d536972c8db385cb4becfe9 (diff) | |
parent | 2f6f6abe4d05282c82dd56fa897949f49ac3cd6c (diff) | |
download | platform_external_doclava-e15c52f95e09391629d4046a1ceb296ae29710cd.tar.gz platform_external_doclava-e15c52f95e09391629d4046a1ceb296ae29710cd.tar.bz2 platform_external_doclava-e15c52f95e09391629d4046a1ceb296ae29710cd.zip |
DO NOT MERGE: Merge commit '2f6f6abe4d05282c82dd56fa897949f49ac3cd6c'android-o-mr1-preview-2android-o-mr1-preview-1
from oc-support-26.0-dev to stage-aosp-master.
Bug: 64219148
Test: build
Change-Id: I3a43d851a95d17df6b33ee416df178fdf494e811
-rw-r--r-- | res/assets/templates-sdk/macros_override.cs | 2 | ||||
-rw-r--r-- | src/com/google/doclava/AnnotationInstanceInfo.java | 36 | ||||
-rw-r--r-- | src/com/google/doclava/ClassInfo.java | 17 | ||||
-rw-r--r-- | src/com/google/doclava/Converter.java | 62 | ||||
-rw-r--r-- | src/com/google/doclava/Doclava.java | 28 | ||||
-rw-r--r-- | src/com/google/doclava/FederationTagger.java | 67 | ||||
-rw-r--r-- | src/com/google/doclava/MemberInfo.java | 8 | ||||
-rw-r--r-- | src/com/google/doclava/MethodInfo.java | 26 | ||||
-rw-r--r-- | src/com/google/doclava/Stubs.java | 145 | ||||
-rw-r--r-- | src/com/google/doclava/TypeInfo.java | 17 | ||||
-rw-r--r-- | test/doclava/ApiCheckTest.java | 92 |
11 files changed, 385 insertions, 115 deletions
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 @@ def:dump_permission(tag) ?>Requires the <?cs 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 com.google.clearsilver.jsilver.data.Data; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; public class AnnotationInstanceInfo implements Resolvable { private ClassInfo mType; @@ -146,19 +147,40 @@ public class AnnotationInstanceInfo implements Resolvable { /** * 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 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco mIsPrimitive = isPrimitive; mAnnotations = annotations; mShowAnnotations = AnnotationInstanceInfo.getShowAnnotationsIntersection(annotations); + mHideAnnotations = AnnotationInstanceInfo.getHideAnnotationsIntersection(annotations); } public void init(TypeInfo typeInfo, ArrayList<ClassInfo> interfaces, @@ -167,6 +168,7 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco 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 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco } /** - * @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 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco 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 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco 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 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco 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.Arrays; 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 @@ public class Converter { 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 @@ public class Converter { 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 @@ public class Converter { } // 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 class Doclava { 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 @@ public class Doclava { 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 @@ public class Doclava { 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 @@ public class Doclava { 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 @@ public class Doclava { // 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 @@ public class Doclava { 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 @@ public class Doclava { 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 @@ import java.util.Map; * 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 @@ public final class FederationTagger { 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 @@ public final class FederationTagger { 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 @@ public final class FederationTagger { } 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 @@ public final class FederationTagger { } } } - + private void federateConstructors(FederatedSite site, ClassInfo federatedClass, ClassInfo localClass) { for (MethodInfo constructor : localClass.constructors()) { @@ -133,7 +166,7 @@ public final class FederationTagger { } } } - + private void federateFields(FederatedSite site, ClassInfo federatedClass, ClassInfo localClass) { for (FieldInfo field : localClass.fields()) { if (federatedClass.allFields().containsKey(field.name())) { @@ -141,12 +174,12 @@ public final class FederationTagger { } } } - + 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 @@ public abstract class MemberInfo extends DocInfo implements Comparable, Scoped { mKind = kind; mAnnotations = annotations; mShowAnnotations = AnnotationInstanceInfo.getShowAnnotationsIntersection(annotations); + mHideAnnotations = AnnotationInstanceInfo.getHideAnnotationsIntersection(annotations); } public abstract boolean isExecutable(); @@ -48,7 +49,7 @@ public abstract class MemberInfo extends DocInfo implements Comparable, Scoped { if (mShowAnnotations.size() > 0) { return false; } - return super.isHidden(); + return super.isHidden() || mHideAnnotations.size() > 0; } @Override @@ -177,6 +178,10 @@ public abstract class MemberInfo extends DocInfo implements Comparable, Scoped { return mShowAnnotations; } + public ArrayList<AnnotationInstanceInfo> hideAnnotations() { + return mHideAnnotations; + } + ClassInfo mContainingClass; ClassInfo mRealContainingClass; String mName; @@ -191,5 +196,6 @@ public abstract class MemberInfo extends DocInfo implements Comparable, Scoped { 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.LinkedList; 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 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv 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 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv 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 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv 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 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv 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 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv + " 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 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv 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 @@ package com.google.doclava; 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.InputStream; 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 @@ import java.util.stream.Collectors; 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 @@ public class Stubs { "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 @@ public class Stubs { + 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 @@ public class Stubs { 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 @@ public class Stubs { 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 @@ public class Stubs { 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 @@ public class Stubs { 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 @@ public class Stubs { 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 @@ public class Stubs { 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 @@ public class Stubs { // 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 @@ public class Stubs { 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 @@ public class Stubs { } } - 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 @@ public class Stubs { 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 @@ public class Stubs { 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 @@ public class Stubs { + "()"); } else { cantStripThis(tcl, notStrippable, "10:" - + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name()); + + mInfo.realContainingClass().qualifiedName() + ":" + mInfo.name(), + stubImportPackages); } } } @@ -414,16 +457,18 @@ public class Stubs { } 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 @@ public class Stubs { || !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 @@ public class Stubs { 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 class TypeInfo implements Resolvable { 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 @@ public class TypeInfo implements Resolvable { 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 @@ public class TypeInfo implements Resolvable { 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 @@ public class TypeInfo implements Resolvable { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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 @@ public class ApiCheckTest extends TestCase { 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(); |