blob: 6bd042b88bf5c94b73ffcce551b781cb15c42225 [file] [log] [blame]
/*
* Copyright 2008 Google Inc.
*
* 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.gwt.tools.apichecker;
import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* abstract super-class for ApiMethod and ApiConstructor.
*/
abstract class ApiAbstractMethod implements Comparable<ApiAbstractMethod>, ApiElement {
static String computeApiSignature(JAbstractMethod method) {
String className = method.getEnclosingType().getQualifiedSourceName();
StringBuffer sb = new StringBuffer();
sb.append(className);
sb.append("::");
sb.append(computeInternalSignature(method));
return sb.toString();
}
static String computeInternalSignature(JAbstractMethod method) {
StringBuffer sb = new StringBuffer();
sb.append(method.getName());
sb.append("(");
JParameter[] params = method.getParameters();
for (int j = 0; j < params.length; j++) {
JParameter param = params[j];
String typeSig = param.getType().getJNISignature();
sb.append(typeSig);
}
sb.append(")");
return sb.toString();
}
/*
* 4 different signatures for a method: (a) internalSignature: just the
* JniSignature of the arguments. does not include any class name. (b)
* apiSignature: internalSignature, plus the className in which it was
* declared. (c) relativeSignature: internalSignature + the current class from
* which the Api method can be accessed. (d) coarseSignature: instead of
* jniSignature of each parameter, just record whether the parameter is a
* class type or a primitive type.
*/
final ApiClass apiClass;
String apiSignature = null;
String internalSignature = null;
final JAbstractMethod method;
String relativeSignature = null;
public ApiAbstractMethod(JAbstractMethod method, ApiClass apiClass) {
this.method = method;
this.apiClass = apiClass;
}
public int compareTo(ApiAbstractMethod other) {
return getRelativeSignature().compareTo(other.getRelativeSignature());
}
/**
* Used in set comparisons.
*/
@Override
public boolean equals(Object o) {
if (o instanceof ApiAbstractMethod) {
ApiAbstractMethod other = (ApiAbstractMethod) o;
return getApiSignature().equals(other.getApiSignature());
}
return false;
}
public ApiClass getApiClass() {
return apiClass;
}
public JAbstractMethod getMethod() {
return method;
}
public String getRelativeSignature() {
if (relativeSignature == null) {
relativeSignature = computeRelativeSignature();
}
return relativeSignature;
}
@Override
public int hashCode() {
return getApiSignature().hashCode();
}
public boolean isCompatible(ApiAbstractMethod methodInNew) {
JParameter[] parametersInNew = methodInNew.getMethod().getParameters();
int length = parametersInNew.length;
if (length != method.getParameters().length) {
return false;
}
for (int i = 0; i < length; i++) {
if (!ApiDiffGenerator.isFirstTypeAssignableToSecond(method.getParameters()[i].getType(),
parametersInNew[i].getType())) {
return false;
}
}
// Control reaches here iff methods are compatible with respect to
// parameters and return type. For source compatibility, I do not need to
// check in which classes the methods are declared.
return true;
}
public abstract boolean isOverridable();
@Override
public String toString() {
return method.toString();
}
List<ApiChange> checkExceptionsAndReturnType(ApiAbstractMethod newMethod) {
List<ApiChange> apiChanges = checkExceptions(newMethod);
ApiChange returnApiChange = checkReturnTypeCompatibility(newMethod);
if (returnApiChange == null) {
return apiChanges;
}
apiChanges.add(returnApiChange);
return apiChanges;
}
abstract ApiChange checkReturnTypeCompatibility(ApiAbstractMethod newMethod);
abstract List<ApiChange> getAllChangesInApi(ApiAbstractMethod newMethod);
String getApiSignature() {
if (apiSignature == null) {
apiSignature = computeApiSignature(method);
}
return apiSignature;
}
/**
* Gets the signature of a method distinguishing just the non-primitive types
* from the primitive types.
* <p>
* Useful to determine the method overloading because a null could be passed
* for a primitive type.
* <p>
* Not sure if the implementation is sufficient. If need be, look at the
* implementation below.
*
* <pre>
* public String getCoarseSignature() {
* StringBuffer returnStr = new StringBuffer();
* JParameter[] parameters = method.getParameters();
* JArrayType jat = null;
* JType type = parameter.getType();
* for (JParameter parameter : parameters) {
* while ((jat = type.isArray()) != null) {
* returnStr.append("a");
* type = jat.getComponentType();
* }
* if (type.isPrimitive() != null) {
* returnStr.append("p");
* } else {
* returnStr.append("c");
* }
* returnStr.append(";"); // to mark the end of a type
* }
* return returnStr.toString();
* }
* </pre>
*
* @return the coarse signature as a String
*/
String getCoarseSignature() {
StringBuffer returnStr = new StringBuffer();
JParameter[] parameters = method.getParameters();
for (JParameter parameter : parameters) {
JType type = parameter.getType();
if (type.isPrimitive() != null) {
returnStr.append(type.getJNISignature());
} else {
returnStr.append("c");
}
returnStr.append(";"); // to mark the end of a type
}
return returnStr.toString();
}
String getInternalSignature() {
if (internalSignature == null) {
internalSignature = computeInternalSignature(method);
}
return internalSignature;
}
/**
* Find changes in modifiers. returns a possibly immutable list.
*
*/
abstract List<ApiChange.Status> getModifierChanges(ApiAbstractMethod newMethod);
private List<ApiChange> checkExceptions(ApiAbstractMethod newMethod) {
ArrayList<JType> legalTypes = new ArrayList<JType>();
// A throw declaration for an unchecked exception does not change the API.
TypeOracle newTypeOracle = newMethod.getMethod().getEnclosingType().getOracle();
JClassType errorType = newTypeOracle.findType(Error.class.getName());
if (errorType != null) {
legalTypes.add(errorType);
}
JClassType rteType = newTypeOracle.findType(RuntimeException.class.getName());
if (rteType != null) {
legalTypes.add(rteType);
}
legalTypes.addAll(Arrays.asList(getMethod().getThrows()));
List<ApiChange> ret = new ArrayList<ApiChange>();
for (JType newException : newMethod.getMethod().getThrows()) {
boolean isSubclass = false;
for (JType legalType : legalTypes) {
if (ApiDiffGenerator.isFirstTypeAssignableToSecond(newException, legalType)) {
isSubclass = true;
break;
}
}
if (!isSubclass) {
ret.add(new ApiChange(this, ApiChange.Status.EXCEPTION_TYPE_ERROR,
"unhandled exception in new code " + newException));
}
}
return ret;
}
private String computeRelativeSignature() {
String signature = computeInternalSignature(method);
if (ApiCompatibilityChecker.DEBUG) {
JClassType enclosingType = method.getEnclosingType();
return apiClass.getClassObject().getQualifiedSourceName()
+ "::"
+ signature
+ " defined in "
+ (enclosingType == null ? "null enclosing type " : enclosingType
.getQualifiedSourceName());
}
return apiClass.getClassObject().getQualifiedSourceName() + "::" + signature;
}
}