blob: 8be4bb105de3e4e71ffe7887f78ba6e7d20ab87d [file] [log] [blame]
/*
* Copyright (C) 2015 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.idea.debug;
import com.android.SdkConstants;
import com.android.tools.lint.checks.SupportAnnotationDetector;
import com.google.common.collect.Maps;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaConstantExpressionEvaluator;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.android.inspections.ResourceTypeInspection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Collection;
import java.util.Map;
import static com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE;
import static com.android.SdkConstants.TYPE_DEF_VALUE_ATTRIBUTE;
public class AndroidResolveHelper {
public static class ResolveResult {
@NotNull public final String label;
@Nullable public final Icon icon;
public ResolveResult(@NotNull String label, @Nullable Icon icon) {
this.label = label;
this.icon = icon;
}
}
@Nullable
public static PsiAnnotation getAnnotationForLocal(@NotNull PsiElement context, @NotNull String name) {
ApplicationManager.getApplication().assertReadAccessAllowed();
JavaPsiFacade facade = JavaPsiFacade.getInstance(context.getProject());
PsiVariable variable = facade.getResolveHelper().resolveReferencedVariable(name, context);
if (variable != null) {
return getAnnotationForVariable(variable, 0);
}
return null;
}
@Nullable
private static PsiAnnotation getAnnotationForVariable(@NotNull PsiVariable variable, int depth) {
// Prevent deep recursion, and cycles if we try to chase assignment values from mutually assigned
// variables in code like this:
// int var1 = 0, var2 = 0;
// var1 = var2;
// var2 = var1;
if (depth > 10) {
return null;
}
// Parameter? Those can carry annotations directly. So can local variable declarations.
if (variable instanceof PsiParameter || variable instanceof PsiLocalVariable) {
PsiAnnotation annotation = getAnnotation(variable);
if (annotation != null) {
return annotation;
}
}
// We're stepping through the code, and we can be far from the variable declaration
// location; the variable can be assigned in multiple places, so to correctly determine
// its current value semantics we'd need to know exactly where the debugger is (the
// current program counter), map that back to the AST, and then flow backwards to the most
// recent assignment, and then chase the value chain from there.
//
// This is necessary if we want to correctly pick @Type1 versus @Type2 based on the
// program location here:
// void test(int var, @Type1 int type1, @Type2 int type2, @ColorInt int rgba) {
// var = type1;
// ...
// var = type2;
// ...
// var = rgba;
// ...
// However, this scenario is unlikely in real code.
//
// It's much more likely that a value will be assigned at most one typedef bound
// value. Therefore, by searching through the known set of assignments and picking the
// first match, we're likely to find a typedef which is helpful to the user.
PsiExpression initializer = variable.getInitializer();
if (initializer != null) {
if (initializer instanceof PsiMethodCallExpression) {
PsiMethodCallExpression callExpression = (PsiMethodCallExpression)initializer;
PsiElement resolved = callExpression.getMethodExpression().resolve();
if (resolved instanceof PsiMethod) {
PsiAnnotation annotation = getAnnotation((PsiMethod)resolved);
if (annotation != null) {
return annotation;
}
}
} else if (initializer instanceof PsiReferenceExpression) {
PsiReferenceExpression reference = (PsiReferenceExpression) initializer;
PsiElement resolved = reference.resolve();
if (resolved instanceof PsiField) {
PsiAnnotation annotation = getAnnotationForField((PsiField)resolved);
if (annotation != null) {
return annotation;
}
} else if (resolved instanceof PsiVariable) {
PsiAnnotation annotation = getAnnotationForVariable((PsiVariable)resolved, depth + 1);
if (annotation != null) {
return annotation;
}
}
}
}
PsiMethod method = PsiTreeUtil.getParentOfType(variable, PsiMethod.class, true);
if (method == null) {
return null;
}
// Variable not initialized; might be assigned to. Look for assignments.
Collection<PsiAssignmentExpression> assignments = PsiTreeUtil.findChildrenOfType(method, PsiAssignmentExpression.class);
for (PsiAssignmentExpression assignment : assignments) {
PsiExpression lhs = assignment.getLExpression();
if (lhs instanceof PsiReferenceExpression) {
PsiReferenceExpression reference = (PsiReferenceExpression) lhs;
PsiElement resolved = reference.resolve();
if (resolved == variable) {
// Yes, assigning to our target variable
// Look at the rhs to see if we can figure out the type
PsiExpression rhs = assignment.getRExpression();
if (rhs instanceof PsiMethodCallExpression) {
PsiMethodCallExpression callExpression = (PsiMethodCallExpression)rhs;
PsiElement r = callExpression.getMethodExpression().resolve();
if (r instanceof PsiMethod) {
PsiAnnotation annotation = getAnnotation((PsiMethod)r);
if (annotation != null) {
return annotation;
}
}
} else if (rhs instanceof PsiReferenceExpression) {
PsiReferenceExpression ref = (PsiReferenceExpression) rhs;
PsiElement r = ref.resolve();
if (r instanceof PsiField) {
PsiAnnotation annotation = getAnnotationForField((PsiField)r);
if (annotation != null) {
return annotation;
}
} else if (r instanceof PsiVariable) {
PsiAnnotation annotation = getAnnotationForVariable((PsiVariable)r, depth + 1);
if (annotation != null) {
return annotation;
}
}
}
}
}
}
return null;
}
@Nullable
public static PsiAnnotation getAnnotationForField(@NotNull PsiElement context, @NotNull String className, @NotNull String fieldName) {
ApplicationManager.getApplication().assertReadAccessAllowed();
PsiClass psiClass = JavaPsiFacade.getInstance(context.getProject()).findClass(className, getSearchScope(context));
if (psiClass == null) {
return null;
}
PsiField field = psiClass.findFieldByName(fieldName, true);
if (field != null) {
return getAnnotationForField(field);
}
return null;
}
@Nullable
private static PsiAnnotation getAnnotationForField(PsiField field) {
PsiAnnotation annotation = getAnnotation(field);
if (annotation == null) {
// Fields are usually not annotated (because they are private, not part of the API).
// However, if a field has a getter, there is a good chance that the getter has been
// annotated!
PsiMethod getter = PropertyUtil.findGetterForField(field);
if (getter != null) {
return getAnnotation(getter);
}
}
return annotation;
}
@NotNull
private static GlobalSearchScope getSearchScope(@NotNull PsiElement context) {
Module module = ModuleUtilCore.findModuleForPsiElement(context);
if (module != null) {
return module.getModuleWithDependenciesAndLibrariesScope(false);
}
else {
return GlobalSearchScope.projectScope(context.getProject());
}
}
@Nullable
private static PsiAnnotation getAnnotation(@Nullable PsiModifierListOwner owner) {
if (owner == null) {
return null;
}
Project project = owner.getProject();
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
GlobalSearchScope searchScope = getSearchScope(owner);
for (PsiAnnotation a : ResourceTypeInspection.getAllAnnotations(owner)) {
String qualifiedName = a.getQualifiedName();
if (qualifiedName == null || qualifiedName.startsWith("java")) {
continue;
}
if (qualifiedName.endsWith(SupportAnnotationDetector.RES_SUFFIX)
|| qualifiedName.equals(SupportAnnotationDetector.COLOR_INT_ANNOTATION)
|| qualifiedName.equals(SdkConstants.INT_DEF_ANNOTATION)) {
return a;
}
// typedef annotations involve a level of indirection
// i.e. we need to from "@Visibility int visibility" to "@IntDef public @interface Visibility"
PsiClass annotationClass = psiFacade.findClass(qualifiedName, searchScope);
PsiAnnotation annotation = getAnnotation(annotationClass);
if (annotation != null) {
return annotation;
}
}
return null;
}
public static class IntDefResolution {
@Nullable public final Map<Integer,String> valuesMap;
public final boolean canBeOred;
public IntDefResolution(boolean canBeOred, @Nullable Map<Integer,String> valuesMap) {
this.canBeOred = canBeOred;
this.valuesMap = valuesMap;
}
public static IntDefResolution createError() {
return new IntDefResolution(false, null);
}
}
public static IntDefResolution resolveIntDef(@NotNull PsiAnnotation annotation) {
// TODO: cache int def resolutions
ApplicationManager.getApplication().assertReadAccessAllowed();
PsiAnnotationMemberValue intValues = annotation.findAttributeValue(TYPE_DEF_VALUE_ATTRIBUTE);
PsiAnnotationMemberValue[] allowedValues = intValues instanceof PsiArrayInitializerMemberValue
? ((PsiArrayInitializerMemberValue)intValues).getInitializers()
: PsiAnnotationMemberValue.EMPTY_ARRAY;
Map<Integer,String> valuesMap = Maps.newHashMap();
for (PsiAnnotationMemberValue value : allowedValues) {
if (!(value instanceof PsiReference)) {
return IntDefResolution.createError();
}
PsiElement resolved = ((PsiReference)value).resolve();
if (!(resolved instanceof PsiNamedElement)) {
return IntDefResolution.createError();
}
// For each name, we need to identify the integer value corresponding to it. We first attempt to check if we can quickly
// extract the value set by ConstantExpressionVisitor.VALUE.
Key<?> key = Key.findKeyByName("VALUE");
Integer constantValue = null;
if (key != null) {
Object v = value.getUserData(key);
if (v instanceof Integer) {
constantValue = (Integer)v;
}
}
// If that didn't work, we invoke it directly
if (constantValue == null && (resolved instanceof PsiField)) {
Object v = JavaConstantExpressionEvaluator.computeConstantExpression(((PsiField)resolved).getInitializer(), null, false);
if (v instanceof Integer) {
constantValue = (Integer)v;
}
}
if (constantValue == null) {
return IntDefResolution.createError();
}
valuesMap.put(constantValue, ((PsiNamedElement)resolved).getName());
}
PsiAnnotationMemberValue orValue = annotation.findAttributeValue(TYPE_DEF_FLAG_ATTRIBUTE);
boolean canBeOred = orValue instanceof PsiLiteral && Boolean.TRUE.equals(((PsiLiteral)orValue).getValue());
return new IntDefResolution(canBeOred, valuesMap);
}
}