/* * 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; } */