• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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