1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.apicheck; 18 import java.util.*; 19 20 public class ClassInfo { 21 private String mName; 22 private String mSuperClassName; 23 private boolean mIsInterface; 24 private boolean mIsAbstract; 25 private boolean mIsStatic; 26 private boolean mIsFinal; 27 private String mDeprecated; 28 private String mScope; 29 private List<String> mInterfaceNames; 30 private List<ClassInfo> mInterfaces; 31 private HashMap<String, MethodInfo> mMethods; 32 private HashMap<String, FieldInfo> mFields; 33 private HashMap<String, ConstructorInfo> mConstructors; 34 private boolean mExistsInBoth; 35 private PackageInfo mPackage; 36 private SourcePositionInfo mSourcePosition; 37 private ClassInfo mSuperClass; 38 private ClassInfo mParentClass; 39 ClassInfo(String name, PackageInfo pack, String superClass, boolean isInterface, boolean isAbstract, boolean isStatic, boolean isFinal, String deprecated, String visibility, SourcePositionInfo source, ClassInfo parent)40 public ClassInfo(String name, PackageInfo pack, String superClass, boolean isInterface, 41 boolean isAbstract, boolean isStatic, boolean isFinal, String deprecated, 42 String visibility, SourcePositionInfo source, ClassInfo parent) { 43 mName = name; 44 mPackage = pack; 45 mSuperClassName = superClass; 46 mIsInterface = isInterface; 47 mIsAbstract = isAbstract; 48 mIsStatic = isStatic; 49 mIsFinal = isFinal; 50 mDeprecated = deprecated; 51 mScope = visibility; 52 mInterfaceNames = new ArrayList<String>(); 53 mInterfaces = new ArrayList<ClassInfo>(); 54 mMethods = new HashMap<String, MethodInfo>(); 55 mFields = new HashMap<String, FieldInfo>(); 56 mConstructors = new HashMap<String, ConstructorInfo>(); 57 mExistsInBoth = false; 58 mSourcePosition = source; 59 mParentClass = parent; 60 } 61 name()62 public String name() { 63 return mName; 64 } 65 qualifiedName()66 public String qualifiedName() { 67 String parentQName = (mParentClass != null) 68 ? (mParentClass.qualifiedName() + ".") 69 : ""; 70 return mPackage.name() + "." + parentQName + name(); 71 } 72 superclassName()73 public String superclassName() { 74 return mSuperClassName; 75 } 76 position()77 public SourcePositionInfo position() { 78 return mSourcePosition; 79 } 80 isInterface()81 public boolean isInterface() { 82 return mIsInterface; 83 } 84 isFinal()85 public boolean isFinal() { 86 return mIsFinal; 87 } 88 89 // Find a superclass implementation of the given method. Looking at our superclass 90 // instead of at 'this' is unusual, but it fits the point-of-call demands well. overriddenMethod(MethodInfo candidate)91 public MethodInfo overriddenMethod(MethodInfo candidate) { 92 if (mSuperClass == null) { 93 return null; 94 } 95 96 // does our immediate superclass have it? 97 ClassInfo sup = mSuperClass; 98 for (MethodInfo mi : sup.mMethods.values()) { 99 if (mi.matches(candidate)) { 100 // found it 101 return mi; 102 } 103 } 104 105 // no, so recurse 106 if (sup.mSuperClass != null) { 107 return mSuperClass.overriddenMethod(candidate); 108 } 109 110 // no parent, so we just don't have it 111 return null; 112 } 113 114 // Find a superinterface declaration of the given method. interfaceMethod(MethodInfo candidate)115 public MethodInfo interfaceMethod(MethodInfo candidate) { 116 for (ClassInfo interfaceInfo : mInterfaces) { 117 for (MethodInfo mi : interfaceInfo.mMethods.values()) { 118 if (mi.matches(candidate)) { 119 return mi; 120 } 121 } 122 } 123 return (mSuperClass != null) ? mSuperClass.interfaceMethod(candidate) : null; 124 } 125 isConsistent(ClassInfo cl)126 public boolean isConsistent(ClassInfo cl) { 127 cl.mExistsInBoth = true; 128 mExistsInBoth = true; 129 boolean consistent = true; 130 131 if (isInterface() != cl.isInterface()) { 132 Errors.error(Errors.CHANGED_CLASS, cl.position(), 133 "Class " + cl.qualifiedName() 134 + " changed class/interface declaration"); 135 consistent = false; 136 } 137 for (String iface : mInterfaceNames) { 138 boolean found = false; 139 for (ClassInfo c = cl; c != null && !found; c = c.mSuperClass) { 140 found = c.mInterfaceNames.contains(iface); 141 } 142 if (!found) { 143 Errors.error(Errors.REMOVED_INTERFACE, cl.position(), 144 "Class " + qualifiedName() + " no longer implements " + iface); 145 } 146 } 147 for (String iface : cl.mInterfaceNames) { 148 if (!mInterfaceNames.contains(iface)) { 149 Errors.error(Errors.ADDED_INTERFACE, cl.position(), 150 "Added interface " + iface + " to class " 151 + qualifiedName()); 152 consistent = false; 153 } 154 } 155 156 for (MethodInfo mInfo : mMethods.values()) { 157 if (cl.mMethods.containsKey(mInfo.getHashableName())) { 158 if (!mInfo.isConsistent(cl.mMethods.get(mInfo.getHashableName()))) { 159 consistent = false; 160 } 161 } else { 162 /* This class formerly provided this method directly, and now does not. 163 * Check our ancestry to see if there's an inherited version that still 164 * fulfills the API requirement. 165 */ 166 MethodInfo mi = mInfo.containingClass().overriddenMethod(mInfo); 167 if (mi == null) { 168 mi = mInfo.containingClass().interfaceMethod(mInfo); 169 } 170 if (mi == null) { 171 Errors.error(Errors.REMOVED_METHOD, mInfo.position(), 172 "Removed public method " + mInfo.qualifiedName()); 173 consistent = false; 174 } 175 } 176 } 177 for (MethodInfo mInfo : cl.mMethods.values()) { 178 if (!mInfo.isInBoth()) { 179 /* Similarly to the above, do not fail if this "new" method is 180 * really an override of an existing superclass method. 181 */ 182 MethodInfo mi = mInfo.containingClass().overriddenMethod(mInfo); 183 if (mi == null) { 184 Errors.error(Errors.ADDED_METHOD, mInfo.position(), 185 "Added public method " + mInfo.qualifiedName()); 186 consistent = false; 187 } 188 } 189 } 190 191 for (ConstructorInfo mInfo : mConstructors.values()) { 192 if (cl.mConstructors.containsKey(mInfo.getHashableName())) { 193 if (!mInfo.isConsistent(cl.mConstructors.get(mInfo.getHashableName()))) { 194 consistent = false; 195 } 196 } else { 197 Errors.error(Errors.REMOVED_METHOD, mInfo.position(), 198 "Removed public constructor " + mInfo.prettySignature()); 199 consistent = false; 200 } 201 } 202 for (ConstructorInfo mInfo : cl.mConstructors.values()) { 203 if (!mInfo.isInBoth()) { 204 Errors.error(Errors.ADDED_METHOD, mInfo.position(), 205 "Added public constructor " + mInfo.prettySignature()); 206 consistent = false; 207 } 208 } 209 210 for (FieldInfo mInfo : mFields.values()) { 211 if (cl.mFields.containsKey(mInfo.name())) { 212 if (!mInfo.isConsistent(cl.mFields.get(mInfo.name()))) { 213 consistent = false; 214 } 215 } else { 216 Errors.error(Errors.REMOVED_FIELD, mInfo.position(), 217 "Removed field " + mInfo.qualifiedName()); 218 consistent = false; 219 } 220 } 221 for (FieldInfo mInfo : cl.mFields.values()) { 222 if (!mInfo.isInBoth()) { 223 Errors.error(Errors.ADDED_FIELD, mInfo.position(), 224 "Added public field " + mInfo.qualifiedName()); 225 consistent = false; 226 } 227 } 228 229 if (mIsAbstract != cl.mIsAbstract) { 230 consistent = false; 231 Errors.error(Errors.CHANGED_ABSTRACT, cl.position(), 232 "Class " + cl.qualifiedName() + " changed abstract qualifier"); 233 } 234 235 if (mIsFinal != cl.mIsFinal) { 236 consistent = false; 237 Errors.error(Errors.CHANGED_FINAL, cl.position(), 238 "Class " + cl.qualifiedName() + " changed final qualifier"); 239 } 240 241 if (mIsStatic != cl.mIsStatic) { 242 consistent = false; 243 Errors.error(Errors.CHANGED_STATIC, cl.position(), 244 "Class " + cl.qualifiedName() + " changed static qualifier"); 245 } 246 247 if (!mScope.equals(cl.mScope)) { 248 consistent = false; 249 Errors.error(Errors.CHANGED_SCOPE, cl.position(), 250 "Class " + cl.qualifiedName() + " scope changed from " 251 + mScope + " to " + cl.mScope); 252 } 253 254 if (!mDeprecated.equals(cl.mDeprecated)) { 255 consistent = false; 256 Errors.error(Errors.CHANGED_DEPRECATED, cl.position(), 257 "Class " + cl.qualifiedName() + " has changed deprecation state"); 258 } 259 260 if (mSuperClassName != null) { 261 if (cl.mSuperClassName == null || !mSuperClassName.equals(cl.mSuperClassName)) { 262 consistent = false; 263 Errors.error(Errors.CHANGED_SUPERCLASS, cl.position(), 264 "Class " + qualifiedName() + " superclass changed from " 265 + mSuperClassName + " to " + cl.mSuperClassName); 266 } 267 } else if (cl.mSuperClassName != null) { 268 consistent = false; 269 Errors.error(Errors.CHANGED_SUPERCLASS, cl.position(), 270 "Class " + qualifiedName() + " superclass changed from " 271 + "null to " + cl.mSuperClassName); 272 } 273 274 return consistent; 275 } 276 resolveInterfaces(ApiInfo apiInfo)277 public void resolveInterfaces(ApiInfo apiInfo) { 278 for (String interfaceName : mInterfaceNames) { 279 mInterfaces.add(apiInfo.findClass(interfaceName)); 280 } 281 } 282 addInterface(String name)283 public void addInterface(String name) { 284 mInterfaceNames.add(name); 285 } 286 addMethod(MethodInfo mInfo)287 public void addMethod(MethodInfo mInfo) { 288 mMethods.put(mInfo.getHashableName(), mInfo); 289 } 290 addConstructor(ConstructorInfo cInfo)291 public void addConstructor(ConstructorInfo cInfo) { 292 mConstructors.put(cInfo.getHashableName(), cInfo); 293 294 } 295 addField(FieldInfo fInfo)296 public void addField(FieldInfo fInfo) { 297 mFields.put(fInfo.name(), fInfo); 298 299 } 300 setSuperClass(ClassInfo superclass)301 public void setSuperClass(ClassInfo superclass) { 302 mSuperClass = superclass; 303 } 304 isInBoth()305 public boolean isInBoth() { 306 return mExistsInBoth; 307 } 308 allConstructors()309 public Map<String, ConstructorInfo> allConstructors() { 310 return mConstructors; 311 } 312 allFields()313 public Map<String, FieldInfo> allFields() { 314 return mFields; 315 } 316 allMethods()317 public Map<String, MethodInfo> allMethods() { 318 return mMethods; 319 } 320 321 /** 322 * Returns the class hierarchy for this class, starting with this class. 323 */ hierarchy()324 public Iterable<ClassInfo> hierarchy() { 325 List<ClassInfo> result = new ArrayList<ClassInfo>(4); 326 for (ClassInfo c = this; c != null; c = c.mSuperClass) { 327 result.add(c); 328 } 329 return result; 330 } 331 } 332