Merge commit '8fd9d692aabab8f55f90ab1aa2885c8502534275' into HEAD
Change-Id: I192640a4782329de7ba17082a93ce75aef56a8c4
diff --git a/res/assets/templates-sdk/page_info.cs b/res/assets/templates-sdk/page_info.cs
index fad1274..4bdda67 100644
--- a/res/assets/templates-sdk/page_info.cs
+++ b/res/assets/templates-sdk/page_info.cs
@@ -23,11 +23,15 @@
?>
<div id="api-info-block">
<div class="api-level">
- <?cs call:since_tags(class) ?><?cs
- if:class.deprecatedsince
- ?><br>Deprecated since <a href="<?cs var:toroot ?>guide/topics/manifest/uses-sdk-element.html#ApiLevels"
- >API level <?cs var:class.deprecatedsince ?></a><?cs
- /if ?>
+ <?cs call:since_tags(class) ?>
+ <?cs if:class.artifact ?>
+ <br><?cs call:artifact_tags(class) ?>
+ <?cs /if ?>
+ <?cs if:class.deprecatedsince ?>
+ <br>Deprecated since
+ <a href="<?cs var:toroot ?>guide/topics/manifest/uses-sdk-element.html#ApiLevels">API level
+ <?cs var:class.deprecatedsince ?></a>
+ <?cs /if ?>
<?cs call:federated_refs(class) ?>
</div>
diff --git a/res/assets/templates/macros.cs b/res/assets/templates/macros.cs
index 3bf36e2..cf8c86e 100644
--- a/res/assets/templates/macros.cs
+++ b/res/assets/templates/macros.cs
@@ -274,6 +274,13 @@
/if ?><?cs
/def ?><?cs
+# print the artifact ?><?cs
+def:artifact_tags(obj) ?><?cs
+if:reference.artifacts && obj.artifact ?>
+ belongs to Maven artifact <?cs var:obj.artifact ?></a><?cs
+/if ?><?cs
+/def ?><?cs
+
def:federated_refs(obj) ?>
<?cs if:subcount(obj.federated) ?>
<div>
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..c6b98ee
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+rootProject.name = 'doclava'
\ No newline at end of file
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/ArtifactTagger.java b/src/com/google/doclava/ArtifactTagger.java
new file mode 100644
index 0000000..3c5ee06
--- /dev/null
+++ b/src/com/google/doclava/ArtifactTagger.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.doclava;
+
+import com.google.clearsilver.jsilver.data.Data;
+import com.google.doclava.apicheck.ApiCheck;
+import com.google.doclava.apicheck.ApiInfo;
+import com.google.doclava.apicheck.ApiParseException;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Applies version information to the Doclava class model from apicheck XML files.
+ * <p>
+ * Sample usage:
+ * <pre>
+ * ClassInfo[] classInfos = ...
+ *
+ * ArtifactTagger artifactTagger = new ArtifactTagger()
+ * artifactTagger.addArtifact("frameworks/support/core-ui/api/current.xml",
+ * "com.android.support:support-core-ui:26.0.0")
+ * artifactTagger.addArtifact("frameworks/support/design/api/current.xml",
+ * "com.android.support:support-design:26.0.0")
+ * artifactTagger.tagAll(...);
+ * </pre>
+ */
+public class ArtifactTagger {
+
+ private final Map<String, String> xmlToArtifact = new LinkedHashMap<>();
+
+ /**
+ * Specifies the apicheck XML file associated with an artifact.
+ * <p>
+ * This method should only be called once per artifact.
+ *
+ * @param file an apicheck XML file
+ * @param mavenSpec the Maven spec for the artifact to which the XML file belongs
+ */
+ public void addArtifact(String file, String mavenSpec) {
+ xmlToArtifact.put(file, mavenSpec);
+ }
+
+ /**
+ * Tags the specified docs with artifact information.
+ *
+ * @param classDocs the docs to tag
+ */
+ public void tagAll(ClassInfo[] classDocs) {
+ // Read through the XML files in order, applying their artifact information
+ // to the Javadoc models.
+ for (Map.Entry<String, String> artifactSpec : xmlToArtifact.entrySet()) {
+ String xmlFile = artifactSpec.getKey();
+ String artifactName = artifactSpec.getValue();
+
+ ApiInfo specApi;
+ try {
+ specApi = new ApiCheck().parseApi(xmlFile);
+ } catch (ApiParseException e) {
+ StringWriter stackTraceWriter = new StringWriter();
+ e.printStackTrace(new PrintWriter(stackTraceWriter));
+ Errors.error(Errors.BROKEN_ARTIFACT_FILE, (SourcePositionInfo) null,
+ "Failed to parse " + xmlFile + " for " + artifactName + " artifact data.\n"
+ + stackTraceWriter.toString());
+ continue;
+ }
+
+ applyArtifactsFromSpec(artifactName, specApi, classDocs);
+ }
+
+ if (!xmlToArtifact.isEmpty()) {
+ warnForMissingArtifacts(classDocs);
+ }
+ }
+
+ /**
+ * Returns {@code true} if any artifact mappings are specified.
+ */
+ public boolean hasArtifacts() {
+ return !xmlToArtifact.isEmpty();
+ }
+
+ /**
+ * Writes an index of the artifact names to {@code data}.
+ */
+ public void writeArtifactNames(Data data) {
+ int index = 1;
+ for (String artifact : xmlToArtifact.values()) {
+ data.setValue("artifact." + index + ".name", artifact);
+ index++;
+ }
+ }
+
+ /**
+ * Applies artifact information to {@code classDocs} where not already present.
+ *
+ * @param mavenSpec the Maven spec for the artifact
+ * @param specApi the spec for this artifact
+ * @param classDocs the docs to update
+ */
+ private void applyArtifactsFromSpec(String mavenSpec, ApiInfo specApi, ClassInfo[] classDocs) {
+ for (ClassInfo classDoc : classDocs) {
+ PackageInfo packageSpec = specApi.getPackages().get(classDoc.containingPackage().name());
+ if (packageSpec != null) {
+ ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name());
+ if (classSpec != null) {
+ if (classDoc.getArtifact() == null) {
+ classDoc.setArtifact(mavenSpec);
+ } else {
+ Errors.error(Errors.BROKEN_ARTIFACT_FILE, (SourcePositionInfo) null, "Class "
+ + classDoc.name() + " belongs to multiple artifacts: " + classDoc.getArtifact()
+ + " and " + mavenSpec);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Warns if any symbols are missing artifact information. When configured properly, this will
+ * yield zero warnings because {@code apicheck} guarantees that all symbols are present in the
+ * most recent API.
+ *
+ * @param classDocs the docs to verify
+ */
+ private void warnForMissingArtifacts(ClassInfo[] classDocs) {
+ for (ClassInfo claz : classDocs) {
+ if (checkLevelRecursive(claz) && claz.getArtifact() == null) {
+ Errors.error(Errors.NO_ARTIFACT_DATA, claz.position(), "XML missing class "
+ + claz.qualifiedName());
+ }
+ }
+ }
+
+ /**
+ * Returns true if {@code claz} and all containing classes are documented. The result may be used
+ * to filter out members that exist in the API data structure but aren't a part of the API.
+ */
+ private boolean checkLevelRecursive(ClassInfo claz) {
+ for (ClassInfo c = claz; c != null; c = c.containingClass()) {
+ if (!c.checkLevel()) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
index 20addc6..3a4090c 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.
@@ -1121,6 +1123,7 @@
if (isDeprecated()) {
data.setValue(base + ".deprecatedsince", getDeprecatedSince());
}
+ data.setValue(base + ".artifact", getArtifact());
ArrayList<AnnotationInstanceInfo> showAnnos = getShowAnnotationsIncludeOuters();
AnnotationInstanceInfo.makeLinkListHDF(
@@ -1191,6 +1194,7 @@
if (isDeprecated()) {
data.setValue("class.deprecatedsince", getDeprecatedSince());
}
+ data.setValue("class.artifact", getArtifact());
setFederatedReferences(data, "class");
// the containing package -- note that this can be passed to type_link,
@@ -1471,8 +1475,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 +1488,7 @@
if (pkg != null && pkg.hasHideComment()) {
return true;
}
- if (cl.comment().isHidden()) {
+ if (cl.comment().isHidden() || cl.hasHideAnnotation()) {
return true;
}
cl = cl.containingClass();
@@ -1508,9 +1512,6 @@
public boolean isRemovedImpl() {
ClassInfo cl = this;
while (cl != null) {
- if (cl.hasShowAnnotation()) {
- return false;
- }
PackageInfo pkg = cl.containingPackage();
if (pkg != null && pkg.hasRemovedComment()) {
return true;
@@ -1536,6 +1537,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 +1832,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/DocInfo.java b/src/com/google/doclava/DocInfo.java
index d8a1961..650ce0d 100644
--- a/src/com/google/doclava/DocInfo.java
+++ b/src/com/google/doclava/DocInfo.java
@@ -104,6 +104,26 @@
return mSince;
}
+ /**
+ * Sets the artifact in which the class resides.
+ * <p>
+ * This property should be specified as a full Maven dependency spec. For example, a Support
+ * Library core utility class may use "com.android.support:support-core-utils:26.0.1".
+ *
+ * @param artifact the artifact in which the class resides
+ * @return
+ */
+ public void setArtifact(String artifact) {
+ mArtifact = artifact;
+ }
+
+ /**
+ * Returns the artifact in which the class resides.
+ */
+ public String getArtifact() {
+ return mArtifact;
+ }
+
public void setDeprecatedSince(String since) {
mDeprecatedSince = since;
}
@@ -137,6 +157,7 @@
Comment mComment;
SourcePositionInfo mPosition;
private String mSince;
+ private String mArtifact;
private String mDeprecatedSince;
private Set<FederatedSite> mFederatedReferences = new LinkedHashSet<FederatedSite>();
}
diff --git a/src/com/google/doclava/Doclava.java b/src/com/google/doclava/Doclava.java
index ee4fd78..e1c92f1 100644
--- a/src/com/google/doclava/Doclava.java
+++ b/src/com/google/doclava/Doclava.java
@@ -101,9 +101,11 @@
public static Map<Character, String> escapeChars = new HashMap<Character, String>();
public static String title = "";
public static SinceTagger sinceTagger = new SinceTagger();
+ public static ArtifactTagger artifactTagger = new ArtifactTagger();
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 +184,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 +255,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 +284,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")) {
@@ -296,6 +310,8 @@
parseComments = true;
} else if (a[0].equals("-since")) {
sinceTagger.addVersion(a[1], a[2]);
+ } else if (a[0].equals("-artifact")) {
+ artifactTagger.addArtifact(a[1], a[2]);
} else if (a[0].equals("-offlinemode")) {
offlineMode = true;
} else if (a[0].equals("-metadataDebug")) {
@@ -420,6 +436,9 @@
// Apply @since tags from the XML file
sinceTagger.tagAll(Converter.rootClasses());
+ // Apply @artifact tags from the XML file
+ artifactTagger.tagAll(Converter.rootClasses());
+
// Apply details of federated documentation
federationTagger.tagAll(Converter.rootClasses());
@@ -514,7 +533,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();
@@ -734,6 +755,9 @@
if (option.equals("-werror")) {
return 1;
}
+ if (option.equals("-lerror")) {
+ return 1;
+ }
if (option.equals("-hide")) {
return 2;
}
@@ -785,6 +809,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;
}
@@ -809,6 +839,9 @@
if (option.equals("-since")) {
return 3;
}
+ if (option.equals("-artifact")) {
+ return 3;
+ }
if (option.equals("-offlinemode")) {
return 1;
}
@@ -955,6 +988,7 @@
}
data.setValue("reference", "1");
data.setValue("reference.apilevels", sinceTagger.hasVersions() ? "1" : "0");
+ data.setValue("reference.artifacts", artifactTagger.hasArtifacts() ? "1" : "0");
data.setValue("docs.packages." + i + ".name", s);
data.setValue("docs.packages." + i + ".link", pkg.htmlPage());
data.setValue("docs.packages." + i + ".since", pkg.getSince());
diff --git a/src/com/google/doclava/Errors.java b/src/com/google/doclava/Errors.java
index 48202a3..9edd239 100644
--- a/src/com/google/doclava/Errors.java
+++ b/src/com/google/doclava/Errors.java
@@ -311,6 +311,8 @@
public static final Error BROADCAST_BEHAVIOR = new Error(126, LINT);
public static final Error SDK_CONSTANT = new Error(127, LINT);
public static final Error TODO = new Error(128, LINT);
+ public static final Error NO_ARTIFACT_DATA = new Error(129, HIDDEN);
+ public static final Error BROKEN_ARTIFACT_FILE = new Error(130, ERROR);
public static boolean setErrorLevel(int code, int level) {
for (Error e : sErrors) {
diff --git a/src/com/google/doclava/MemberInfo.java b/src/com/google/doclava/MemberInfo.java
index 6c5aad3..8e7863e 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,15 +49,7 @@
if (mShowAnnotations.size() > 0) {
return false;
}
- return super.isHidden();
- }
-
- @Override
- public boolean isRemoved() {
- if (mShowAnnotations.size() > 0) {
- return false;
- }
- return super.isRemoved();
+ return super.isHidden() || mHideAnnotations.size() > 0;
}
@Override
@@ -177,6 +170,10 @@
return mShowAnnotations;
}
+ public ArrayList<AnnotationInstanceInfo> hideAnnotations() {
+ return mHideAnnotations;
+ }
+
ClassInfo mContainingClass;
ClassInfo mRealContainingClass;
String mName;
@@ -191,5 +188,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/NavTree.java b/src/com/google/doclava/NavTree.java
index 03926b2..5d055db 100644
--- a/src/com/google/doclava/NavTree.java
+++ b/src/com/google/doclava/NavTree.java
@@ -165,7 +165,7 @@
for (ClassInfo cl : classes) {
if (cl.checkLevel()) {
- children.add(new Node(cl.name(), cl.htmlPage(), null, cl.getSince()));
+ children.add(new Node(cl.name(), cl.htmlPage(), null, cl.getSince(), cl.getArtifact()));
}
}
@@ -179,12 +179,18 @@
private String mLink;
List<Node> mChildren;
private String mSince;
+ private String mArtifact;
Node(String label, String link, List<Node> children, String since) {
+ this(label, link, children, since, null);
+ }
+
+ Node(String label, String link, List<Node> children, String since, String artifact) {
mLabel = label;
mLink = link;
mChildren = children;
mSince = since;
+ mArtifact = artifact;
}
static void renderString(StringBuilder buf, String s) {
@@ -243,6 +249,8 @@
renderChildren(buf);
buf.append(", ");
renderString(buf, mSince);
+ buf.append(", ");
+ renderString(buf, mArtifact);
buf.append(" ]");
}
}
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index fbcff97..4dc859d 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.
- static boolean methodIsOverride(HashSet<ClassInfo> notStrippable, MethodInfo mi) {
+ /**
+ * 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(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) {
@@ -980,30 +1017,12 @@
stream.println(";");
}
- static void writeXML(PrintStream xmlWriter, HashMap<PackageInfo, List<ClassInfo>> allClasses,
- HashSet<ClassInfo> notStrippable) {
- // extract the set of packages, sort them by name, and write them out in that order
- Set<PackageInfo> allClassKeys = allClasses.keySet();
- PackageInfo[] allPackages = allClassKeys.toArray(new PackageInfo[allClassKeys.size()]);
- Arrays.sort(allPackages, PackageInfo.comparator);
+ public static void writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs,
+ Predicate<ClassInfo> notStrippable) {
- xmlWriter.println("<api>");
- for (PackageInfo pack : allPackages) {
- writePackageXML(xmlWriter, pack, allClasses.get(pack), notStrippable);
- }
- xmlWriter.println("</api>");
- }
-
- public static void writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs) {
final PackageInfo[] packages = pkgs.toArray(new PackageInfo[pkgs.size()]);
Arrays.sort(packages, PackageInfo.comparator);
- HashSet<ClassInfo> notStrippable = new HashSet();
- for (PackageInfo pkg: packages) {
- for (ClassInfo cl: pkg.allClasses().values()) {
- notStrippable.add(cl);
- }
- }
xmlWriter.println("<api>");
for (PackageInfo pkg: packages) {
writePackageXML(xmlWriter, pkg, pkg.allClasses().values(), notStrippable);
@@ -1011,8 +1030,17 @@
xmlWriter.println("</api>");
}
+ public static void writeXml(PrintStream xmlWriter, Collection<PackageInfo> pkgs) {
+ HashSet<ClassInfo> allClasses = new HashSet<>();
+ for (PackageInfo pkg: pkgs) {
+ allClasses.addAll(pkg.allClasses().values());
+ }
+ Predicate<ClassInfo> notStrippable = allClasses::contains;
+ writeXml(xmlWriter, pkgs, notStrippable);
+ }
+
static void writePackageXML(PrintStream xmlWriter, PackageInfo pack,
- Collection<ClassInfo> classList, HashSet<ClassInfo> notStrippable) {
+ Collection<ClassInfo> classList, Predicate<ClassInfo> notStrippable) {
ClassInfo[] classes = classList.toArray(new ClassInfo[classList.size()]);
Arrays.sort(classes, ClassInfo.comparator);
// Work around the bogus "Array" class we invent for
@@ -1031,7 +1059,7 @@
}
- static void writeClassXML(PrintStream xmlWriter, ClassInfo cl, HashSet<ClassInfo> notStrippable) {
+ static void writeClassXML(PrintStream xmlWriter, ClassInfo cl, Predicate<ClassInfo> notStrippable) {
String scope = cl.scope();
String deprecatedString = "";
String declString = (cl.isInterface()) ? "interface" : "class";
@@ -1055,7 +1083,7 @@
ArrayList<ClassInfo> interfaces = cl.realInterfaces();
Collections.sort(interfaces, ClassInfo.comparator);
for (ClassInfo iface : interfaces) {
- if (notStrippable.contains(iface)) {
+ if (notStrippable.test(iface)) {
xmlWriter.println("<implements name=\"" + iface.qualifiedName() + "\">");
xmlWriter.println("</implements>");
}
@@ -1070,7 +1098,7 @@
ArrayList<MethodInfo> methods = cl.allSelfMethods();
Collections.sort(methods, MethodInfo.comparator);
for (MethodInfo mi : methods) {
- if (!methodIsOverride(notStrippable, mi)) {
+ if (!methodIsOverride(mi)) {
writeMethodXML(xmlWriter, mi);
}
}
@@ -1202,14 +1230,16 @@
ClassInfo clazz = member.containingClass();
boolean visible = member.isPublic() || member.isProtected();
+ boolean hidden = member.isHidden();
boolean removed = member.isRemoved();
while (clazz != null) {
visible &= clazz.isPublic() || clazz.isProtected();
+ hidden |= clazz.isHidden();
removed |= clazz.isRemoved();
clazz = clazz.containingClass();
}
- if (visible && removed) {
+ if (visible && !hidden && removed) {
if (member instanceof MethodInfo) {
final MethodInfo method = (MethodInfo) member;
return (method.findOverriddenMethod(method.name(), method.signature()) == null);
@@ -1229,15 +1259,17 @@
boolean visible = member.isPublic() || member.isProtected();
boolean hasShowAnnotation = member.hasShowAnnotation();
- boolean hiddenOrRemoved = member.isHiddenOrRemoved();
+ boolean hidden = member.isHidden();
+ boolean removed = member.isRemoved();
while (clazz != null) {
visible &= clazz.isPublic() || clazz.isProtected();
hasShowAnnotation |= clazz.hasShowAnnotation();
- hiddenOrRemoved |= clazz.isHiddenOrRemoved();
+ hidden |= clazz.isHidden();
+ removed |= clazz.isRemoved();
clazz = clazz.containingClass();
}
- if (visible && hasShowAnnotation && !hiddenOrRemoved) {
+ if (visible && hasShowAnnotation && !hidden && !removed) {
if (member instanceof MethodInfo) {
final MethodInfo method = (MethodInfo) member;
return (method.findOverriddenMethod(method.name(), method.signature()) == null);
@@ -1539,7 +1571,7 @@
ArrayList<MethodInfo> methods = cl.allSelfMethods();
Collections.sort(methods, MethodInfo.comparator);
for (MethodInfo mi : methods) {
- if (!methodIsOverride(notStrippable, mi)) {
+ if (!methodIsOverride(mi)) {
writeMethodApi(apiWriter, mi);
}
}
@@ -1573,10 +1605,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/apicheck/ApiCheck.java b/src/com/google/doclava/apicheck/ApiCheck.java
index 8498d0a..5dda8d2 100644
--- a/src/com/google/doclava/apicheck/ApiCheck.java
+++ b/src/com/google/doclava/apicheck/ApiCheck.java
@@ -26,6 +26,7 @@
import java.util.List;
import java.util.HashSet;
import java.util.Set;
+import java.util.function.Predicate;
import com.google.doclava.Errors;
import com.google.doclava.PackageInfo;
@@ -64,7 +65,9 @@
if (originalArgs.length == 3 && "-convert".equals(originalArgs[0])) {
System.exit(convertToApi(originalArgs[1], originalArgs[2]));
} else if (originalArgs.length == 3 && "-convert2xml".equals(originalArgs[0])) {
- System.exit(convertToXml(originalArgs[1], originalArgs[2]));
+ System.exit(convertToXml(originalArgs[1], originalArgs[2], true));
+ } else if (originalArgs.length == 3 && "-convert2xmlnostrip".equals(originalArgs[0])) {
+ System.exit(convertToXml(originalArgs[1], originalArgs[2], false));
} else if (originalArgs.length == 4 && "-new_api".equals(originalArgs[0])) {
// command syntax: -new_api oldapi.txt newapi.txt diff.xml
// TODO: Support reading in other options for new_api, such as ignored classes/packages.
@@ -268,7 +271,7 @@
return 0;
}
- static int convertToXml(String src, String dst) {
+ static int convertToXml(String src, String dst, boolean strip) {
ApiInfo api;
try {
api = parseApi(src);
@@ -285,7 +288,7 @@
System.err.println("can't open file: " + dst);
}
- Stubs.writeXml(apiWriter, api.getPackages().values());
+ Stubs.writeXml(apiWriter, api.getPackages().values(), c -> true);
return 0;
}
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();