summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTreeHugger Robot <treehugger-gerrit@google.com>2016-09-26 15:29:14 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2016-09-26 15:29:15 +0000
commit92ab2088e4a8e76b44e3ef50f05623ee5b0d1719 (patch)
treed63d04374f7721846ce5a0fed1898ede30bfdad0
parentdae8c69270576df69a931be0b4d99029758bbca1 (diff)
parent697a33db1bb0df263769d4a32f6a72ecf64d49db (diff)
downloadplatform_external_doclava-92ab2088e4a8e76b44e3ef50f05623ee5b0d1719.tar.gz
platform_external_doclava-92ab2088e4a8e76b44e3ef50f05623ee5b0d1719.tar.bz2
platform_external_doclava-92ab2088e4a8e76b44e3ef50f05623ee5b0d1719.zip
Merge "DO NOT MERGE Keep track of method and class type variables." into nyc-mr1-dev
-rw-r--r--src/com/google/doclava/ClassInfo.java38
-rw-r--r--src/com/google/doclava/MethodInfo.java86
-rw-r--r--src/com/google/doclava/Stubs.java7
-rw-r--r--src/com/google/doclava/TypeInfo.java73
-rw-r--r--src/com/google/doclava/apicheck/ApiFile.java94
5 files changed, 247 insertions, 51 deletions
diff --git a/src/com/google/doclava/ClassInfo.java b/src/com/google/doclava/ClassInfo.java
index 8f0199e..7b50938 100644
--- a/src/com/google/doclava/ClassInfo.java
+++ b/src/com/google/doclava/ClassInfo.java
@@ -312,6 +312,19 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco
return result;
}
+ public TypeInfo getTypeParameter(String qualifiedTypeName) {
+ List<TypeInfo> parameters = mTypeInfo.typeArguments();
+ if (parameters == null) {
+ return null;
+ }
+ for (TypeInfo parameter : parameters) {
+ if (parameter.qualifiedTypeName().equals(qualifiedTypeName)) {
+ return parameter;
+ }
+ }
+ return null;
+ }
+
/**
* List of only direct interface's classes, without worrying about type param mapping.
* This can't be lazy loaded, because its overloads depend on changing type parameters
@@ -2232,6 +2245,17 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco
}
}
+ if (hasTypeParameters() && cl.hasTypeParameters()) {
+ ArrayList<TypeInfo> oldParams = typeParameters();
+ ArrayList<TypeInfo> newParams = cl.typeParameters();
+ if (oldParams.size() != newParams.size()) {
+ consistent = false;
+ Errors.error(Errors.CHANGED_TYPE, cl.position(), "Class " + qualifiedName()
+ + " changed number of type parameters from " + oldParams.size()
+ + " to " + newParams.size());
+ }
+ }
+
return consistent;
}
@@ -2284,6 +2308,20 @@ public class ClassInfo extends DocInfo implements ContainerInfo, Comparable, Sco
return mTypeInfo;
}
+ public boolean hasTypeParameters() {
+ if (mTypeInfo != null && mTypeInfo.typeArguments() != null) {
+ return !mTypeInfo.typeArguments().isEmpty();
+ }
+ return false;
+ }
+
+ public ArrayList<TypeInfo> typeParameters() {
+ if (hasTypeParameters()) {
+ return mTypeInfo.typeArguments();
+ }
+ return null;
+ }
+
public void addInnerClass(ClassInfo innerClass) {
if (mInnerClasses == null) {
mInnerClasses = new ArrayList<ClassInfo>();
diff --git a/src/com/google/doclava/MethodInfo.java b/src/com/google/doclava/MethodInfo.java
index 299043d..28f1899 100644
--- a/src/com/google/doclava/MethodInfo.java
+++ b/src/com/google/doclava/MethodInfo.java
@@ -237,6 +237,10 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv
return mTypeParameters;
}
+ public boolean hasTypeParameters() {
+ return mTypeParameters != null && !mTypeParameters.isEmpty();
+ }
+
/**
* Clone this MethodInfo as if it belonged to the specified ClassInfo and apply the
* typeArgumentMapping to the parameters and return types.
@@ -668,7 +672,7 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv
}
public String typeArgumentsName(HashSet<String> typeVars) {
- if (mTypeParameters == null || mTypeParameters.isEmpty()) {
+ if (!hasTypeParameters()) {
return "";
} else {
return TypeInfo.typeArgumentsName(mTypeParameters, typeVars);
@@ -792,26 +796,39 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv
public boolean isConsistent(MethodInfo mInfo) {
boolean consistent = true;
- if (this.mReturnType != mInfo.mReturnType && !this.mReturnType.equals(mInfo.mReturnType)) {
- if (!mReturnType.isPrimitive() && !mInfo.mReturnType.isPrimitive()
- && Objects.equals(mInfo.mReturnType.dimension(), mReturnType.dimension())) {
- // Check to see if our class extends the old class.
- ApiInfo infoApi = mInfo.containingClass().containingPackage().containingApi();
- ClassInfo infoReturnClass = infoApi.findClass(mInfo.mReturnType.qualifiedTypeName());
- // Find the classes.
- consistent = infoReturnClass != null &&
- infoReturnClass.isAssignableTo(mReturnType.qualifiedTypeName());
- } else {
+ if (!mReturnType.isTypeVariable() && !mInfo.mReturnType.isTypeVariable()) {
+ if (!mReturnType.equals(mInfo.mReturnType) ||
+ mReturnType.dimension() != mInfo.mReturnType.dimension()) {
consistent = false;
}
-
- if (!consistent) {
- Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method "
- + mInfo.prettyQualifiedSignature() + " has changed return type from " + mReturnType
- + " to " + mInfo.mReturnType);
+ } else if (!mReturnType.isTypeVariable() && mInfo.mReturnType.isTypeVariable()) {
+ List<ClassInfo> constraints = mInfo.resolveConstraints(mInfo.mReturnType);
+ for (ClassInfo constraint : constraints) {
+ if (!constraint.isAssignableTo(mReturnType.qualifiedTypeName())) {
+ consistent = false;
+ }
+ }
+ } else if (mReturnType.isTypeVariable() && !mInfo.mReturnType.isTypeVariable()) {
+ // It's never valid to go from being a parameterized type to not being one.
+ // This would drop the implicit cast breaking backwards compatibility.
+ consistent = false;
+ } else {
+ // If both return types are parameterized then the constraints must be
+ // exactly the same.
+ List<ClassInfo> currentConstraints = mInfo.resolveConstraints(mReturnType);
+ List<ClassInfo> newConstraints = mInfo.resolveConstraints(mInfo.mReturnType);
+ if (currentConstraints.size() != newConstraints.size() ||
+ currentConstraints.retainAll(newConstraints)) {
+ consistent = false;
}
}
+ if (!consistent) {
+ Errors.error(Errors.CHANGED_TYPE, mInfo.position(), "Method "
+ + mInfo.prettyQualifiedSignature() + " has changed return type from " + mReturnType
+ + " to " + mInfo.mReturnType);
+ }
+
if (mIsAbstract != mInfo.mIsAbstract) {
consistent = false;
Errors.error(Errors.CHANGED_ABSTRACT, mInfo.position(), "Method "
@@ -898,6 +915,43 @@ public class MethodInfo extends MemberInfo implements AbstractMethodInfo, Resolv
return consistent;
}
+ private TypeInfo getTypeParameter(String qualifiedTypeName) {
+ if (hasTypeParameters()) {
+ for (TypeInfo parameter : mTypeParameters) {
+ if (parameter.qualifiedTypeName().equals(qualifiedTypeName)) {
+ return parameter;
+ }
+ }
+ }
+ return containingClass().getTypeParameter(qualifiedTypeName);
+ }
+
+ // Given a type parameter it returns a list of all of the classes and interfaces it must extend
+ // and implement.
+ private List<ClassInfo> resolveConstraints(TypeInfo type) {
+ ApiInfo api = containingClass().containingPackage().containingApi();
+ List<ClassInfo> classes = new ArrayList<>();
+ Queue<TypeInfo> types = new LinkedList<>();
+ types.add(type);
+ while (!types.isEmpty()) {
+ type = types.poll();
+ if (!type.isTypeVariable()) {
+ ClassInfo cl = api.findClass(type.qualifiedTypeName());
+ if (cl != null) {
+ classes.add(cl);
+ }
+ } else {
+ TypeInfo parameter = getTypeParameter(type.qualifiedTypeName());
+ if (parameter.extendsBounds() != null) {
+ for (TypeInfo bound : parameter.extendsBounds()) {
+ types.add(bound);
+ }
+ }
+ }
+ }
+ return classes;
+ }
+
public void printResolutions() {
if (mResolutions == null || mResolutions.isEmpty()) {
return;
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index dbec6f7..e1ada09 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -1343,6 +1343,10 @@ public class Stubs {
apiWriter.print(cl.isInterface() ? "interface" : "class");
apiWriter.print(" ");
apiWriter.print(cl.name());
+ if (cl.hasTypeParameters()) {
+ apiWriter.print(TypeInfo.typeArgumentsName(cl.asTypeInfo().typeArguments(),
+ new HashSet<String>()));
+ }
if (!cl.isInterface()
&& !"java.lang.Object".equals(cl.qualifiedName())
@@ -1432,6 +1436,9 @@ public class Stubs {
if (mi.isSynchronized()) {
apiWriter.print(" synchronized");
}
+ if (mi.hasTypeParameters()) {
+ apiWriter.print(" " + mi.typeArgumentsName(new HashSet<String>()));
+ }
apiWriter.print(" ");
if (mi.returnType() == null) {
apiWriter.print("void");
diff --git a/src/com/google/doclava/TypeInfo.java b/src/com/google/doclava/TypeInfo.java
index 567415b..ad26def 100644
--- a/src/com/google/doclava/TypeInfo.java
+++ b/src/com/google/doclava/TypeInfo.java
@@ -39,42 +39,75 @@ public class TypeInfo implements Resolvable {
if (typeString.endsWith("...")) {
typeString = typeString.substring(0, typeString.length() - 3);
}
-
+
// Generic parameters
+ int extendsPos = typeString.indexOf(" extends ");
int paramStartPos = typeString.indexOf('<');
- if (paramStartPos > -1) {
+ if (paramStartPos > -1 && (extendsPos == -1 || paramStartPos < extendsPos)) {
ArrayList<TypeInfo> generics = new ArrayList<TypeInfo>();
- int paramEndPos = typeString.lastIndexOf('>');
-
+ int paramEndPos = 0;
+
int entryStartPos = paramStartPos + 1;
int bracketNesting = 0;
- for (int i = entryStartPos; i < paramEndPos; i++) {
+ for (int i = entryStartPos; i < typeString.length(); i++) {
char c = typeString.charAt(i);
if (c == ',' && bracketNesting == 0) {
String entry = typeString.substring(entryStartPos, i).trim();
TypeInfo info = new TypeInfo(entry);
+ info.setIsTypeVariable(true);
generics.add(info);
entryStartPos = i + 1;
} else if (c == '<') {
bracketNesting++;
} else if (c == '>') {
bracketNesting--;
+ // Once bracketNesting goes negative, we've found the closing angle bracket
+ if (bracketNesting < 0) {
+ paramEndPos = i;
+ break;
+ }
}
}
-
+
TypeInfo info = new TypeInfo(typeString.substring(entryStartPos, paramEndPos).trim());
+ info.setIsTypeVariable(true);
generics.add(info);
-
+
mTypeArguments = generics;
-
+
if (paramEndPos < typeString.length() - 1) {
typeString = typeString.substring(0,paramStartPos) + typeString.substring(paramEndPos + 1);
} else {
typeString = typeString.substring(0,paramStartPos);
}
}
-
- // Dimensions
+
+ // The previous extends may have been within the generic type parameters which we don't
+ // actually care about and were removed from the type string above
+ extendsPos = typeString.indexOf(" extends ");
+ if (extendsPos > -1) {
+ ArrayList<TypeInfo> extendsBounds = new ArrayList<>();
+ int entryStartPos = extendsPos + 9;
+ int bracketNesting = 0;
+ for (int i = entryStartPos; i < typeString.length(); i++) {
+ char c = typeString.charAt(i);
+ if (c == '&' && bracketNesting == 0) {
+ String entry = typeString.substring(entryStartPos, i).trim();
+ TypeInfo info = new TypeInfo(entry);
+ extendsBounds.add(info);
+ entryStartPos = i + 1;
+ } else if (c == '<') {
+ bracketNesting++;
+ } else if (c == '>') {
+ bracketNesting--;
+ }
+ }
+ TypeInfo info = new TypeInfo(typeString.substring(entryStartPos, typeString.length()).trim());
+ extendsBounds.add(info);
+ mExtendsBounds = extendsBounds;
+ typeString = typeString.substring(0, extendsPos);
+ }
+
int pos = typeString.indexOf('[');
if (pos > -1) {
mDimension = typeString.substring(pos);
@@ -82,7 +115,7 @@ public class TypeInfo implements Resolvable {
} else {
mDimension = "";
}
-
+
if (PRIMITIVE_TYPES.contains(typeString)) {
mIsPrimitive = true;
mSimpleTypeName = typeString;
@@ -317,7 +350,7 @@ public class TypeInfo implements Resolvable {
mTypeArguments.add(arg);
}
- void setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds) {
+ public void setBounds(ArrayList<TypeInfo> superBounds, ArrayList<TypeInfo> extendsBounds) {
mSuperBounds = superBounds;
mExtendsBounds = extendsBounds;
}
@@ -330,7 +363,7 @@ public class TypeInfo implements Resolvable {
return mExtendsBounds;
}
- void setIsTypeVariable(boolean b) {
+ public void setIsTypeVariable(boolean b) {
mIsTypeVariable = b;
}
@@ -342,7 +375,7 @@ public class TypeInfo implements Resolvable {
return mIsWildcard;
}
- static HashSet<String> typeVariables(ArrayList<TypeInfo> params) {
+ public static HashSet<String> typeVariables(ArrayList<TypeInfo> params) {
return typeVariables(params, new HashSet<String>());
}
@@ -362,6 +395,16 @@ public class TypeInfo implements Resolvable {
return mIsTypeVariable;
}
+ public void resolveTypeVariables(HashSet<String> variables) {
+ if (mExtendsBounds != null) {
+ for (TypeInfo bound : mExtendsBounds) {
+ if (variables.contains(bound.qualifiedTypeName())) {
+ bound.setIsTypeVariable(true);
+ }
+ }
+ }
+ }
+
public String defaultValue() {
if (mIsPrimitive) {
if ("boolean".equals(mSimpleTypeName)) {
@@ -477,7 +520,7 @@ public class TypeInfo implements Resolvable {
Map<String, TypeInfo> map = new HashMap<String, TypeInfo>();
for (int i = 0; i < generic.typeArguments().size(); i++) {
if (typed.typeArguments() != null && typed.typeArguments().size() > i) {
- map.put(generic.typeArguments().get(i).fullName(), typed.typeArguments().get(i));
+ map.put(generic.typeArguments().get(i).simpleTypeName(), typed.typeArguments().get(i));
}
}
return map;
diff --git a/src/com/google/doclava/apicheck/ApiFile.java b/src/com/google/doclava/apicheck/ApiFile.java
index 2d958d4..d865e20 100644
--- a/src/com/google/doclava/apicheck/ApiFile.java
+++ b/src/com/google/doclava/apicheck/ApiFile.java
@@ -30,6 +30,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -163,12 +164,15 @@ class ApiFile {
}
assertIdent(tokenizer, token);
name = token;
- token = tokenizer.requireToken();
qname = qualifiedName(pkg.name(), name, null);
+ final TypeInfo typeInfo = Converter.obtainTypeFromString(qname);
+ token = tokenizer.requireToken();
cl = new ClassInfo(null/*classDoc*/, ""/*rawCommentText*/, tokenizer.pos(), pub, prot,
pkgpriv, false/*isPrivate*/, stat, iface, abs, true/*isOrdinaryClass*/,
false/*isException*/, false/*isError*/, false/*isEnum*/, false/*isAnnotation*/,
- fin, false/*isIncluded*/, name, qname, null/*qualifiedTypeName*/, false/*isPrimitive*/);
+ fin, false/*isIncluded*/, typeInfo.simpleTypeName(), typeInfo.qualifiedTypeName(),
+ null/*qualifiedTypeName*/, false/*isPrimitive*/);
+ cl.setTypeInfo(typeInfo);
cl.setDeprecated(dep);
if ("extends".equals(token)) {
token = tokenizer.requireToken();
@@ -178,8 +182,6 @@ class ApiFile {
}
// Resolve superclass after done parsing
api.mapClassToSuper(cl, ext);
- final TypeInfo typeInfo = Converter.obtainTypeFromString(qname) ;
- cl.setTypeInfo(typeInfo);
cl.setAnnotations(new ArrayList<AnnotationInstanceInfo>());
if ("implements".equals(token)) {
while (true) {
@@ -261,7 +263,7 @@ class ApiFile {
new ArrayList<AnnotationInstanceInfo>()/*annotations*/);
method.setDeprecated(dep);
token = tokenizer.requireToken();
- parseParameterList(tokenizer, method, token);
+ parseParameterList(tokenizer, method, new HashSet<String>(), token);
token = tokenizer.requireToken();
if ("throws".equals(token)) {
token = parseThrows(tokenizer, method);
@@ -283,7 +285,9 @@ class ApiFile {
boolean dep = false;
boolean syn = false;
boolean def = false;
- String type;
+ ArrayList<TypeInfo> typeParameters = new ArrayList<>();
+ TypeInfo returnType;
+ HashSet<String> typeVariableNames;
String name;
String ext = null;
MethodInfo method;
@@ -321,25 +325,32 @@ class ApiFile {
syn = true;
token = tokenizer.requireToken();
}
+ if ("<".equals(token)) {
+ parseTypeParameterList(tokenizer, typeParameters, cl);
+ token = tokenizer.requireToken();
+ }
assertIdent(tokenizer, token);
- type = token;
+ returnType = Converter.obtainTypeFromString(token);
+ typeVariableNames = TypeInfo.typeVariables(typeParameters);
+ if (typeVariableNames.contains(returnType.qualifiedTypeName())) {
+ returnType.setIsTypeVariable(true);
+ }
token = tokenizer.requireToken();
assertIdent(tokenizer, token);
name = token;
- method = new MethodInfo(""/*rawCommentText*/, new ArrayList<TypeInfo>()/*typeParameters*/,
- name, null/*signature*/, cl, cl, pub, prot, pkgpriv, false/*isPrivate*/, fin,
- stat, false/*isSynthetic*/, abs/*isAbstract*/, syn, false/*isNative*/, def/*isDefault*/,
- false /*isAnnotationElement*/, "method", null/*flatSignature*/, null/*overriddenMethod*/,
- Converter.obtainTypeFromString(type), new ArrayList<ParameterInfo>(),
- new ArrayList<ClassInfo>()/*thrownExceptions*/, tokenizer.pos(),
- new ArrayList<AnnotationInstanceInfo>()/*annotations*/);
+ method = new MethodInfo(""/*rawCommentText*/, typeParameters, name, null/*signature*/, cl, cl,
+ pub, prot, pkgpriv, false/*isPrivate*/, fin, stat, false/*isSynthetic*/, abs/*isAbstract*/,
+ syn, false/*isNative*/, def/*isDefault*/, false /*isAnnotationElement*/, "method",
+ null/*flatSignature*/, null/*overriddenMethod*/, returnType,
+ new ArrayList<ParameterInfo>(), new ArrayList<ClassInfo>()/*thrownExceptions*/,
+ tokenizer.pos(), new ArrayList<AnnotationInstanceInfo>()/*annotations*/);
method.setDeprecated(dep);
token = tokenizer.requireToken();
if (!"(".equals(token)) {
throw new ApiParseException("expected (", tokenizer.getLine());
}
token = tokenizer.requireToken();
- parseParameterList(tokenizer, method, token);
+ parseParameterList(tokenizer, method, typeVariableNames, token);
token = tokenizer.requireToken();
if ("throws".equals(token)) {
token = parseThrows(tokenizer, method);
@@ -475,8 +486,47 @@ class ApiFile {
}
}
+ private static void parseTypeParameterList(Tokenizer tokenizer,
+ List<TypeInfo> methodTypeParameters, ClassInfo cl) throws ApiParseException {
+ String token;
+ HashSet<String> variables = cl.typeVariables();
+ do {
+ token = tokenizer.requireToken();
+ assertIdent(tokenizer, token);
+ TypeInfo type = new TypeInfo(token);
+ type.setIsTypeVariable(true);
+ variables.add(type.qualifiedTypeName());
+ ArrayList<TypeInfo> extendsBounds = new ArrayList<>();
+ token = tokenizer.requireToken();
+ if ("extends".equals(token)) {
+ do {
+ token = tokenizer.requireToken();
+ assertIdent(tokenizer, token);
+ extendsBounds.add(new TypeInfo(token));
+ token = tokenizer.requireToken();
+ } while ("&".equals(token));
+ }
+ if (!extendsBounds.isEmpty()) {
+ type.setBounds(null, extendsBounds);
+ }
+ methodTypeParameters.add(type);
+ } while (",".equals(token));
+
+ // Type variables aren't guaranteed to be declared before they're referenced so we need to wait
+ // until after we've processed them all to figure out which ones are type variables and which
+ // ones are classes (which we may not have processed yet either).
+ for (TypeInfo type : methodTypeParameters) {
+ type.resolveTypeVariables(variables);
+ }
+
+ if (!">".equals(token)) {
+ throw new ApiParseException("Expected '>' to end type parameter list, found "
+ + token, tokenizer.getLine());
+ }
+ }
+
private static void parseParameterList(Tokenizer tokenizer, AbstractMethodInfo method,
- String token) throws ApiParseException {
+ HashSet<String> typeParameters, String token) throws ApiParseException {
while (true) {
if (")".equals(token)) {
return;
@@ -497,8 +547,12 @@ class ApiFile {
}
// api file does not preserve annotations.
List<AnnotationInstanceInfo> annotations = Collections.emptyList();
+ TypeInfo typeInfo = Converter.obtainTypeFromString(type);
+ if (typeParameters.contains(typeInfo.qualifiedTypeName())) {
+ typeInfo.setIsTypeVariable(true);
+ }
method.addParameter(new ParameterInfo(name, type,
- Converter.obtainTypeFromString(type),
+ typeInfo,
type.endsWith("..."),
tokenizer.pos(),
annotations));
@@ -660,10 +714,10 @@ class ApiFile {
if (mBuf[mPos] == '<') {
genericDepth++;
mPos++;
- } else if (mBuf[mPos] == '>') {
- genericDepth--;
- mPos++;
} else if (genericDepth != 0) {
+ if (mBuf[mPos] == '>') {
+ genericDepth--;
+ }
mPos++;
}
}