blob: c5946b92c0b0daa5b64d166b3279ad1e510fc0c0 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.tools.lint.checks;
import static com.android.SdkConstants.CONSTRUCTOR_NAME;
import com.android.annotations.Nullable;
import com.android.utils.Pair;
import com.google.common.collect.Lists;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Represents a class and its methods/fields.
*
* {@link #getSince()} gives the API level it was introduced.
*
* {@link #getMethod} returns when the method was introduced.
* {@link #getField} returns when the field was introduced.
*/
public class ApiClass {
private final String mName;
private final int mSince;
private final List<Pair<String, Integer>> mSuperClasses = Lists.newArrayList();
private final List<Pair<String, Integer>> mInterfaces = Lists.newArrayList();
private final Map<String, Integer> mFields = new HashMap<String, Integer>();
private final Map<String, Integer> mMethods = new HashMap<String, Integer>();
ApiClass(String name, int since) {
mName = name;
mSince = since;
}
/**
* Returns the name of the class.
* @return the name of the class
*/
String getName() {
return mName;
}
/**
* Returns when the class was introduced.
* @return the api level the class was introduced.
*/
int getSince() {
return mSince;
}
/**
* Returns when a field was added, or null if it doesn't exist.
* @param name the name of the field.
* @param info the corresponding info
*/
Integer getField(String name, Api info) {
// The field can come from this class or from a super class or an interface
// The value can never be lower than this introduction of this class.
// When looking at super classes and interfaces, it can never be lower than when the
// super class or interface was added as a super class or interface to this class.
// Look at all the values and take the lowest.
// For instance:
// This class A is introduced in 5 with super class B.
// In 10, the interface C was added.
// Looking for SOME_FIELD we get the following:
// Present in A in API 15
// Present in B in API 11
// Present in C in API 7.
// The answer is 10, which is when C became an interface
int min = Integer.MAX_VALUE;
Integer i = mFields.get(name);
if (i != null) {
min = i;
}
// now look at the super classes
for (Pair<String, Integer> superClassPair : mSuperClasses) {
ApiClass superClass = info.getClass(superClassPair.getFirst());
if (superClass != null) {
i = superClass.getField(name, info);
if (i != null) {
int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
if (tmp < min) {
min = tmp;
}
}
}
}
// now look at the interfaces
for (Pair<String, Integer> superClassPair : mInterfaces) {
ApiClass superClass = info.getClass(superClassPair.getFirst());
if (superClass != null) {
i = superClass.getField(name, info);
if (i != null) {
int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
if (tmp < min) {
min = tmp;
}
}
}
}
return min;
}
/**
* Returns when a method was added, or null if it doesn't exist. This goes through the super
* class to find method only present there.
* @param methodSignature the method signature
*/
int getMethod(String methodSignature, Api info) {
// The method can come from this class or from a super class.
// The value can never be lower than this introduction of this class.
// When looking at super classes, it can never be lower than when the super class became
// a super class of this class.
// Look at all the values and take the lowest.
// For instance:
// This class A is introduced in 5 with super class B.
// In 10, the super class changes to C.
// Looking for foo() we get the following:
// Present in A in API 15
// Present in B in API 11
// Present in C in API 7.
// The answer is 10, which is when C became the super class.
int min = Integer.MAX_VALUE;
Integer i = mMethods.get(methodSignature);
if (i != null) {
min = i;
// Constructors aren't inherited
if (methodSignature.startsWith(CONSTRUCTOR_NAME)) {
return i;
}
}
// now look at the super classes
for (Pair<String, Integer> superClassPair : mSuperClasses) {
ApiClass superClass = info.getClass(superClassPair.getFirst());
if (superClass != null) {
i = superClass.getMethod(methodSignature, info);
if (i != null) {
int tmp = superClassPair.getSecond() > i ? superClassPair.getSecond() : i;
if (tmp < min) {
min = tmp;
}
}
}
}
// now look at the interfaces classes
for (Pair<String, Integer> interfacePair : mInterfaces) {
ApiClass superClass = info.getClass(interfacePair.getFirst());
if (superClass != null) {
i = superClass.getMethod(methodSignature, info);
if (i != null) {
int tmp = interfacePair.getSecond() > i ? interfacePair.getSecond() : i;
if (tmp < min) {
min = tmp;
}
}
}
}
return min;
}
void addField(String name, int since) {
Integer i = mFields.get(name);
if (i == null || i.intValue() > since) {
mFields.put(name, Integer.valueOf(since));
}
}
void addMethod(String name, int since) {
// Strip off the method type at the end to ensure that the code which
// produces inherited methods doesn't get confused and end up multiple entries.
// For example, java/nio/Buffer has the method "array()Ljava/lang/Object;",
// and the subclass java/nio/ByteBuffer has the method "array()[B". We want
// the lookup on mMethods to associate the ByteBuffer array method to be
// considered overriding the Buffer method.
int index = name.indexOf(')');
if (index != -1) {
name = name.substring(0, index + 1);
}
Integer i = mMethods.get(name);
if (i == null || i.intValue() > since) {
mMethods.put(name, Integer.valueOf(since));
}
}
void addSuperClass(String superClass, int since) {
addToArray(mSuperClasses, superClass, since);
}
void addInterface(String interfaceClass, int since) {
addToArray(mInterfaces, interfaceClass, since);
}
static void addToArray(List<Pair<String, Integer>> list, String name, int value) {
// check if we already have that name (at a lower level)
for (Pair<String, Integer> pair : list) {
if (name.equals(pair.getFirst())) {
return;
}
}
list.add(Pair.of(name, Integer.valueOf(value)));
}
@Nullable
public String getPackage() {
int index = mName.lastIndexOf('/');
if (index != -1) {
return mName.substring(0, index);
}
return null;
}
@Override
public String toString() {
return mName;
}
/**
* Returns the set of all methods, including inherited
* ones.
*
* @param info the api to look up super classes from
* @return a set containing all the members fields
*/
Set<String> getAllMethods(Api info) {
Set<String> members = new HashSet<String>(100);
addAllMethods(info, members, true /*includeConstructors*/);
return members;
}
private void addAllMethods(Api info, Set<String> set, boolean includeConstructors) {
if (!includeConstructors) {
for (String method : mMethods.keySet()) {
if (!method.startsWith(CONSTRUCTOR_NAME)) {
set.add(method);
}
}
} else {
for (String method : mMethods.keySet()) {
set.add(method);
}
}
for (Pair<String, Integer> superClass : mSuperClasses) {
ApiClass clz = info.getClass(superClass.getFirst());
assert clz != null : superClass.getSecond();
if (clz != null) {
clz.addAllMethods(info, set, false);
}
}
// Get methods from implemented interfaces as well;
for (Pair<String, Integer> superClass : mInterfaces) {
ApiClass clz = info.getClass(superClass.getFirst());
assert clz != null : superClass.getSecond();
if (clz != null) {
clz.addAllMethods(info, set, false);
}
}
}
/**
* Returns the set of all fields, including inherited
* ones.
*
* @param info the api to look up super classes from
* @return a set containing all the fields
*/
Set<String> getAllFields(Api info) {
Set<String> members = new HashSet<String>(100);
addAllFields(info, members);
return members;
}
private void addAllFields(Api info, Set<String> set) {
for (String field : mFields.keySet()) {
set.add(field);
}
for (Pair<String, Integer> superClass : mSuperClasses) {
ApiClass clz = info.getClass(superClass.getFirst());
assert clz != null : superClass.getSecond();
if (clz != null) {
clz.addAllFields(info, set);
}
}
// Get methods from implemented interfaces as well;
for (Pair<String, Integer> superClass : mInterfaces) {
ApiClass clz = info.getClass(superClass.getFirst());
assert clz != null : superClass.getSecond();
if (clz != null) {
clz.addAllFields(info, set);
}
}
}
/* This code can be used to scan through all the fields and look for fields
that have moved to a higher class:
Field android/view/MotionEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
Field android/provider/ContactsContract$CommonDataKinds$Organization#PHONETIC_NAME has api=5 but parent android/provider/ContactsContract$ContactNameColumns provides it as 11
Field android/widget/ListView#CHOICE_MODE_MULTIPLE has api=1 but parent android/widget/AbsListView provides it as 11
Field android/widget/ListView#CHOICE_MODE_NONE has api=1 but parent android/widget/AbsListView provides it as 11
Field android/widget/ListView#CHOICE_MODE_SINGLE has api=1 but parent android/widget/AbsListView provides it as 11
Field android/view/KeyEvent#CREATOR has api=1 but parent android/view/InputEvent provides it as 9
This is used for example in the ApiDetector to filter out warnings which result
when people follow Eclipse's advice to replace
ListView.CHOICE_MODE_MULTIPLE
references with
AbsListView.CHOICE_MODE_MULTIPLE
since the latter has API=11 and the former has API=1; since the constant is unchanged
between the two, and the literal is copied into the class, using the AbsListView
reference works.
public void checkFields(Api info) {
fieldLoop:
for (String field : mFields.keySet()) {
Integer since = getField(field, info);
if (since == null || since == Integer.MAX_VALUE) {
continue;
}
for (Pair<String, Integer> superClass : mSuperClasses) {
ApiClass clz = info.getClass(superClass.getFirst());
assert clz != null : superClass.getSecond();
if (clz != null) {
Integer superSince = clz.getField(field, info);
if (superSince == Integer.MAX_VALUE) {
continue;
}
if (superSince != null && superSince > since) {
String declaredIn = clz.findFieldDeclaration(info, field);
System.out.println("Field " + getName() + "#" + field + " has api="
+ since + " but parent " + declaredIn + " provides it as "
+ superSince);
continue fieldLoop;
}
}
}
// Get methods from implemented interfaces as well;
for (Pair<String, Integer> superClass : mInterfaces) {
ApiClass clz = info.getClass(superClass.getFirst());
assert clz != null : superClass.getSecond();
if (clz != null) {
Integer superSince = clz.getField(field, info);
if (superSince == Integer.MAX_VALUE) {
continue;
}
if (superSince != null && superSince > since) {
String declaredIn = clz.findFieldDeclaration(info, field);
System.out.println("Field " + getName() + "#" + field + " has api="
+ since + " but parent " + declaredIn + " provides it as "
+ superSince);
continue fieldLoop;
}
}
}
}
}
private String findFieldDeclaration(Api info, String name) {
if (mFields.containsKey(name)) {
return getName();
}
for (Pair<String, Integer> superClass : mSuperClasses) {
ApiClass clz = info.getClass(superClass.getFirst());
assert clz != null : superClass.getSecond();
if (clz != null) {
String declaredIn = clz.findFieldDeclaration(info, name);
if (declaredIn != null) {
return declaredIn;
}
}
}
// Get methods from implemented interfaces as well;
for (Pair<String, Integer> superClass : mInterfaces) {
ApiClass clz = info.getClass(superClass.getFirst());
assert clz != null : superClass.getSecond();
if (clz != null) {
String declaredIn = clz.findFieldDeclaration(info, name);
if (declaredIn != null) {
return declaredIn;
}
}
}
return null;
}
*/
}