1 /* 2 * Copyright (C) 2012 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.tools.lint.checks; 18 19 import com.android.util.Pair; 20 21 import java.util.ArrayList; 22 import java.util.HashMap; 23 import java.util.HashSet; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.Set; 27 28 /** 29 * Represents a class and its methods/fields. 30 * 31 * {@link #getSince()} gives the API level it was introduced. 32 * 33 * {@link #getMethod(String)} returns when the method was introduced. 34 * {@link #getField(String)} returns when the field was introduced. 35 */ 36 public class ApiClass { 37 38 private final String mName; 39 private final int mSince; 40 41 private final List<Pair<String, Integer>> mSuperClasses = new ArrayList<Pair<String, Integer>>(); 42 private final List<Pair<String, Integer>> mInterfaces = new ArrayList<Pair<String, Integer>>(); 43 44 private final Map<String, Integer> mFields = new HashMap<String, Integer>(); 45 private final Map<String, Integer> mMethods = new HashMap<String, Integer>(); 46 ApiClass(String name, int since)47 public ApiClass(String name, int since) { 48 mName = name; 49 mSince = since; 50 } 51 52 /** 53 * Returns the name of the class. 54 * @return the name of the class 55 */ getName()56 public String getName() { 57 return mName; 58 } 59 60 /** 61 * Returns when the class was introduced. 62 * @return the api level the class was introduced. 63 */ getSince()64 public int getSince() { 65 return mSince; 66 } 67 68 /** 69 * Returns when a field was added, or null if it doesn't exist. 70 * @param name the name of the field. 71 * @return 72 */ getField(String name, Api info)73 public Integer getField(String name, Api info) { 74 // The field can come from this class or from a super class or an interface 75 // The value can never be lower than this introduction of this class. 76 // When looking at super classes and interfaces, it can never be lower than when the 77 // super class or interface was added as a super class or interface to this clas. 78 // Look at all the values and take the lowest. 79 // For instance: 80 // This class A is introduced in 5 with super class B. 81 // In 10, the interface C was added. 82 // Looking for SOME_FIELD we get the following: 83 // Present in A in API 15 84 // Present in B in API 11 85 // Present in C in API 7. 86 // The answer is 10, which is when C became an interface 87 int min = Integer.MAX_VALUE; 88 Integer i = mFields.get(name); 89 if (i != null) { 90 min = i; 91 } 92 93 // now look at the super classes 94 for (Pair<String, Integer> superClassPair : mSuperClasses) { 95 ApiClass superClass = info.getClass(superClassPair.getFirst()); 96 if (superClass != null) { 97 i = superClass.getField(name, info); 98 if (i != null) { 99 int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i; 100 if (tmp < min) { 101 min = tmp; 102 } 103 } 104 } 105 } 106 107 // now look at the interfaces 108 for (Pair<String, Integer> superClassPair : mInterfaces) { 109 ApiClass superClass = info.getClass(superClassPair.getFirst()); 110 if (superClass != null) { 111 i = superClass.getField(name, info); 112 if (i != null) { 113 int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i; 114 if (tmp < min) { 115 min = tmp; 116 } 117 } 118 } 119 } 120 121 return min; 122 } 123 124 /** 125 * Returns when a method was added, or null if it doesn't exist. This goes through the super 126 * class to find method only present there. 127 * @param methodSignature the method signature 128 * @return 129 */ getMethod(String methodSignature, Api info)130 public int getMethod(String methodSignature, Api info) { 131 // The method can come from this class or from a super class. 132 // The value can never be lower than this introduction of this class. 133 // When looking at super classes, it can never be lower than when the super class became 134 // a super class of this class. 135 // Look at all the values and take the lowest. 136 // For instance: 137 // This class A is introduced in 5 with super class B. 138 // In 10, the super class changes to C. 139 // Looking for foo() we get the following: 140 // Present in A in API 15 141 // Present in B in API 11 142 // Present in C in API 7. 143 // The answer is 10, which is when C became the super class. 144 int min = Integer.MAX_VALUE; 145 Integer i = mMethods.get(methodSignature); 146 if (i != null) { 147 min = i; 148 } 149 150 // now look at the super classes 151 for (Pair<String, Integer> superClassPair : mSuperClasses) { 152 ApiClass superClass = info.getClass(superClassPair.getFirst()); 153 if (superClass != null) { 154 i = superClass.getMethod(methodSignature, info); 155 if (i != null) { 156 int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i; 157 if (tmp < min) { 158 min = tmp; 159 } 160 } 161 } 162 } 163 164 return min; 165 } 166 addField(String name, int since)167 public void addField(String name, int since) { 168 Integer i = mFields.get(name); 169 if (i == null || i.intValue() > since) { 170 mFields.put(name, Integer.valueOf(since)); 171 } 172 } 173 addMethod(String name, int since)174 public void addMethod(String name, int since) { 175 // Strip off the method type at the end to ensure that the code which 176 // produces inherited methods doesn't get confused and end up multiple entries. 177 // For example, java/nio/Buffer has the method "array()Ljava/lang/Object;", 178 // and the subclass java/nio/ByteBuffer has the method "array()[B". We want 179 // the lookup on mMethods to associate the ByteBuffer array method to be 180 // considered overriding the Buffer method. 181 int index = name.indexOf(')'); 182 if (index != -1) { 183 name = name.substring(0, index + 1); 184 } 185 186 Integer i = mMethods.get(name); 187 if (i == null || i.intValue() > since) { 188 mMethods.put(name, Integer.valueOf(since)); 189 } 190 } 191 addSuperClass(String superClass, int since)192 public void addSuperClass(String superClass, int since) { 193 addToArray(mSuperClasses, superClass, since); 194 } 195 addInterface(String interfaceClass, int since)196 public void addInterface(String interfaceClass, int since) { 197 addToArray(mInterfaces, interfaceClass, since); 198 } 199 addToArray(List<Pair<String, Integer>> list, String name, int value)200 void addToArray(List<Pair<String, Integer>> list, String name, int value) { 201 // check if we already have that name (at a lower level) 202 for (Pair<String, Integer> pair : list) { 203 if (name.equals(pair.getFirst())) { 204 return; 205 } 206 } 207 208 list.add(Pair.of(name, Integer.valueOf(value))); 209 210 } 211 212 @Override toString()213 public String toString() { 214 return mName; 215 } 216 217 /** 218 * Returns the set of all methods, including inherited 219 * ones. 220 * 221 * @param info the api to look up super classes from 222 * @return a set containing all the members fields 223 */ getAllMethods(Api info)224 public Set<String> getAllMethods(Api info) { 225 Set<String> members = new HashSet<String>(100); 226 addAllMethods(info, members); 227 228 return members; 229 } 230 addAllMethods(Api info, Set<String> set)231 private void addAllMethods(Api info, Set<String> set) { 232 for (String method : mMethods.keySet()) { 233 set.add(method); 234 } 235 for (Pair<String, Integer> superClass : mSuperClasses) { 236 ApiClass clz = info.getClass(superClass.getFirst()); 237 assert clz != null : superClass.getSecond(); 238 if (clz != null) { 239 clz.addAllMethods(info, set); 240 } 241 } 242 } 243 244 /** 245 * Returns the set of all fields, including inherited 246 * ones. 247 * 248 * @param info the api to look up super classes from 249 * @return a set containing all the fields 250 */ getAllFields(Api info)251 public Set<String> getAllFields(Api info) { 252 Set<String> members = new HashSet<String>(100); 253 addAllFields(info, members); 254 255 return members; 256 } 257 addAllFields(Api info, Set<String> set)258 private void addAllFields(Api info, Set<String> set) { 259 for (String field : mFields.keySet()) { 260 set.add(field); 261 } 262 for (Pair<String, Integer> superClass : mSuperClasses) { 263 ApiClass clz = info.getClass(superClass.getFirst()); 264 assert clz != null : superClass.getSecond(); 265 if (clz != null) { 266 clz.addAllFields(info, set); 267 } 268 } 269 } 270 271 } 272