CORDA-704: Implement @DoNotImplement annotation (#1903)

* Enhance the API Scanner plugin to monitor class annotations.
* Implement @DoNotImplement annotation, and apply it.
* Update API definition.
* Update API change detection to handle @DoNotImplement.
* Document the `@DoNotImplement` annotation.
This commit is contained in:
Chris Rankin
2017-10-19 17:18:35 +01:00
committed by GitHub
parent 3dd09fd69b
commit 2c84d07e8e
29 changed files with 402 additions and 266 deletions

View File

@ -16,6 +16,7 @@ import org.gradle.api.tasks.TaskAction;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@ -25,7 +26,7 @@ import java.net.URLClassLoader;
import java.util.*;
import java.util.stream.StreamSupport;
import static java.util.Collections.unmodifiableSet;
import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
@SuppressWarnings("unused")
@ -228,9 +229,19 @@ public class ScanApi extends DefaultTask {
private void writeClass(PrintWriter writer, ClassInfo classInfo, int modifiers) {
if (classInfo.isAnnotation()) {
/*
* Annotation declaration.
*/
writer.append(Modifier.toString(modifiers & INTERFACE_MASK));
writer.append(" @interface ").print(classInfo);
} else if (classInfo.isStandardClass()) {
/*
* Class declaration.
*/
List<String> annotationNames = toNames(readClassAnnotationsFor(classInfo));
if (!annotationNames.isEmpty()) {
writer.append(asAnnotations(annotationNames));
}
writer.append(Modifier.toString(modifiers & CLASS_MASK));
writer.append(" class ").print(classInfo);
Set<ClassInfo> superclasses = classInfo.getDirectSuperclasses();
@ -242,6 +253,13 @@ public class ScanApi extends DefaultTask {
writer.append(" implements ").print(stringOf(interfaces));
}
} else {
/*
* Interface declaration.
*/
List<String> annotationNames = toNames(readInterfaceAnnotationsFor(classInfo));
if (!annotationNames.isEmpty()) {
writer.append(asAnnotations(annotationNames));
}
writer.append(Modifier.toString(modifiers & INTERFACE_MASK));
writer.append(" interface ").print(classInfo);
Set<ClassInfo> superinterfaces = classInfo.getDirectSuperinterfaces();
@ -253,7 +271,7 @@ public class ScanApi extends DefaultTask {
}
private void writeMethods(PrintWriter writer, List<MethodInfo> methods) {
Collections.sort(methods);
sort(methods);
for (MethodInfo method : methods) {
if (isVisible(method.getAccessFlags()) // Only public and protected methods
&& isValid(method.getAccessFlags(), METHOD_MASK) // Excludes bridge and synthetic methods
@ -264,7 +282,7 @@ public class ScanApi extends DefaultTask {
}
private void writeFields(PrintWriter output, List<FieldInfo> fields) {
Collections.sort(fields);
sort(fields);
for (FieldInfo field : fields) {
if (isVisible(field.getAccessFlags()) && isValid(field.getAccessFlags(), FIELD_MASK)) {
output.append(" ").println(field);
@ -286,6 +304,36 @@ public class ScanApi extends DefaultTask {
return 0;
}
private List<String> toNames(Collection<ClassInfo> classes) {
return classes.stream()
.map(ClassInfo::toString)
.filter(ScanApi::isApplicationClass)
.collect(toList());
}
private Set<ClassInfo> readClassAnnotationsFor(ClassInfo classInfo) {
Set<ClassInfo> annotations = new HashSet<>(classInfo.getAnnotations());
annotations.addAll(selectInheritedAnnotations(classInfo.getSuperclasses()));
annotations.addAll(selectInheritedAnnotations(classInfo.getImplementedInterfaces()));
return annotations;
}
private Set<ClassInfo> readInterfaceAnnotationsFor(ClassInfo classInfo) {
Set<ClassInfo> annotations = new HashSet<>(classInfo.getAnnotations());
annotations.addAll(selectInheritedAnnotations(classInfo.getSuperinterfaces()));
return annotations;
}
/**
* Returns those annotations which have themselves been annotated as "Inherited".
*/
private List<ClassInfo> selectInheritedAnnotations(Collection<ClassInfo> classes) {
return classes.stream()
.flatMap(cls -> cls.getAnnotations().stream())
.filter(ann -> ann.hasMetaAnnotation(Inherited.class.getName()))
.collect(toList());
}
private MethodInfo filterAnnotationsFor(MethodInfo method) {
return new MethodInfo(
method.getClassName(),
@ -319,6 +367,14 @@ public class ScanApi extends DefaultTask {
return items.stream().map(ClassInfo::toString).collect(joining(", "));
}
private static String asAnnotations(Collection<String> items) {
return items.stream().collect(joining(" @", "@", " "));
}
private static boolean isApplicationClass(String typeName) {
return !typeName.startsWith("java.") && !typeName.startsWith("kotlin.");
}
private static URL toURL(File file) throws MalformedURLException {
return file.toURI().toURL();
}