| /* |
| * 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.JClassType; |
| import com.google.gwt.core.ext.typeinfo.JType; |
| import com.google.gwt.core.ext.typeinfo.NotFoundException; |
| import com.google.gwt.core.ext.typeinfo.TypeOracle; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * {@link ApiDiffGenerator} encapsulates a class that produces the diff between |
| * two api's. |
| */ |
| public final class ApiDiffGenerator { |
| |
| private static enum Relation { |
| NONE, SUBCLASS, SUPERCLASS; |
| } |
| |
| public static final String DELIMITER = " "; |
| |
| /* variable just for debugging -- find why an apiChange is being removed */ |
| static final String HAY_API_CHANGE = ""; |
| |
| /** |
| * The two types might belong to different typeOracles. |
| */ |
| static boolean isFirstTypeAssignableToSecond(JType firstType, JType secondType) { |
| |
| // getJNISignature() does TypeErasure |
| if (firstType.getJNISignature().equals(secondType.getJNISignature())) { |
| return true; |
| } |
| JClassType classType1 = firstType.isClassOrInterface(); |
| JClassType classType2 = secondType.isClassOrInterface(); |
| if (classType1 == null || classType2 == null) { |
| return false; |
| } |
| TypeOracle newApiTypeOracle = classType2.getOracle(); |
| // get the appropriate classObject in the newApi |
| JClassType firstClassType = newApiTypeOracle.findType(classType1.getQualifiedSourceName()); |
| JClassType secondClassType = newApiTypeOracle.findType(classType2.getQualifiedSourceName()); |
| // The types might not necessarily exist in the newApi |
| if (firstClassType == null || secondClassType == null) { |
| return false; |
| } |
| return firstClassType.isAssignableTo(secondClassType); |
| } |
| |
| static Set<String> removeIntersection(Set<String> s1, Set<String> s2) { |
| Set<String> intersection = new HashSet<String>(s1); |
| intersection.retainAll(s2); |
| s1.removeAll(intersection); |
| s2.removeAll(intersection); |
| return intersection; |
| } |
| |
| Map<String, ApiPackageDiffGenerator> intersectingPackages = |
| new HashMap<String, ApiPackageDiffGenerator>(); |
| Set<String> missingPackageNames; |
| |
| final ApiContainer newApi; |
| |
| final ApiContainer oldApi; |
| |
| ApiDiffGenerator(ApiContainer newApi, ApiContainer oldApi) { |
| this.newApi = newApi; |
| this.oldApi = oldApi; |
| } |
| |
| ApiClassDiffGenerator findApiClassDiffGenerator(String className) { |
| int i = className.length() - 1; |
| while (i >= 0) { |
| int dot = className.lastIndexOf('.', i); |
| String pkgName = ""; |
| String typeName = className; |
| if (dot != -1) { |
| pkgName = className.substring(0, dot); |
| typeName = className.substring(dot + 1); |
| i = dot - 1; |
| } else { |
| i = -1; |
| } |
| ApiClassDiffGenerator result = findApiClassDiffGenerator(pkgName, typeName); |
| if (result != null) { |
| return result; |
| } |
| } |
| return null; |
| } |
| |
| // TODO(amitmanjhi): cache the results |
| /** |
| * Finds a type given its package-relative name. For nested classes, use its |
| * source name rather than its binary name (that is, use a "." rather than a |
| * "$"). |
| * |
| * @return <code>null</code> if the type is not found |
| */ |
| ApiClassDiffGenerator findApiClassDiffGenerator(String pkgName, String typeName) { |
| ApiPackageDiffGenerator pkg = findApiPackageDiffGenerator(pkgName); |
| if (pkg != null) { |
| ApiClassDiffGenerator type = pkg.findApiClassDiffGenerator(pkgName + "." + typeName); |
| if (type != null) { |
| return type; |
| } |
| } |
| return null; |
| } |
| |
| ApiPackageDiffGenerator findApiPackageDiffGenerator(String key) { |
| return intersectingPackages.get(key); |
| } |
| |
| Collection<ApiChange> getApiDiff() throws NotFoundException { |
| |
| computeApiDiff(); |
| |
| Collection<ApiChange> collection = new ArrayList<ApiChange>(); |
| Set<ApiPackage> missingPackages = oldApi.getApiPackagesBySet(missingPackageNames); |
| for (ApiPackage missingPackage : missingPackages) { |
| collection.add(new ApiChange(missingPackage, ApiChange.Status.MISSING)); |
| } |
| for (ApiPackageDiffGenerator intersectingPackage : intersectingPackages.values()) { |
| collection.addAll(intersectingPackage.getApiDiff()); |
| } |
| return collection; |
| } |
| |
| ApiContainer getNewApiContainer() { |
| return newApi; |
| } |
| |
| ApiContainer getOldApiContainer() { |
| return oldApi; |
| } |
| |
| /** |
| * Remove any apiChange x if there is another apiChange y such that the |
| * apiElement and status for both x and y are the same and the apiClass for x |
| * is a subclass of the apiClass for y. |
| * |
| * @param originalCollection collection with duplicates. |
| * @return collection minus duplicates. |
| */ |
| Collection<ApiChange> removeDuplicates(Collection<ApiChange> originalCollection) { |
| /* |
| * Map from the hashCode of an apiChange to the list of ApiChanges. There |
| * can be multiple ApiChanges that have the same hashCode, but neither is a |
| * subset of another. Example: if B and C both extend A, and there is an |
| * ApiChange in B and C due to an api element of A. |
| */ |
| Map<Integer, Collection<ApiChange>> apiChangeMap = |
| new HashMap<Integer, Collection<ApiChange>>(); |
| for (ApiChange apiChange : originalCollection) { |
| String apiChangeStr = apiChange.getApiElement().getRelativeSignature(); |
| Collection<ApiChange> apiChangesSameHashCode = |
| apiChangeMap.get(apiChange.hashCodeForDuplication()); |
| if (apiChangesSameHashCode == null) { |
| apiChangesSameHashCode = new HashSet<ApiChange>(); |
| apiChangeMap.put(apiChange.hashCodeForDuplication(), apiChangesSameHashCode); |
| } |
| Collection<ApiChange> apiChangesToRemove = new HashSet<ApiChange>(); |
| boolean addNewElement = true; |
| |
| for (ApiChange oldApiChange : apiChangesSameHashCode) { |
| String oldApiChangeStr = oldApiChange.getApiElement().getRelativeSignature(); |
| Relation relation = |
| getRelationOfApiClassOfFirstArgToThatOfSecond(apiChange.getApiElement(), oldApiChange |
| .getApiElement()); |
| if (relation == Relation.SUPERCLASS) { |
| apiChangesToRemove.add(oldApiChange); |
| if (ApiCompatibilityChecker.DEBUG_DUPLICATE_REMOVAL |
| && oldApiChangeStr.indexOf(HAY_API_CHANGE) != -1) { |
| System.out.println(oldApiChangeStr + " replaced by " + apiChangeStr + ", status = " |
| + oldApiChange.getStatus()); |
| } |
| } else if (relation == Relation.SUBCLASS) { |
| addNewElement = false; |
| if (ApiCompatibilityChecker.DEBUG_DUPLICATE_REMOVAL |
| && apiChangeStr.indexOf(HAY_API_CHANGE) != -1) { |
| System.out.println(apiChangeStr + " replaced by " + oldApiChangeStr); |
| } |
| } |
| } |
| apiChangesSameHashCode.removeAll(apiChangesToRemove); |
| if (addNewElement) { |
| apiChangesSameHashCode.add(apiChange); |
| } |
| } |
| Collection<ApiChange> prunedCollection = new HashSet<ApiChange>(); |
| for (Collection<ApiChange> changes : apiChangeMap.values()) { |
| prunedCollection.addAll(changes); |
| } |
| return prunedCollection; |
| } |
| |
| /** |
| * Compares 2 APIs for source compatibility. Algorithm: First find packages |
| * that are in one but not in another. Then, look at at classes in the common |
| * packages. Look at public classes. |
| * |
| */ |
| private void computeApiDiff() throws NotFoundException { |
| Set<String> newApiPackageNames = newApi.getApiPackageNames(); |
| missingPackageNames = oldApi.getApiPackageNames(); |
| Set<String> intersection = removeIntersection(newApiPackageNames, missingPackageNames); |
| // Inspect each of the classes in each of the packages in the intersection |
| for (String packageName : intersection) { |
| ApiPackageDiffGenerator tempPackageDiffGenerator = |
| new ApiPackageDiffGenerator(packageName, this); |
| intersectingPackages.put(packageName, tempPackageDiffGenerator); |
| tempPackageDiffGenerator.computeApiDiff(); |
| } |
| } |
| |
| /** |
| * Returns how ApiClass for first element is "related" to the ApiClass for |
| * secondElement. |
| */ |
| private Relation getRelationOfApiClassOfFirstArgToThatOfSecond(ApiElement firstApiElement, |
| ApiElement secondApiElement) { |
| JClassType firstClassType = null; |
| JClassType secondClassType = null; |
| if (firstApiElement instanceof ApiField) { |
| firstClassType = ((ApiField) firstApiElement).getApiClass().getClassObject(); |
| secondClassType = ((ApiField) secondApiElement).getApiClass().getClassObject(); |
| } |
| if (firstApiElement instanceof ApiAbstractMethod) { |
| firstClassType = ((ApiAbstractMethod) firstApiElement).getApiClass().getClassObject(); |
| secondClassType = ((ApiAbstractMethod) secondApiElement).getApiClass().getClassObject(); |
| } |
| if (firstClassType != null && secondClassType != null) { |
| if (secondClassType.isAssignableTo(firstClassType)) { |
| return Relation.SUPERCLASS; |
| } |
| if (firstClassType.isAssignableTo(secondClassType)) { |
| return Relation.SUBCLASS; |
| } |
| return Relation.NONE; |
| } |
| throw new RuntimeException("Inconsistent types for ApiElements: newApiElement " |
| + firstApiElement + ", oldApiElement : " + secondApiElement); |
| } |
| |
| } |