blob: 11e444868b1779ffef47fd105379346751de8ac8 [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.JMethod;
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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Encapsulates an API method. Useful for set-operations.
*/
final class ApiMethod extends ApiAbstractMethod {
ApiMethod(JAbstractMethod method, ApiClass apiClass) {
super(method, apiClass);
}
@Override
public boolean isOverridable() {
JMethod methodType = (JMethod) method;
if (methodType.isStatic() || methodType.isFinal()) {
return false;
}
return apiClass.isSubclassableApiClass();
}
@Override
ApiChange checkReturnTypeCompatibility(ApiAbstractMethod newMethod)
throws TypeNotPresentException {
JType firstType, secondType;
if (newMethod.getMethod() instanceof JMethod && method instanceof JMethod) {
firstType = ((JMethod) method).getReturnType();
secondType = ((JMethod) newMethod.getMethod()).getReturnType();
} else {
throw new AssertionError("Different types for method = " + method.getClass()
+ ", and newMethodObject = " + newMethod.getMethod().getClass() + ", signature = "
+ getApiSignature());
}
StringBuffer sb = new StringBuffer();
if (firstType.getSimpleSourceName().indexOf("void") != -1) {
return null;
}
boolean compatible = ApiDiffGenerator.isFirstTypeAssignableToSecond(secondType, firstType);
if (compatible) {
return null;
}
sb.append(" from ");
sb.append(firstType.getQualifiedSourceName());
sb.append(" to ");
sb.append(secondType.getQualifiedSourceName());
return new ApiChange(this, ApiChange.Status.RETURN_TYPE_ERROR, sb.toString());
}
/**
* check for changes in: (i) argument types, (ii) return type, (iii)
* exceptions thrown. use getJNISignature() for type equality, it does type
* erasure
*/
@Override
List<ApiChange> getAllChangesInApi(ApiAbstractMethod newApiMethod) {
if (!(newApiMethod.getMethod() instanceof JMethod)) {
return Collections.emptyList();
}
List<ApiChange> changeApis = new ArrayList<ApiChange>();
JMethod existingMethod = (JMethod) method;
JMethod newMethod = (JMethod) newApiMethod.getMethod();
// check return type
if (!existingMethod.getReturnType().getJNISignature().equals(
newMethod.getReturnType().getJNISignature())) {
changeApis.add(new ApiChange(this, ApiChange.Status.OVERRIDABLE_METHOD_RETURN_TYPE_CHANGE,
" from " + existingMethod.getReturnType() + " to " + newMethod.getReturnType()));
}
// check argument type
JParameter[] newParametersList = newMethod.getParameters();
JParameter[] existingParametersList = existingMethod.getParameters();
if (newParametersList.length != existingParametersList.length) {
changeApis.add(new ApiChange(this, ApiChange.Status.OVERRIDABLE_METHOD_ARGUMENT_TYPE_CHANGE,
"number of parameters changed"));
} else {
int length = newParametersList.length;
for (int i = 0; i < length; i++) {
if (!existingParametersList[i].getType().getJNISignature().equals(
newParametersList[i].getType().getJNISignature())) {
changeApis.add(new ApiChange(this,
ApiChange.Status.OVERRIDABLE_METHOD_ARGUMENT_TYPE_CHANGE, " at position " + i
+ " from " + existingParametersList[i].getType() + " to "
+ newParametersList[i].getType()));
}
}
}
// check exceptions
Set<String> newExceptionsSet = new HashSet<String>();
Map<String, JType> newExceptionsMap = new HashMap<String, JType>();
for (JType newType : newMethod.getThrows()) {
String jniSignature = newType.getJNISignature();
newExceptionsMap.put(jniSignature, newType);
newExceptionsSet.add(jniSignature);
}
Set<String> existingExceptionsSet = new HashSet<String>();
Map<String, JType> existingExceptionsMap = new HashMap<String, JType>();
for (JType existingType : existingMethod.getThrows()) {
String jniSignature = existingType.getJNISignature();
existingExceptionsMap.put(jniSignature, existingType);
existingExceptionsSet.add(jniSignature);
}
ApiDiffGenerator.removeIntersection(existingExceptionsSet, newExceptionsSet);
removeUncheckedExceptions(newMethod, newExceptionsSet, newExceptionsMap);
removeUncheckedExceptions(existingMethod, existingExceptionsSet, existingExceptionsMap);
if (existingExceptionsSet.size() > 0) {
changeApis.add(new ApiChange(this, ApiChange.Status.OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE,
"existing method had more exceptions: " + existingExceptionsSet));
}
if (newExceptionsSet.size() > 0) {
changeApis.add(new ApiChange(this, ApiChange.Status.OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE,
"new method has more exceptions: " + newExceptionsSet));
}
return changeApis;
}
/*
* check for: (i) added 'final' or 'abstract', (ii) removed 'static', adding
* the 'static' keyword is fine.
*
* A private, static, or final method can't be made 'abstract' (Java language
* specification).
*/
@Override
List<ApiChange.Status> getModifierChanges(final ApiAbstractMethod newMethod) {
JMethod newjmethod = null;
JMethod oldjmethod = null;
if (newMethod.getMethod() instanceof JMethod && method instanceof JMethod) {
newjmethod = (JMethod) newMethod.getMethod();
oldjmethod = (JMethod) method;
} else {
throw new AssertionError("Different types for method = " + method.getClass()
+ " and newMethod = " + newMethod.getMethod().getClass() + ", signature = "
+ getApiSignature());
}
List<ApiChange.Status> statuses = new ArrayList<ApiChange.Status>();
if (!oldjmethod.isFinal() && !apiClass.getClassObject().isFinal() && newjmethod.isFinal()) {
statuses.add(ApiChange.Status.FINAL_ADDED);
}
if (!oldjmethod.isAbstract() && newjmethod.isAbstract()) {
statuses.add(ApiChange.Status.ABSTRACT_ADDED);
}
if ((oldjmethod.isStatic() && !newjmethod.isStatic())) {
statuses.add(ApiChange.Status.STATIC_REMOVED);
}
return statuses;
}
// remove Error.class, RuntimeException.class, and their sub-classes
private void removeUncheckedExceptions(JMethod method, Set<String> exceptionsSet,
Map<String, JType> exceptionsMap) {
if (exceptionsSet.size() == 0) {
return;
}
TypeOracle typeOracle = method.getEnclosingType().getOracle();
JClassType errorType = typeOracle.findType(Error.class.getName());
JClassType rteType = typeOracle.findType(RuntimeException.class.getName());
Set<String> exceptionsToRemove = new HashSet<String>();
for (String exceptionString : exceptionsSet) {
JType exception = exceptionsMap.get(exceptionString);
assert (exception != null);
boolean remove =
(errorType != null && ApiDiffGenerator
.isFirstTypeAssignableToSecond(exception, errorType))
|| (rteType != null && ApiDiffGenerator.isFirstTypeAssignableToSecond(exception,
rteType));
if (remove) {
exceptionsToRemove.add(exceptionString);
}
}
exceptionsSet.removeAll(exceptionsToRemove);
}
}
/*
* final class TestB { static protected int i = 5; }
*
* class TestC { public int j = TestB.i + 10; }
*/