/* * Copyright 2000-2014 JetBrains s.r.o. * * 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 org.jetbrains.android.inspections; import com.android.resources.ResourceType; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.intellij.analysis.AnalysisScope; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInspection.BaseJavaLocalInspectionTool; import com.intellij.codeInspection.LocalInspectionToolSession; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.*; import com.intellij.slicer.DuplicateMap; import com.intellij.slicer.SliceAnalysisParams; import com.intellij.slicer.SliceRootNode; import com.intellij.slicer.SliceUsage; import com.intellij.util.Function; import com.intellij.util.Processor; import com.intellij.util.containers.ConcurrentSoftValueHashMap; import gnu.trove.THashSet; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import static com.android.SdkConstants.*; /** * A custom version of the IntelliJ * {@link com.intellij.codeInspection.magicConstant.MagicConstantInspection}, * with two changes: * <ol> * <li> * Checks for proper resource types, e.g. ensuring that you call * getResources().getDimension(id) with an ID that is a constant from * R.dimen, not for example R.string. * </li> * <li> * Checks for typedef annotations. These are precisely like the IntelliJ * MagicConstant annotations, but with different annotation names and * fields (for example for the integer case there is an explicit flags= boolean * annotation attribute. * </li> * </ol> * <p> * We didn't necessarily need to customize the inspection itself to handle the * second case; instead, we could have translated the annotations.zip file at * Gradle sync time to generate a derived annotations file with the annotation * names and parameters rewritten as IntelliJ annotations. However, doing * a resource type check is similar enough the the inspector needs to do nearly * everything else anyway (check the same kinds of calls, load the external annotations * for each resolved element, and so on) so with all that code in place we might * as well also support the android support annotations natively. This also has * the advantage that it will work with non-Gradle (IntelliJ and Maven) projects. * <p> * Since a lot of the code is identical to the IntelliJ magic constant inspection, * I have left the code identical to that inspection as much as possible, in order * to facilitate diffing the two classes and migrating future changes to the base * inspection over to this one. * <p> * To diff this class with the inspection it was based on, check out tag * idea/135.445 in the IntelliJ community edition git repository. * <p> * This means the code isn't as clear as possible; I just added a {@link ResourceType} * field to the AllowedValues inner class to pass around the required ResourceType, * which if non-null is the set of resource types we're enforcing rather than the * other values held in the object. * <p> * The main methods that were modified are: * <ul> * <li> * {@link #getAllowedValues(com.intellij.psi.PsiModifierListOwner, com.intellij.psi.PsiType, java.util.Set)}: * Changed to look for IntDef/StringDef instead of MagicConstant, as well as look for the Resource type annotations * ({@code }@StringRes}, {@code @IdRes}, etc) and for these we have to loop since you can specify more than one. * </li> * <li> * {@code getAllowedValuesFromMagic()}: Changed to extract attributes from * the support library's IntDef and TypeDef annotations and split into * {@link #getAllowedValuesFromTypedef} and {@link #getResourceTypeFromAnnotation(String)} * </li> * <li> * {@link AllowedValues}: Added ResourceType field which if non-null means * we should look for a ResourceType instead of a set of integer/string constants * </li> * <li> * {@link #isAllowed}: Added check for allowedValues.types and if non-null, * check that if the call can be resolved to R.type.name, that the type is one * of the expected types (this is done by a method similar to getGoodExpression * but which analyzes resource types instead) * </li> * <li> * Removed {@code checkAnnotationsJarAttached()}, since we will always provide SDK annotations * for use by the type def collector in the SDK. (Also removed the code to call it and * to clean up the associated key.) * </li> * <li> * Sprinkled {@code @Nullable} annotations on various parameters and return values to * remove warnings * </li> * <li> * Removed {@code readFromClass} and code associated with picking all fields from * a class * </li> * <li> * Plugin registration: Increased severity to error, and changed category to Android. * </li> * <li> * Stripped out {@code parseBeanInfo} * </li> * </ul> * <p> */ public class ResourceTypeInspection extends BaseJavaLocalInspectionTool { private static final String RESOURCE_TYPE_ANNOTATIONS_SUFFIX = "Res"; @NotNull @Override public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) { AndroidFacet facet = AndroidFacet.getInstance(holder.getFile()); if (facet == null) { // No-op outside of Android modules return new PsiElementVisitor() {}; } return new JavaElementVisitor() { @Override public void visitCallExpression(PsiCallExpression callExpression) { checkCall(callExpression, holder); } @Override public void visitAssignmentExpression(PsiAssignmentExpression expression) { PsiExpression r = expression.getRExpression(); if (r == null) return; PsiExpression l = expression.getLExpression(); if (!(l instanceof PsiReferenceExpression)) return; PsiElement resolved = ((PsiReferenceExpression)l).resolve(); if (!(resolved instanceof PsiModifierListOwner)) return; PsiModifierListOwner owner = (PsiModifierListOwner)resolved; PsiType type = expression.getType(); checkExpression(r, owner, type, holder); } @Override public void visitReturnStatement(PsiReturnStatement statement) { PsiExpression value = statement.getReturnValue(); if (value == null) return; PsiMethod method = PsiTreeUtil.getParentOfType(statement, PsiMethod.class); if (method == null) return; checkExpression(value, method, value.getType(), holder); } @Override public void visitNameValuePair(PsiNameValuePair pair) { PsiAnnotationMemberValue value = pair.getValue(); if (!(value instanceof PsiExpression)) return; PsiReference ref = pair.getReference(); if (ref == null) return; PsiMethod method = (PsiMethod)ref.resolve(); if (method == null) return; checkExpression((PsiExpression)value, method, method.getReturnType(), holder); } @Override public void visitBinaryExpression(PsiBinaryExpression expression) { IElementType tokenType = expression.getOperationTokenType(); if (tokenType != JavaTokenType.EQEQ && tokenType != JavaTokenType.NE) return; PsiExpression l = expression.getLOperand(); PsiExpression r = expression.getROperand(); if (r == null) return; checkBinary(l, r); checkBinary(r, l); } private void checkBinary(PsiExpression l, PsiExpression r) { if (l instanceof PsiReference) { PsiElement resolved = ((PsiReference)l).resolve(); if (resolved instanceof PsiModifierListOwner) { checkExpression(r, (PsiModifierListOwner)resolved, getType((PsiModifierListOwner)resolved), holder); } } else if (l instanceof PsiMethodCallExpression) { PsiMethod method = ((PsiMethodCallExpression)l).resolveMethod(); if (method != null) { checkExpression(r, method, method.getReturnType(), holder); } } } }; } private static void checkExpression(PsiExpression expression, PsiModifierListOwner owner, @Nullable PsiType type, ProblemsHolder holder) { AllowedValues allowed = getAllowedValues(owner, type, null); if (allowed == null) return; //noinspection ConstantConditions PsiElement scope = PsiUtil.getTopLevelEnclosingCodeBlock(expression, null); if (scope == null) scope = expression; if (!isAllowed(scope, expression, allowed, expression.getManager(), null)) { registerProblem(expression, allowed, holder); } } private static void checkCall(@NotNull PsiCallExpression methodCall, @NotNull ProblemsHolder holder) { PsiMethod method = methodCall.resolveMethod(); if (method == null) return; PsiParameter[] parameters = method.getParameterList().getParameters(); PsiExpressionList argumentList = methodCall.getArgumentList(); if (argumentList == null) return; PsiExpression[] arguments = argumentList.getExpressions(); for (int i = 0; i < parameters.length; i++) { PsiParameter parameter = parameters[i]; AllowedValues values = getAllowedValues(parameter, parameter.getType(), null); if (values == null) continue; if (i >= arguments.length) break; PsiExpression argument = arguments[i]; argument = PsiUtil.deparenthesizeExpression(argument); if (argument == null) continue; checkMagicParameterArgument(parameter, argument, values, holder); } } static class AllowedValues { final PsiAnnotationMemberValue[] values; final boolean canBeOred; /** Type of Android resource that we must be passing. If non null, this is the only value to be * checked, not the member values. This is done to minimize the set of changes to this inspection * from the {@link com.intellij.codeInspection.magicConstant.MagicConstantInspection} it was based on. */ final List<ResourceType> types; private AllowedValues(@NotNull PsiAnnotationMemberValue[] values, boolean canBeOred, @Nullable List<ResourceType> types) { this.values = values; this.canBeOred = canBeOred; this.types = types; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AllowedValues a2 = (AllowedValues)o; if (canBeOred != a2.canBeOred) { return false; } Set<PsiAnnotationMemberValue> v1 = new THashSet<PsiAnnotationMemberValue>(Arrays.asList(values)); Set<PsiAnnotationMemberValue> v2 = new THashSet<PsiAnnotationMemberValue>(Arrays.asList(a2.values)); if (v1.size() != v2.size()) { return false; } for (PsiAnnotationMemberValue value : v1) { for (PsiAnnotationMemberValue value2 : v2) { if (same(value, value2, value.getManager())) { v2.remove(value2); break; } } } return v2.isEmpty(); } @Override public int hashCode() { int result = Arrays.hashCode(values); result = 31 * result + (canBeOred ? 1 : 0); return result; } public boolean isSubsetOf(@NotNull AllowedValues other, @NotNull PsiManager manager) { for (PsiAnnotationMemberValue value : values) { boolean found = false; for (PsiAnnotationMemberValue otherValue : other.values) { if (same(value, otherValue, manager)) { found = true; break; } } if (!found) return false; } return true; } } @Nullable private static AllowedValues getAllowedValuesFromTypedef(@NotNull PsiType type, @NotNull PsiAnnotation magic, @NotNull PsiManager manager) { PsiAnnotationMemberValue[] allowedValues; final boolean canBeOred; boolean isInt = TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.LONG_RANK; boolean isString = !isInt && type.equals(PsiType.getJavaLangString(manager, GlobalSearchScope.allScope(manager.getProject()))); if (isInt || isString) { PsiAnnotationMemberValue intValues = magic.findAttributeValue(TYPE_DEF_VALUE_ATTRIBUTE); allowedValues = intValues instanceof PsiArrayInitializerMemberValue ? ((PsiArrayInitializerMemberValue)intValues).getInitializers() : PsiAnnotationMemberValue.EMPTY_ARRAY; if (isInt) { PsiAnnotationMemberValue orValue = magic.findAttributeValue(TYPE_DEF_FLAG_ATTRIBUTE); canBeOred = orValue instanceof PsiLiteral && Boolean.TRUE.equals(((PsiLiteral)orValue).getValue()); } else { canBeOred = false; } } else { return null; //other types not supported } if (allowedValues.length != 0) { return new AllowedValues(allowedValues, canBeOred, null); } return null; } @Nullable private static ResourceType getResourceTypeFromAnnotation(@NotNull String qualifiedName) { String resourceTypeName = Character.toLowerCase(qualifiedName.charAt(SUPPORT_ANNOTATIONS_PREFIX.length())) + qualifiedName.substring(SUPPORT_ANNOTATIONS_PREFIX.length() + 1, qualifiedName.length() - RESOURCE_TYPE_ANNOTATIONS_SUFFIX.length()); return ResourceType.getEnum(resourceTypeName); } @Nullable static AllowedValues getAllowedValues(@NotNull PsiModifierListOwner element, @Nullable PsiType type, @Nullable Set<PsiClass> visited) { //noinspection ConstantConditions PsiAnnotation[] annotations = AnnotationUtil.getAllAnnotations(element, true, null); PsiManager manager = element.getManager(); List<ResourceType> resourceTypes = null; for (PsiAnnotation annotation : annotations) { AllowedValues values; if (type != null) { String qualifiedName = annotation.getQualifiedName(); if (qualifiedName == null) { continue; } if (INT_DEF_ANNOTATION.equals(qualifiedName) || STRING_DEF_ANNOTATION.equals(qualifiedName)) { values = getAllowedValuesFromTypedef(type, annotation, manager); if (values != null) return values; } else if (qualifiedName.startsWith(SUPPORT_ANNOTATIONS_PREFIX) && qualifiedName.endsWith(RESOURCE_TYPE_ANNOTATIONS_SUFFIX)) { ResourceType resourceType = getResourceTypeFromAnnotation(qualifiedName); if (resourceType != null) { if (resourceTypes == null) { resourceTypes = Lists.newArrayList(); } resourceTypes.add(resourceType); } } } PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); PsiElement resolved = ref == null ? null : ref.resolve(); if (!(resolved instanceof PsiClass) || !((PsiClass)resolved).isAnnotationType()) continue; PsiClass aClass = (PsiClass)resolved; if (visited == null) visited = new THashSet<PsiClass>(); if (!visited.add(aClass)) continue; values = getAllowedValues(aClass, type, visited); if (values != null) return values; } if (resourceTypes != null) { return new AllowedValues(PsiAnnotationMemberValue.EMPTY_ARRAY, false, resourceTypes); } return null; } @Nullable private static PsiType getType(@NotNull PsiModifierListOwner element) { return element instanceof PsiVariable ? ((PsiVariable)element).getType() : element instanceof PsiMethod ? ((PsiMethod)element).getReturnType() : null; } private static void checkMagicParameterArgument(@NotNull PsiParameter parameter, @NotNull PsiExpression argument, @NotNull AllowedValues allowedValues, @NotNull ProblemsHolder holder) { final PsiManager manager = PsiManager.getInstance(holder.getProject()); if (!argument.getTextRange().isEmpty() && !isAllowed(parameter.getDeclarationScope(), argument, allowedValues, manager, null)) { registerProblem(argument, allowedValues, holder); } } private static void registerProblem(@NotNull PsiExpression argument, @NotNull AllowedValues allowedValues, @NotNull ProblemsHolder holder) { if (allowedValues.types != null) { if (allowedValues.types.size() == 1) { holder.registerProblem(argument, "Expected resource of type " + allowedValues.types.get(0)); } else { holder.registerProblem(argument, "Expected resource type to be one of " + Joiner.on(", ").join(allowedValues.types)); } return; } String values = StringUtil.join(allowedValues.values, new Function<PsiAnnotationMemberValue, String>() { @Override public String fun(PsiAnnotationMemberValue value) { if (value instanceof PsiReferenceExpression) { PsiElement resolved = ((PsiReferenceExpression)value).resolve(); if (resolved instanceof PsiVariable) { return PsiFormatUtil.formatVariable((PsiVariable)resolved, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_CONTAINING_CLASS, PsiSubstitutor.EMPTY); } } return value.getText(); } }, ", "); holder.registerProblem(argument, "Must be one of: "+ values); } private static boolean isAllowed(@NotNull final PsiElement scope, @NotNull final PsiExpression argument, @NotNull final AllowedValues allowedValues, @NotNull final PsiManager manager, @Nullable final Set<PsiExpression> visited) { // Resource type check if (allowedValues.types != null) { return isResourceTypeAllowed(scope, argument, allowedValues, manager, visited); } if (isGoodExpression(argument, allowedValues, scope, manager, visited)) return true; return processValuesFlownTo(argument, scope, manager, new Processor<PsiExpression>() { @Override public boolean process(PsiExpression expression) { return isGoodExpression(expression, allowedValues, scope, manager, visited); } }); } private static boolean isGoodExpression(@NotNull PsiExpression e, @NotNull AllowedValues allowedValues, @NotNull PsiElement scope, @NotNull PsiManager manager, @Nullable Set<PsiExpression> visited) { PsiExpression expression = PsiUtil.deparenthesizeExpression(e); if (expression == null) return true; if (visited == null) visited = new THashSet<PsiExpression>(); if (!visited.add(expression)) return true; if (expression instanceof PsiConditionalExpression) { PsiExpression thenExpression = ((PsiConditionalExpression)expression).getThenExpression(); boolean thenAllowed = thenExpression == null || isAllowed(scope, thenExpression, allowedValues, manager, visited); if (!thenAllowed) return false; PsiExpression elseExpression = ((PsiConditionalExpression)expression).getElseExpression(); return elseExpression == null || isAllowed(scope, elseExpression, allowedValues, manager, visited); } // Resource type check assert allowedValues.types == null; // Handled separately if (isOneOf(expression, allowedValues, manager)) return true; if (allowedValues.canBeOred) { PsiExpression zero = getLiteralExpression(expression, manager, "0"); if (same(expression, zero, manager)) return true; PsiExpression mOne = getLiteralExpression(expression, manager, "-1"); if (same(expression, mOne, manager)) return true; if (expression instanceof PsiPolyadicExpression) { IElementType tokenType = ((PsiPolyadicExpression)expression).getOperationTokenType(); if (JavaTokenType.OR.equals(tokenType) || JavaTokenType.AND.equals(tokenType) || JavaTokenType.PLUS.equals(tokenType)) { for (PsiExpression operand : ((PsiPolyadicExpression)expression).getOperands()) { if (!isAllowed(scope, operand, allowedValues, manager, visited)) return false; } return true; } } if (expression instanceof PsiPrefixExpression && JavaTokenType.TILDE.equals(((PsiPrefixExpression)expression).getOperationTokenType())) { PsiExpression operand = ((PsiPrefixExpression)expression).getOperand(); return operand == null || isAllowed(scope, operand, allowedValues, manager, visited); } } PsiElement resolved = null; if (expression instanceof PsiReference) { resolved = ((PsiReference)expression).resolve(); } else if (expression instanceof PsiCallExpression) { resolved = ((PsiCallExpression)expression).resolveMethod(); } AllowedValues allowedForRef; if (resolved instanceof PsiModifierListOwner && (allowedForRef = getAllowedValues((PsiModifierListOwner)resolved, getType((PsiModifierListOwner)resolved), null)) != null && allowedForRef.isSubsetOf(allowedValues, manager)) return true; //noinspection ConstantConditions return PsiType.NULL.equals(expression.getType()); } /** Return value from {@link #isValidResourceTypeExpression} : the expression is valid */ private static final int VALID = 1001; /** Return value from {@link #isValidResourceTypeExpression} : the expression is not valid */ private static final int INVALID = VALID + 1; /** Return value from {@link #isValidResourceTypeExpression} : uncertain whether the resource type is valid */ private static final int UNCERTAIN = INVALID + 1; private static boolean isResourceTypeAllowed(@NotNull final PsiElement scope, @NotNull final PsiExpression argument, @NotNull final AllowedValues allowedValues, @NotNull final PsiManager manager, @Nullable final Set<PsiExpression> visited) { int result = isValidResourceTypeExpression(argument, allowedValues, scope, manager, visited); if (result == VALID) { return true; } else if (result == INVALID) { return false; } assert result == UNCERTAIN; final AtomicInteger b = new AtomicInteger(); processValuesFlownTo(argument, scope, manager, new Processor<PsiExpression>() { @Override public boolean process(PsiExpression expression) { int goodExpression = isValidResourceTypeExpression(expression, allowedValues, scope, manager, visited); b.set(goodExpression); return goodExpression == UNCERTAIN; } }); result = b.get(); // Treat uncertain as allowed: this means that we were passed some integer whose origins // we don't recognize; don't flag those. return result != INVALID; } private static int isValidResourceTypeExpression(@NotNull PsiExpression e, @NotNull AllowedValues allowedValues, @NotNull PsiElement scope, @NotNull PsiManager manager, @Nullable Set<PsiExpression> visited) { PsiExpression expression = PsiUtil.deparenthesizeExpression(e); if (expression == null) return VALID; if (visited == null) visited = new THashSet<PsiExpression>(); if (!visited.add(expression)) return VALID; if (expression instanceof PsiConditionalExpression) { PsiExpression thenExpression = ((PsiConditionalExpression)expression).getThenExpression(); boolean thenAllowed = thenExpression == null || isAllowed(scope, thenExpression, allowedValues, manager, visited); if (!thenAllowed) return INVALID; PsiExpression elseExpression = ((PsiConditionalExpression)expression).getElseExpression(); return elseExpression == null || isAllowed(scope, elseExpression, allowedValues, manager, visited) ? VALID : UNCERTAIN; } // Resource type check assert allowedValues.types != null; if (expression instanceof PsiReferenceExpression) { PsiReferenceExpression refExpression = (PsiReferenceExpression)expression; PsiExpression qualifierExpression = refExpression.getQualifierExpression(); if (qualifierExpression instanceof PsiReferenceExpression) { PsiReferenceExpression typeDef = (PsiReferenceExpression)qualifierExpression; PsiExpression r = typeDef.getQualifierExpression(); if (r instanceof PsiReferenceExpression) { if (R_CLASS.equals(((PsiReferenceExpression)r).getReferenceName())) { String typeName = typeDef.getReferenceName(); return isTypeAllowed(allowedValues, typeName); } } } } else if (expression instanceof PsiLiteral) { if (expression instanceof PsiLiteralExpression) { PsiElement parent = expression.getParent(); if (parent instanceof PsiField) { parent = parent.getParent(); if (parent instanceof PsiClass) { PsiElement outerMost = parent.getParent(); if (outerMost instanceof PsiClass && R_CLASS.equals(((PsiClass)outerMost).getName())) { PsiClass typeClass = (PsiClass)parent; String typeClassName = typeClass.getName(); return isTypeAllowed(allowedValues, typeClassName); } } } } // Allow a literal '0' or '-1' as the resource type; this is sometimes used to communicate that // no id was specified (the support library does this in a few places for example) Object value = ((PsiLiteral)expression).getValue(); if (value instanceof Integer) { return ((Integer)value).intValue() == 0 ? VALID : INVALID; } } else if (expression instanceof PsiPrefixExpression) { // Allow a literal '-1' as the resource type; this is sometimes used to communicate that // no id was specified PsiPrefixExpression ppe = (PsiPrefixExpression)expression; if (ppe.getOperationTokenType() == JavaTokenType.MINUS && ppe.getOperand() instanceof PsiLiteral) { Object value = ((PsiLiteral)ppe.getOperand()).getValue(); if (value instanceof Integer) { return ((Integer)value).intValue() == 1 ? VALID : INVALID; } } } PsiElement resolved = null; if (expression instanceof PsiReference) { resolved = ((PsiReference)expression).resolve(); if (resolved instanceof PsiField) { PsiField field = (PsiField)resolved; PsiClass containingClass = field.getContainingClass(); if (containingClass != null) { PsiClass r = containingClass.getContainingClass(); if (r != null && R_CLASS.equals(r.getName())) { ResourceType type = ResourceType.getEnum(containingClass.getName()); if (type != null) { for (ResourceType t : allowedValues.types) { if (t == type) { return VALID; } } return INVALID; } } } } } else if (expression instanceof PsiCallExpression) { resolved = ((PsiCallExpression)expression).resolveMethod(); } AllowedValues allowedForRef; if (resolved instanceof PsiModifierListOwner) { PsiType type = getType((PsiModifierListOwner)resolved); allowedForRef = getAllowedValues((PsiModifierListOwner)resolved, type, null); if (allowedForRef != null && allowedForRef.types != null) { // Happy if *any* of the resource types on the annotation matches any of the // annotations allowed for this API for (ResourceType t1 : allowedForRef.types) { for (ResourceType t2 : allowedValues.types) { if (t1 == t2) { return VALID; } } } return INVALID; } } return UNCERTAIN; } private static int isTypeAllowed(@NotNull AllowedValues allowedValues, @NotNull String typeName) { if (allowedValues.types != null) { for (ResourceType type : allowedValues.types) { if (type.getName().equals(typeName)) { return VALID; } if (type == ResourceType.DRAWABLE && ResourceType.COLOR.getName().equals(typeName)) { // Can also supply colors for drawables return VALID; } } } return INVALID; } // Would be nice to reuse the MagicConstantInspection's cache for this, but it's not accessible private static final Key<Map<String, PsiExpression>> LITERAL_EXPRESSION_CACHE = Key.create("TYPE_DEF_LITERAL_EXPRESSION"); private static PsiExpression getLiteralExpression(@NotNull PsiExpression context, @NotNull PsiManager manager, @NotNull String text) { Map<String, PsiExpression> cache = LITERAL_EXPRESSION_CACHE.get(manager); if (cache == null) { cache = new ConcurrentSoftValueHashMap<String, PsiExpression>(); cache = manager.putUserDataIfAbsent(LITERAL_EXPRESSION_CACHE, cache); } PsiExpression expression = cache.get(text); if (expression == null) { expression = JavaPsiFacade.getElementFactory(manager.getProject()).createExpressionFromText(text, context); cache.put(text, expression); } return expression; } private static boolean isOneOf(@NotNull PsiExpression expression, @NotNull AllowedValues allowedValues, @NotNull PsiManager manager) { for (PsiAnnotationMemberValue allowedValue : allowedValues.values) { if (same(allowedValue, expression, manager)) return true; } return false; } private static boolean same(@Nullable PsiElement e1, @Nullable PsiElement e2, @NotNull PsiManager manager) { if (e1 instanceof PsiLiteralExpression && e2 instanceof PsiLiteralExpression) { return Comparing.equal(((PsiLiteralExpression)e1).getValue(), ((PsiLiteralExpression)e2).getValue()); } if (e1 instanceof PsiPrefixExpression && e2 instanceof PsiPrefixExpression && ((PsiPrefixExpression)e1).getOperationTokenType() == ((PsiPrefixExpression)e2).getOperationTokenType()) { return same(((PsiPrefixExpression)e1).getOperand(), ((PsiPrefixExpression)e2).getOperand(), manager); } if (e1 instanceof PsiReference && e2 instanceof PsiReference) { e1 = ((PsiReference)e1).resolve(); e2 = ((PsiReference)e2).resolve(); } return manager.areElementsEquivalent(e2, e1); } private static boolean processValuesFlownTo(@NotNull final PsiExpression argument, @NotNull PsiElement scope, @NotNull PsiManager manager, @NotNull final Processor<PsiExpression> processor) { SliceAnalysisParams params = new SliceAnalysisParams(); params.dataFlowToThis = true; params.scope = new AnalysisScope(new LocalSearchScope(scope), manager.getProject()); SliceRootNode rootNode = new SliceRootNode(manager.getProject(), new DuplicateMap(), SliceUsage.createRootUsage(argument, params)); @SuppressWarnings("unchecked") Collection<? extends AbstractTreeNode> children = rootNode.getChildren().iterator().next().getChildren(); for (AbstractTreeNode child : children) { SliceUsage usage = (SliceUsage)child.getValue(); PsiElement element = usage.getElement(); if (element instanceof PsiExpression && !processor.process((PsiExpression)element)) return false; } return !children.isEmpty(); } }