| /* |
| * 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.TreeLogger; |
| import com.google.gwt.core.ext.typeinfo.JAbstractMethod; |
| import com.google.gwt.core.ext.typeinfo.JClassType; |
| import com.google.gwt.core.ext.typeinfo.JField; |
| import com.google.gwt.core.ext.typeinfo.JMethod; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.EnumMap; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Encapsulates an API class. |
| */ |
| final class ApiClass implements Comparable<ApiClass>, ApiElement { |
| /** |
| * Enum for indexing the common storage used for methods and constructors |
| * |
| */ |
| public static enum MethodType { |
| CONSTRUCTOR, METHOD; |
| } |
| |
| private HashMap<String, ApiField> apiFields = null; |
| |
| /** |
| * TODO (amitmanjhi): Toby felt that combining structures for storing |
| * MethodType and Constructors was unnecessary. In particular, the hashMap of |
| * name#args -> object is meaningless for constructor, since name is empty for |
| * constructors. Make it separate. |
| * |
| * In addition, the current method fails when constructors or methods accept |
| * variable arguments. In future, just index everything by name [and not add |
| * the number of arguments that they accept]. |
| */ |
| |
| /** |
| * 2 entries in the list: one for CONSTRUCTOR, and the other for METHOD. Each |
| * entry is a mapping from MethodName#args to a set of ApiAbstractMethod |
| */ |
| private EnumMap<MethodType, Map<String, Set<ApiAbstractMethod>>> apiMembersByName = |
| null; |
| |
| private final ApiPackage apiPackage; |
| private final JClassType classType; |
| |
| private final boolean isInstantiableApiClass; |
| private final boolean isNotsubclassableApiClass; |
| private final boolean isSubclassableApiClass; |
| private final TreeLogger logger; |
| |
| ApiClass(JClassType classType, ApiPackage apiPackage) { |
| this.classType = classType; |
| this.apiPackage = apiPackage; |
| logger = apiPackage.getApiContainer().getLogger(); |
| ApiContainer apiContainer = apiPackage.getApiContainer(); |
| isSubclassableApiClass = apiContainer.isSubclassableApiClass(classType); |
| isNotsubclassableApiClass = |
| apiContainer.isNotsubclassableApiClass(classType); |
| isInstantiableApiClass = apiContainer.isInstantiableApiClass(classType); |
| } |
| |
| public int compareTo(ApiClass other) { |
| return getName().compareTo(other.getName()); |
| } |
| |
| public String getRelativeSignature() { |
| return classType.getQualifiedSourceName(); |
| } |
| |
| @Override |
| public String toString() { |
| return classType.toString(); |
| } |
| |
| String getApiAsString() { |
| StringBuffer sb = new StringBuffer(); |
| sb.append("\t" + getName() + "\n"); |
| if (apiFields != null) { |
| ArrayList<ApiField> apiFieldsList = |
| new ArrayList<ApiField>(apiFields.values()); |
| Collections.sort(apiFieldsList); |
| for (ApiField apiField : apiFieldsList) { |
| sb.append("\t\t" + apiField.getRelativeSignature() + "\n"); |
| } |
| } |
| if (apiMembersByName != null |
| && apiMembersByName.get(MethodType.METHOD) != null) { |
| for (MethodType method : MethodType.values()) { |
| HashSet<ApiAbstractMethod> apiMethodsSet = |
| new HashSet<ApiAbstractMethod>(); |
| for (Set<ApiAbstractMethod> methodsSets : apiMembersByName.get(method).values()) { |
| apiMethodsSet.addAll(methodsSets); |
| } |
| ArrayList<ApiAbstractMethod> apiMethodsList = |
| new ArrayList<ApiAbstractMethod>(apiMethodsSet); |
| Collections.sort(apiMethodsList); |
| for (ApiAbstractMethod apiMethod : apiMethodsList) { |
| sb.append("\t\t" + apiMethod.getRelativeSignature() + "\n"); |
| } |
| } |
| } |
| return sb.toString(); |
| } |
| |
| ApiField getApiFieldByName(String name) { |
| return apiFields.get(name); |
| } |
| |
| Set<String> getApiFieldNames() { |
| if (apiFields == null) { |
| initializeApiFields(); |
| } |
| return new HashSet<String>(apiFields.keySet()); |
| } |
| |
| Set<ApiField> getApiFieldsBySet(Set<String> names) { |
| Set<ApiField> ret = new HashSet<ApiField>(); |
| for (String name : names) { |
| ret.add(apiFields.get(name)); |
| } |
| return ret; |
| } |
| |
| Set<String> getApiMemberNames(MethodType type) { |
| if (apiMembersByName == null) { |
| initializeApiConstructorsAndMethods(); |
| } |
| return new HashSet<String>(apiMembersByName.get(type).keySet()); |
| } |
| |
| Set<ApiAbstractMethod> getApiMembersBySet(Set<String> methodNames, |
| MethodType type) { |
| Map<String, Set<ApiAbstractMethod>> current = apiMembersByName.get(type); |
| Set<ApiAbstractMethod> tempMethods = new HashSet<ApiAbstractMethod>(); |
| for (String methodName : methodNames) { |
| tempMethods.addAll(current.get(methodName)); |
| } |
| return tempMethods; |
| } |
| |
| Set<ApiAbstractMethod> getApiMethodsByName(String name, MethodType type) { |
| return apiMembersByName.get(type).get(name); |
| } |
| |
| JClassType getClassObject() { |
| return classType; |
| } |
| |
| String getFullName() { |
| return classType.getQualifiedSourceName(); |
| } |
| |
| /** |
| * compute the modifier changes. check for: (i) added 'final' or 'abstract' or |
| * 'static' (ii) removed 'static' or 'non-abstract class made into interface' |
| * (if a non-abstract class is made into interface, the client class/interface |
| * inheriting from it would need to change) |
| */ |
| List<ApiChange.Status> getModifierChanges(ApiClass newClass) { |
| JClassType newClassType = newClass.getClassObject(); |
| List<ApiChange.Status> statuses = new ArrayList<ApiChange.Status>(5); |
| |
| // check for addition of 'final', 'abstract', 'static' |
| if (!classType.isFinal() && newClassType.isFinal()) { |
| statuses.add(ApiChange.Status.FINAL_ADDED); |
| } |
| if (!classType.isAbstract() && newClassType.isAbstract()) { |
| statuses.add(ApiChange.Status.ABSTRACT_ADDED); |
| } |
| if (!classType.isStatic() && newClassType.isStatic()) { |
| statuses.add(ApiChange.Status.STATIC_ADDED); |
| } |
| |
| // removed 'static' |
| if (classType.isStatic() && !newClassType.isStatic()) { |
| statuses.add(ApiChange.Status.STATIC_REMOVED); |
| } |
| |
| if (!classType.isAbstract() && (newClassType.isInterface() != null)) { |
| statuses.add(ApiChange.Status.NONABSTRACT_CLASS_MADE_INTERFACE); |
| } |
| if (apiPackage.getApiContainer().isSubclassableApiClass(classType)) { |
| if ((classType.isClass() != null) && (newClassType.isInterface() != null)) { |
| statuses.add(ApiChange.Status.SUBCLASSABLE_API_CLASS_MADE_INTERFACE); |
| } |
| if ((classType.isInterface() != null) && (newClassType.isClass() != null)) { |
| statuses.add(ApiChange.Status.SUBCLASSABLE_API_INTERFACE_MADE_CLASS); |
| } |
| } |
| return statuses; |
| } |
| |
| String getName() { |
| return classType.getName(); |
| } |
| |
| ApiPackage getPackage() { |
| return apiPackage; |
| } |
| |
| void initializeApiFields() { |
| apiFields = new HashMap<String, ApiField>(); |
| List<String> notAddedFields = new ArrayList<String>(); |
| JField fields[] = getAccessibleFields(); |
| for (JField field : fields) { |
| if (isApiMember(field)) { |
| apiFields.put(ApiField.computeApiSignature(field), new ApiField(field, |
| this)); |
| } else { |
| notAddedFields.add(field.toString()); |
| } |
| } |
| if (notAddedFields.size() > 0) { |
| logger.log(TreeLogger.SPAM, "class " + getName() + " " + ", not adding " |
| + notAddedFields.size() + " nonApi fields: " + notAddedFields, null); |
| } |
| } |
| |
| private JField[] getAccessibleFields() { |
| Map<String, JField> fieldsBySignature = new HashMap<String, JField>(); |
| JClassType tempClassType = classType; |
| do { |
| JField declaredFields[] = tempClassType.getFields(); |
| for (JField field : declaredFields) { |
| if (field.isPrivate()) { |
| continue; |
| } |
| String signature = field.toString(); |
| JField existing = fieldsBySignature.put(signature, field); |
| if (existing != null) { |
| // TODO(amitmanjhi): Consider whether this is sufficient |
| fieldsBySignature.put(signature, existing); |
| } |
| } |
| tempClassType = tempClassType.getSuperclass(); |
| } while (tempClassType != null); |
| return fieldsBySignature.values().toArray(new JField[0]); |
| } |
| |
| // TODO(amitmanjhi): to optimize, cache results |
| private JMethod[] getAccessibleMethods() { |
| boolean isInterface = false; |
| if (classType.isInterface() != null) { |
| isInterface = true; |
| } |
| Map<String, JMethod> methodsBySignature = new HashMap<String, JMethod>(); |
| LinkedList<JClassType> classesToBeProcessed = new LinkedList<JClassType>(); |
| classesToBeProcessed.add(classType); |
| JClassType tempClassType = null; |
| while (classesToBeProcessed.peek() != null) { |
| tempClassType = classesToBeProcessed.remove(); |
| JMethod declaredMethods[] = tempClassType.getMethods(); |
| for (JMethod method : declaredMethods) { |
| if (method.isPrivate()) { |
| continue; |
| } |
| String signature = ApiAbstractMethod.computeInternalSignature(method); |
| JMethod existing = methodsBySignature.put(signature, method); |
| if (existing != null) { |
| // decide which implementation to keep |
| if (existing.getEnclosingType().isAssignableTo( |
| method.getEnclosingType())) { |
| methodsBySignature.put(signature, existing); |
| } |
| } |
| } |
| if (isInterface) { |
| classesToBeProcessed.addAll(Arrays.asList(tempClassType.getImplementedInterfaces())); |
| } else { |
| classesToBeProcessed.add(tempClassType.getSuperclass()); |
| } |
| } |
| return methodsBySignature.values().toArray(new JMethod[0]); |
| } |
| |
| private JAbstractMethod[] getAccessibleMethods(MethodType member) { |
| switch (member) { |
| case CONSTRUCTOR: |
| return classType.getConstructors(); |
| case METHOD: |
| return getAccessibleMethods(); |
| } |
| throw new AssertionError("Unknown value : " + member); |
| } |
| |
| private void initializeApiConstructorsAndMethods() { |
| apiMembersByName = |
| new EnumMap<MethodType, Map<String, Set<ApiAbstractMethod>>>( |
| MethodType.class); |
| for (MethodType method : MethodType.values()) { |
| apiMembersByName.put(method, |
| new HashMap<String, Set<ApiAbstractMethod>>()); |
| Map<String, Set<ApiAbstractMethod>> pointer = |
| apiMembersByName.get(method); |
| List<String> notAddedMembers = new ArrayList<String>(); |
| JAbstractMethod jams[] = getAccessibleMethods(method); |
| for (JAbstractMethod jam : jams) { |
| if (isApiMember(jam)) { |
| String tempName = jam.getName() + jam.getParameters().length; |
| Set<ApiAbstractMethod> existingMembers = pointer.get(tempName); |
| if (existingMembers == null) { |
| existingMembers = new HashSet<ApiAbstractMethod>(); |
| } |
| switch (method) { |
| case CONSTRUCTOR: |
| existingMembers.add(new ApiConstructor(jam, this)); |
| break; |
| case METHOD: |
| existingMembers.add(new ApiMethod(jam, this)); |
| break; |
| default: |
| throw new AssertionError("Unknown memberType : " + method); |
| } |
| pointer.put(tempName, existingMembers); |
| } else { |
| notAddedMembers.add(jam.toString()); |
| } |
| } |
| if (notAddedMembers.size() > 0) { |
| logger.log(TreeLogger.SPAM, "class " + getName() + ", removing " |
| + notAddedMembers.size() + " nonApi members: " + notAddedMembers, |
| null); |
| } |
| } |
| } |
| |
| /** |
| * Note: Instance members of a class that is not instantiable are not api |
| * members. |
| */ |
| private boolean isApiMember(final Object member) { |
| boolean isPublic = false; |
| boolean isPublicOrProtected = false; |
| boolean isStatic = false; |
| |
| if (member instanceof JField) { |
| JField field = (JField) member; |
| isPublic = field.isPublic(); |
| isPublicOrProtected = isPublic || field.isProtected(); |
| isStatic = field.isStatic(); |
| } |
| if (member instanceof JAbstractMethod) { |
| JAbstractMethod method = (JAbstractMethod) member; |
| isPublic = method.isPublic(); |
| isPublicOrProtected = isPublic || method.isProtected(); |
| if (method instanceof JMethod) { |
| JMethod temp = (JMethod) method; |
| isStatic = temp.isStatic(); |
| } else { |
| isStatic = false; // constructors can't be static |
| } |
| } |
| if (ApiCompatibilityChecker.REMOVE_NON_SUBCLASSABLE_ABSTRACT_CLASS_FROM_API) { |
| if (!isInstantiableApiClass && !isStatic && !isSubclassableApiClass) { |
| return false; |
| } |
| } |
| return (isSubclassableApiClass && isPublicOrProtected) |
| || (isNotsubclassableApiClass && isPublic); |
| } |
| |
| } |