diff --git a/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java index 486ad032946b..110cbe4f15ba 100644 --- a/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/ExternalAnnotationsManagerImpl.java @@ -145,7 +145,7 @@ public class ExternalAnnotationsManagerImpl extends ExternalAnnotationsManager { if (document == null) continue; final XmlTag rootTag = document.getRootTag(); if (rootTag == null) continue; - final String externalName = PsiFormatUtil.getExternalName(listOwner, false); + final String externalName = getExternalName(listOwner, false); final String oldExternalName = getNormalizedExternalName(listOwner); for (final XmlTag tag : rootTag.getSubTags()) { final String className = tag.getAttributeValue("name"); @@ -179,6 +179,10 @@ public class ExternalAnnotationsManagerImpl extends ExternalAnnotationsManager { return result; } + private static String getExternalName(PsiModifierListOwner listOwner, boolean showParamName) { + return PsiFormatUtil.getExternalName(listOwner, showParamName, Integer.MAX_VALUE); + } + public void annotateExternally(@NotNull final PsiModifierListOwner listOwner, @NotNull final String annotationFQName, @@ -317,7 +321,7 @@ public class ExternalAnnotationsManagerImpl extends ExternalAnnotationsManager { if (document != null) { final XmlTag rootTag = document.getRootTag(); if (rootTag != null) { - final String externalName = PsiFormatUtil.getExternalName(listOwner, false); + final String externalName = getExternalName(listOwner, false); final String oldExternalName = getNormalizedExternalName(listOwner); for (final XmlTag tag : rootTag.getSubTags()) { final String className = tag.getAttributeValue("name"); @@ -445,7 +449,7 @@ public class ExternalAnnotationsManagerImpl extends ExternalAnnotationsManager { final XmlDocument document = xmlFile.getDocument(); if (document != null) { final XmlTag rootTag = document.getRootTag(); - final String externalName = PsiFormatUtil.getExternalName(listOwner, false); + final String externalName = getExternalName(listOwner, false); if (rootTag != null) { for (XmlTag tag : rootTag.getSubTags()) { if (Comparing.strEqual(tag.getAttributeValue("name"), externalName)) { @@ -603,7 +607,7 @@ public class ExternalAnnotationsManagerImpl extends ExternalAnnotationsManager { @Nullable private static String getNormalizedExternalName(PsiModifierListOwner owner) { - String externalName = PsiFormatUtil.getExternalName(owner); + String externalName = getExternalName(owner, true); if (externalName != null) { if (owner instanceof PsiParameter && owner.getParent() instanceof PsiParameterList) { final PsiMethod method = PsiTreeUtil.getParentOfType(owner, PsiMethod.class); diff --git a/java/java-impl/src/com/intellij/codeInspection/magicConstant/MagicCompletionContributor.java b/java/java-impl/src/com/intellij/codeInspection/magicConstant/MagicCompletionContributor.java new file mode 100644 index 000000000000..7325103e04ce --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/magicConstant/MagicCompletionContributor.java @@ -0,0 +1,181 @@ +/* + * Copyright 2000-2012 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 com.intellij.codeInspection.magicConstant; + +import com.intellij.codeInsight.completion.*; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.codeInsight.lookup.LookupItemUtil; +import com.intellij.codeInsight.lookup.VariableLookupItem; +import com.intellij.patterns.ElementPattern; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Consumer; +import gnu.trove.THashSet; +import gnu.trove.TObjectHashingStrategy; + +import java.util.Set; + +import static com.intellij.patterns.PlatformPatterns.psiElement; + +public class MagicCompletionContributor extends CompletionContributor { + private static final ElementPattern IN_METHOD_CALL_ARGUMENT = + psiElement().withParent(psiElement(PsiReferenceExpression.class).inside(psiElement(PsiExpressionList.class).withParent(PsiCall.class))); + private static final ElementPattern IN_BINARY_COMPARISON = + psiElement().withParent(psiElement(PsiReferenceExpression.class).inside(psiElement(PsiBinaryExpression.class))); + private static final ElementPattern IN_ASSIGNMENT = + psiElement().withParent(psiElement(PsiReferenceExpression.class).inside(psiElement(PsiAssignmentExpression.class))); + private static final ElementPattern IN_RETURN = + psiElement().withParent(psiElement(PsiReferenceExpression.class).inside(psiElement(PsiReturnStatement.class))); + private static final ElementPattern IN_ANNOTATION_INITIALIZER = + psiElement().afterLeaf("=").withParent(PsiReferenceExpression.class).withSuperParent(2,PsiNameValuePair.class).withSuperParent(3,PsiAnnotationParameterList.class).withSuperParent(4,PsiAnnotation.class); + private static final int PRIORITY = 100; + + @Override + public void fillCompletionVariants(final CompletionParameters parameters, final CompletionResultSet result) { + //if (parameters.getCompletionType() != CompletionType.SMART) return; + PsiElement pos = parameters.getPosition(); + MagicConstantInspection.AllowedValues allowedValues = null; + + if (IN_METHOD_CALL_ARGUMENT.accepts(pos)) { + PsiCall call = PsiTreeUtil.getParentOfType(pos, PsiCall.class); + if (!(call instanceof PsiExpression)) return; + PsiType type = ((PsiExpression)call).getType(); + + PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(call.getProject()).getResolveHelper(); + JavaResolveResult[] methods = call instanceof PsiMethodCallExpression + ? ((PsiMethodCallExpression)call).getMethodExpression().multiResolve(true) + : call instanceof PsiNewExpression && type instanceof PsiClassType + ? resolveHelper.multiResolveConstructor((PsiClassType)type, call.getArgumentList(), call) + : JavaResolveResult.EMPTY_ARRAY; + for (JavaResolveResult resolveResult : methods) { + PsiElement element = resolveResult.getElement(); + if (!(element instanceof PsiMethod)) return; + PsiMethod method = (PsiMethod)element; + if (!resolveHelper.isAccessible(method, call, null)) continue; + PsiElement argument = pos; + while (!(argument.getParent() instanceof PsiExpressionList)) argument = argument.getParent(); + PsiExpressionList list = (PsiExpressionList)argument.getParent(); + int i = ArrayUtil.indexOf(list.getExpressions(), argument); + if (i == -1) continue; + PsiParameter[] params = method.getParameterList().getParameters(); + if (i >= params.length) continue; + PsiParameter parameter = params[i]; + MagicConstantInspection.AllowedValues values = + parameter == null ? null : MagicConstantInspection.getAllowedValues(parameter, parameter.getType(), null); + if (values == null) continue; + if (allowedValues == null) { + allowedValues = values; + continue; + } + if (!allowedValues.equals(values)) return; + } + } + else if (IN_BINARY_COMPARISON.accepts(pos)) { + PsiBinaryExpression exp = PsiTreeUtil.getParentOfType(pos, PsiBinaryExpression.class); + if (exp != null && (exp.getOperationTokenType() == JavaTokenType.EQEQ || exp.getOperationTokenType() == JavaTokenType.NE)) { + PsiExpression l = exp.getLOperand(); + PsiElement resolved; + if (l instanceof PsiReferenceExpression && (resolved = ((PsiReferenceExpression)l).resolve()) instanceof PsiModifierListOwner) { + allowedValues = MagicConstantInspection.getAllowedValues((PsiModifierListOwner)resolved, l.getType(), null); + } + PsiExpression r = exp.getROperand(); + if (allowedValues == null && r instanceof PsiReferenceExpression && (resolved = ((PsiReferenceExpression)r).resolve()) instanceof PsiModifierListOwner) { + allowedValues = MagicConstantInspection.getAllowedValues((PsiModifierListOwner)resolved, r.getType(), null); + } + } + } + else if (IN_ASSIGNMENT.accepts(pos)) { + PsiAssignmentExpression assignment = PsiTreeUtil.getParentOfType(pos, PsiAssignmentExpression.class); + PsiElement resolved; + PsiExpression l = assignment == null ? null : assignment.getLExpression(); + if (assignment != null && PsiTreeUtil.isAncestor(assignment.getRExpression(), pos, false) && l instanceof PsiReferenceExpression && (resolved = ((PsiReferenceExpression)l).resolve()) instanceof PsiModifierListOwner) { + allowedValues = MagicConstantInspection.getAllowedValues((PsiModifierListOwner)resolved, l.getType(), null); + } + } + else if (IN_RETURN.accepts(pos)) { + PsiReturnStatement statement = PsiTreeUtil.getParentOfType(pos, PsiReturnStatement.class); + PsiExpression l = statement == null ? null : statement.getReturnValue(); + PsiMethod method = PsiTreeUtil.getParentOfType(l, PsiMethod.class); + if (method != null) { + allowedValues = MagicConstantInspection.getAllowedValues(method, method.getReturnType(), null); + } + } + else if (IN_ANNOTATION_INITIALIZER.accepts(pos)) { + PsiNameValuePair pair = (PsiNameValuePair)pos.getParent().getParent(); + 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; + allowedValues = MagicConstantInspection.getAllowedValues(method, method.getReturnType(), null); + } + if (allowedValues == null) return; + + final Set allowed = new THashSet(new TObjectHashingStrategy() { + @Override + public int computeHashCode(PsiElement object) { + return 0; + } + + @Override + public boolean equals(PsiElement o1, PsiElement o2) { + return parameters.getOriginalFile().getManager().areElementsEquivalent(o1, o2); + } + }); + if (allowedValues.canBeOred) { + PsiElementFactory factory = JavaPsiFacade.getElementFactory(pos.getProject()); + PsiExpression e0 = factory.createExpressionFromText("0", pos); + result.addElement(PrioritizedLookupElement.withPriority(LookupElementBuilder.create(e0,"0"), PRIORITY-1)); + PsiExpression e1 = factory.createExpressionFromText("-1", pos); + result.addElement(PrioritizedLookupElement.withPriority(LookupElementBuilder.create(e1,"-1"), PRIORITY-1)); + allowed.add(e0); + allowed.add(e1); + } + for (PsiAnnotationMemberValue value : allowedValues.values) { + if (value instanceof PsiReference) { + PsiElement resolved = ((PsiReference)value).resolve(); + if (resolved instanceof PsiNamedElement) { + + LookupElement lookupElement = LookupItemUtil.objectToLookupItem(resolved); + if (lookupElement instanceof VariableLookupItem) { + ((VariableLookupItem)lookupElement).setSubstitutor(PsiSubstitutor.EMPTY); + } + result.addElement(PrioritizedLookupElement.withPriority(lookupElement, PRIORITY)); + allowed.add(resolved); + continue; + } + } + LookupElementBuilder builder = LookupElementBuilder.create(value, value.getText()); + result.addElement(builder); + allowed.add(value); + } + result.runRemainingContributors(parameters, new Consumer() { + @Override + public void consume(CompletionResult completionResult) { + LookupElement element = completionResult.getLookupElement(); + if (allowed.contains(element.getObject())) { + return; + } + result.passResult(completionResult); + } + }); + } +} + + diff --git a/java/java-impl/src/com/intellij/codeInspection/magicConstant/MagicConstantInspection.java b/java/java-impl/src/com/intellij/codeInspection/magicConstant/MagicConstantInspection.java new file mode 100644 index 000000000000..971787ca7c58 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/magicConstant/MagicConstantInspection.java @@ -0,0 +1,544 @@ +/* + * Copyright 2000-2012 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 com.intellij.codeInspection.magicConstant; + +import com.intellij.analysis.AnalysisScope; +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.javadoc.PsiDocComment; +import com.intellij.psi.javadoc.PsiDocTag; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PropertyUtil; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.slicer.*; +import com.intellij.util.Function; +import com.intellij.util.Processor; +import gnu.trove.THashSet; +import org.intellij.lang.annotations.MagicConstant; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public class MagicConstantInspection extends LocalInspectionTool { + @Nls + @NotNull + @Override + public String getGroupDisplayName() { + return GroupNames.BUGS_GROUP_NAME; + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return "Magic Constant"; + } + + @NotNull + @Override + public String getShortName() { + return "MagicConstant"; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, + boolean isOnTheFly, + @NotNull LocalInspectionToolSession session) { + 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, + PsiType type, + ProblemsHolder holder) { + AllowedValues allowed = getAllowedValues(owner, type, null); + if (allowed == null) return; + PsiElement scope = PsiUtil.getTopLevelEnclosingCodeBlock(expression, null); + if (scope == null) scope = expression; + if (!isAllowed(scope, expression, allowed, expression.getManager())) { + 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(); + PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + AllowedValues values = getAllowedValues(parameter, parameter.getType(), null); + if (values == null) continue; + //PsiAnnotation annotation = AnnotationUtil.findAnnotation(parameter, Collections.singletonList(MagicConstant.class.getName())); + //if (annotation == 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; + + private AllowedValues(PsiAnnotationMemberValue[] values, boolean canBeOred) { + this.values = values; + this.canBeOred = canBeOred; + } + + @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 v1 = new THashSet(Arrays.asList(values)); + Set v2 = new THashSet(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 = values != null ? Arrays.hashCode(values) : 0; + result = 31 * result + (canBeOred ? 1 : 0); + return result; + } + } + + private static AllowedValues getAllowedValuesFromMagic(@NotNull PsiModifierListOwner element, @NotNull PsiType type) { + PsiAnnotation magic = AnnotationUtil.findAnnotationInHierarchy(element, Collections.singleton(MagicConstant.class.getName())); + if (magic == null) return null; + PsiAnnotationMemberValue[] allowedValues; + final boolean canBeOred; + if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.LONG_RANK) { + PsiAnnotationMemberValue intValues = magic.findAttributeValue("intValues"); + allowedValues = intValues instanceof PsiArrayInitializerMemberValue + ? ((PsiArrayInitializerMemberValue)intValues).getInitializers() : PsiAnnotationMemberValue.EMPTY_ARRAY; + if (allowedValues.length == 0) { + PsiAnnotationMemberValue orValue = magic.findAttributeValue("flags"); + allowedValues = orValue instanceof PsiArrayInitializerMemberValue ? ((PsiArrayInitializerMemberValue)orValue).getInitializers() : PsiAnnotationMemberValue.EMPTY_ARRAY; + canBeOred = true; + } + else { + canBeOred = false; + } + } + else if (type.equals(PsiType.getJavaLangString(element.getManager(), GlobalSearchScope.allScope(element.getProject())))) { + PsiAnnotationMemberValue strValuesAttr = magic.findAttributeValue("stringValues"); + allowedValues = strValuesAttr instanceof PsiArrayInitializerMemberValue ? ((PsiArrayInitializerMemberValue)strValuesAttr).getInitializers() : PsiAnnotationMemberValue.EMPTY_ARRAY; + canBeOred = false; + } + else { + return null; //other types not supported + } + + if (allowedValues.length != 0) { + return new AllowedValues(allowedValues, canBeOred); + } + + // last resort: try valuesFromClass + PsiAnnotationMemberValue[] values = readFromClass("valuesFromClass", magic, type); + boolean ored = false; + if (values == null) { + values = readFromClass("flagsFromClass", magic, type); + ored = true; + } + if (values == null) return null; + return new AllowedValues(values, ored); + } + + private static PsiAnnotationMemberValue[] readFromClass(@NonNls String attributeName, @NotNull PsiAnnotation magic, PsiType type) { + PsiAnnotationMemberValue fromClassAttr = magic.findAttributeValue(attributeName); + PsiType fromClassType = fromClassAttr instanceof PsiClassObjectAccessExpression ? ((PsiClassObjectAccessExpression)fromClassAttr).getOperand().getType() : null; + PsiClass fromClass = fromClassType instanceof PsiClassType ? ((PsiClassType)fromClassType).resolve() : null; + if (fromClass == null) return null; + String fqn = fromClass.getQualifiedName(); + if (fqn == null) return null; + List constants = new ArrayList(); + for (PsiField field : fromClass.getFields()) { + if (!field.hasModifierProperty(PsiModifier.PUBLIC) || !field.hasModifierProperty(PsiModifier.STATIC) || !field.hasModifierProperty(PsiModifier.FINAL)) continue; + PsiType fieldType = field.getType(); + if (!Comparing.equal(fieldType, type)) continue; + PsiAssignmentExpression e = (PsiAssignmentExpression)JavaPsiFacade.getElementFactory(field.getProject()).createExpressionFromText("x="+fqn + "." + field.getName(), field); + PsiReferenceExpression refToField = (PsiReferenceExpression)e.getRExpression(); + constants.add(refToField); + } + if (constants.isEmpty()) return null; + + return constants.toArray(new PsiAnnotationMemberValue[constants.size()]); + } + + static AllowedValues getAllowedValues(@NotNull PsiModifierListOwner element, PsiType type, Set visited) { + AllowedValues values; + if (type != null) { + values = getAllowedValuesFromMagic(element, type); + if (values != null) return values; + } + + PsiAnnotation[] annotations = AnnotationUtil.getAllAnnotations(element, true, null); + for (PsiAnnotation annotation : annotations) { + 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(); + if (!visited.add(aClass)) continue; + values = getAllowedValues(aClass, type, visited); + if (values != null) return values; + } + + + return parseBeanInfo(element); + } + + private static AllowedValues parseBeanInfo(@NotNull PsiModifierListOwner owner) { + PsiMethod method = null; + if (owner instanceof PsiParameter) { + PsiParameter parameter = (PsiParameter)owner; + PsiElement scope = parameter.getDeclarationScope(); + if (!(scope instanceof PsiMethod)) return null; + PsiElement nav = scope.getNavigationElement(); + if (!(nav instanceof PsiMethod)) return null; + method = (PsiMethod)nav; + if (method.isConstructor()) { + // not a property, try the @ConstructorProperties({"prop"}) + PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, "java.beans.ConstructorProperties"); + if (annotation == null) return null; + PsiAnnotationMemberValue value = annotation.findAttributeValue("value"); + if (!(value instanceof PsiArrayInitializerMemberValue)) return null; + PsiAnnotationMemberValue[] initializers = ((PsiArrayInitializerMemberValue)value).getInitializers(); + PsiElement parent = parameter.getParent(); + if (!(parent instanceof PsiParameterList)) return null; + int index = ((PsiParameterList)parent).getParameterIndex(parameter); + if (index >= initializers.length) return null; + PsiAnnotationMemberValue initializer = initializers[index]; + if (!(initializer instanceof PsiLiteralExpression)) return null; + Object val = ((PsiLiteralExpression)initializer).getValue(); + if (!(val instanceof String)) return null; + PsiMethod setter = PropertyUtil.findPropertySetter(method.getContainingClass(), (String)val, false, false); + if (setter == null) return null; + // try the @beaninfo of the corresponding setter + method = (PsiMethod)setter.getNavigationElement(); + } + } + else if (owner instanceof PsiMethod) { + PsiElement nav = owner.getNavigationElement(); + if (!(nav instanceof PsiMethod)) return null; + method = (PsiMethod)nav; + } + if (method == null) return null; + + PsiClass aClass = method.getContainingClass(); + if (aClass == null) return null; + if (PropertyUtil.isSimplePropertyGetter(method)) { + List setters = PropertyUtil.getSetters(aClass, PropertyUtil.getPropertyNameByGetter(method)); + if (setters.size() != 1) return null; + method = setters.get(0); + } + if (!PropertyUtil.isSimplePropertySetter(method)) return null; + PsiDocComment doc = method.getDocComment(); + if (doc == null) return null; + PsiDocTag beaninfo = doc.findTagByName("beaninfo"); + if (beaninfo == null) return null; + String data = StringUtil.join(beaninfo.getDataElements(), new Function() { + @Override + public String fun(PsiElement element) { + return element.getText(); + } + }, "\n"); + int enumIndex = StringUtil.indexOfSubstringEnd(data, "enum:"); + if (enumIndex == -1) return null; + data = data.substring(enumIndex); + int colon = data.indexOf(":"); + int last = colon == -1 ? data.length() : data.substring(0,colon).lastIndexOf("\n"); + data = data.substring(0, last); + + List values = new ArrayList(); + for (String line : StringUtil.splitByLines(data)) { + List words = StringUtil.split(line, " ", true, true); + if (words.size() != 2) continue; + String ref = words.get(1); + PsiExpression constRef = JavaPsiFacade.getElementFactory(aClass.getProject()).createExpressionFromText(ref, aClass); + if (!(constRef instanceof PsiReferenceExpression)) continue; + PsiReferenceExpression expr = (PsiReferenceExpression)constRef; + values.add(expr); + } + if (values.isEmpty()) return null; + PsiAnnotationMemberValue[] array = values.toArray(new PsiAnnotationMemberValue[values.size()]); + return new AllowedValues(array, false); + } + + private static PsiType getType(PsiModifierListOwner element) { + return element instanceof PsiVariable ? ((PsiVariable)element).getType() : element instanceof PsiMethod ? ((PsiMethod)element).getReturnType() : null; + } + + private static void checkMagicParameterArgument(@NotNull PsiParameter parameter, + 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)) { + registerProblem(argument, allowedValues, holder); + } + } + + private static void registerProblem(PsiExpression argument, AllowedValues allowedValues, ProblemsHolder holder) { + String values = StringUtil.join(allowedValues.values, + new Function() { + @Override + public String fun(PsiAnnotationMemberValue value) { + return value.getText(); + } + }, ", "); + holder.registerProblem(argument, "Must be one of the: "+ values); + } + + private static boolean isAllowed(@NotNull final PsiElement scope, + @NotNull final PsiExpression argument, + @NotNull final AllowedValues allowedValues, + @NotNull final PsiManager manager) { + if (isGoodExpression(argument, allowedValues, scope, manager)) return true; + + return processValuesFlownTo(argument, scope, new Processor() { + @Override + public boolean process(PsiExpression expression) { + if (false & !PsiTreeUtil.isAncestor(scope, expression, false)) return true; + return isGoodExpression(expression, allowedValues, scope, manager); + } + }); + } + + private static boolean isGoodExpression(PsiExpression expression, + AllowedValues allowedValues, + PsiElement scope, + PsiManager manager) { + expression = PsiUtil.deparenthesizeExpression(expression); + if (expression == null) return true; + if (expression instanceof PsiConditionalExpression) { + PsiExpression thenExpression = ((PsiConditionalExpression)expression).getThenExpression(); + boolean thenAllowed = thenExpression == null || isAllowed(scope, thenExpression, allowedValues, manager); + if (!thenAllowed) return false; + PsiExpression elseExpression = ((PsiConditionalExpression)expression).getElseExpression(); + return elseExpression == null || isAllowed(scope, elseExpression, allowedValues, manager); + } + + if (isOneOf(expression, allowedValues, manager)) return true; + + if (allowedValues.canBeOred) { + if (expression instanceof PsiPolyadicExpression && + JavaTokenType.OR.equals(((PsiPolyadicExpression)expression).getOperationTokenType())) { + for (PsiExpression operand : ((PsiPolyadicExpression)expression).getOperands()) { + if (!isAllowed(scope, operand, allowedValues, manager)) return false; + } + return true; + } + //todo & ~(CONST | CONST) + PsiExpression zero = JavaPsiFacade.getElementFactory(manager.getProject()).createExpressionFromText("0", expression); + if (same(expression, zero, manager)) return true; + PsiExpression mOne = JavaPsiFacade.getElementFactory(manager.getProject()).createExpressionFromText("-1", expression); + if (same(expression, mOne, manager)) return true; + } + + 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 && + Comparing.equal(allowedValues, allowedForRef)) return true; + + return PsiType.NULL.equals(expression.getType()); + } + + 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(PsiElement e1, 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, + PsiElement scope, + @NotNull final Processor processor) { + SliceAnalysisParams params = new SliceAnalysisParams(); + params.dataFlowToThis = true; + params.scope = new AnalysisScope(new LocalSearchScope(scope), argument.getProject()); + + SliceRootNode rootNode = new SliceRootNode(scope.getProject(), new DuplicateMap(), SliceManager.createRootUsage(argument, params)); + + Collection 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(); + + //Set elements = new THashSet(DfaUtil.getPossibleInitializationElements(argument)); + //elements.remove(argument); + //if (elements.isEmpty()) return processor.process(argument); + //for (PsiElement element : elements) { + // if (element != argument && !processor.process((PsiExpression)element)) return false; + //} + //return processor.process(argument); + + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/java-impl/src/com/intellij/slicer/SliceManager.java b/java/java-impl/src/com/intellij/slicer/SliceManager.java index 629851233aad..5d79d7c96c22 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceManager.java +++ b/java/java-impl/src/com/intellij/slicer/SliceManager.java @@ -44,8 +44,8 @@ import java.util.regex.Pattern; ) public class SliceManager implements PersistentStateComponent { private final Project myProject; - private final ContentManager myBackContentManager; - private final ContentManager myForthContentManager; + private ContentManager myBackContentManager; + private ContentManager myForthContentManager; private volatile boolean myCanceled; private final StoredSettingsBean myStoredSettings = new StoredSettingsBean(); private static final String BACK_TOOLWINDOW_ID = "Analyze Dataflow to"; @@ -60,15 +60,8 @@ public class SliceManager implements PersistentStateComponent + + + + + + + + + + X.java + 29 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 30 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 31 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 33 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 34 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 35 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 36 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 55 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 56 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 57 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 59 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 60 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 61 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 62 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + X.java + 81 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + X.java + 82 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 83 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 85 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 86 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 87 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 88 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + X.java + 118 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + X.java + 119 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 122 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 123 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 124 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 125 + + + + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + + X.java + 173 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 174 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 175 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 177 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + X.java + 178 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 179 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + + X.java + 180 + magic constant + Must be one of the: Const.X, Const.Y, Const.Z + + + + + X.java + 193 + magic constant + Must be one of the: Const.X, Const.Y + + + + + X.java + 195 + magic constant + Must be one of the: Const.X, Const.Y + + + + X.java + 227 + magic constant + Must be one of the: Const.X, Const.Y + + + + + X.java + 228 + magic constant + Must be one of the: Const.X, Const.Y + + + + + X.java + 229 + magic constant + Must be one of the: Const.X, Const.Y + + + + + X.java + 230 + magic constant + Must be one of the: Const.X, Const.Y + + + + X.java + 231 + magic constant + Must be one of the: Const.X, Const.Y + + + diff --git a/java/java-tests/testData/inspection/magic/simple/src/X.java b/java/java-tests/testData/inspection/magic/simple/src/X.java new file mode 100644 index 000000000000..766b3074a2e3 --- /dev/null +++ b/java/java-tests/testData/inspection/magic/simple/src/X.java @@ -0,0 +1,235 @@ +/* + * Copyright 2000-2011 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. + */ +import org.intellij.lang.annotations.MagicConstant; + +import java.io.*; + +class Const { + public static final int X = 0; + public static final int Y = 2; + public static final int Z = 3; +} +public class X { + + void f(@MagicConstant(intValues={Const.X, Const.Y, Const.Z}) int x) { + /////////// BAD + f(0); + f(1); + f(Const.X | Const.Y); + int i = Const.X | Const.Y; + f(i); + if (x == 3) { + x = 2; + assert x != 1; + } + + ////////////// GOOD + f(Const.X); + f(Const.Y); + f(Const.Z); + int i2 = this == null ? Const.X : Const.Y; + f(i2); + if (x == Const.X) { + x = Const.Y; + assert x != Const.Z; + } + + f2(x); + } + + void f2(@MagicConstant(valuesFromClass =Const.class) int x) { + /////////// BAD + f2(0); + f2(1); + f2(Const.X | Const.Y); + int i = Const.X | Const.Y; + f2(i); + if (x == 3) { + x = 2; + assert x != 1; + } + + ////////////// GOOD + f2(Const.X); + f2(Const.Y); + f2(Const.Z); + int i2 = this == null ? Const.X : Const.Y; + f2(i2); + if (x == Const.X) { + x = Const.Y; + assert x != Const.Z; + } + + f(x); + } + + void f3(@MagicConstant(flags ={Const.X, Const.Y, Const.Z}) int x) { + /////////// BAD + f3(2); + f3(1); + f(Const.X | Const.Y); + int i = Const.X | 4; + f3(i); + if (x == 3) { + x = 2; + assert x != 1; + } + + ////////////// GOOD + f3(Const.X); + f3(Const.Y); + f3(Const.Z); + + int i2 = this == null ? Const.X : Const.Y; + f3(i2); + int ix = Const.X | Const.Y; + f3(ix); + f3(0); + f3(-1); + int f = 0; + if (x == Const.X) { + x = Const.Y; + assert x != Const.Z; + f |= Const.Y; + } + else { + f |= Const.X; + } + f3(f); + + f4(x); + } + + void f4(@MagicConstant(flagsFromClass =Const.class) int x) { + /////////// BAD + f4(-3); + f4(1); + f4(Const.X | Const.Y); + int i = Const.X | 4; + f4(i); + if (x == 3) { + x = 2; + assert x != 1; + } + + ////////////// GOOD + f4(Const.X); + f4(Const.Y); + f4(Const.Z); + + int i2 = this == null ? Const.X : Const.Y; + f4(i2); + int ix = Const.X | Const.Y; + f4(ix); + f4(0); + f4(-1); + int f = 0; + if (x == Const.X) { + x = Const.Y; + assert x != Const.Z; + f |= Const.Y; + } + else { + f |= Const.X; + } + f4(f); + + f3(x); + } + + + class Alias { + @MagicConstant(intValues={Const.X, Const.Y, Const.Z}) + @interface IntEnum{} + + void f(@IntEnum int x) { + ////////////// GOOD + f(Const.X); + f(Const.Y); + f(Const.Z); + int i2 = this == null ? Const.X : Const.Y; + f(i2); + if (x == Const.X) { + x = Const.Y; + assert x != Const.Z; + } + + f2(x); + + /////////// BAD + f(0); + f(1); + f(Const.X | Const.Y); + int i = Const.X | Const.Y; + f(i); + if (x == 3 || getClass().isInterface()) { + x = 2; + assert x != 1; + } + + f2(x); + } + } + + class MagicAnnoInsideAnnotationUsage { + @interface III { + @MagicConstant(intValues = {Const.X, Const.Y}) int val(); + } + + // bad + @III(val = 2) + int h; + @III(val = Const.X | Const.Y) + void f(){} + + // good + @III(val = Const.X) + int h2; + } + + abstract class BeanInfoParsing { + /** + * @see java.lang.Runtime#exit(int) + * + * @beaninfo + * preferred: true + * bound: true + * enum: DO_NOTHING_ON_CLOSE Const.X + * HIDE_ON_CLOSE Const.Y + * description: The frame's default close operation. + */ + public void setX(int operation) { + + } + + public abstract int getX(); + + { + // good + setX(Const.X); + setX(Const.Y); + if (getX() == Const.X || getX() == Const.Y) {} + + // bad + setX(0); + setX(-1); + setX(Const.Z); + if (getX() == 1) {} + if (getX() == Const.Z) {} + } + + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/MagicConstantInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/MagicConstantInspectionTest.java new file mode 100644 index 000000000000..fb4ecbb3b843 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInspection/MagicConstantInspectionTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2011 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. + */ + +/* + * Created by IntelliJ IDEA. + * User: max + * Date: Apr 11, 2002 + * Time: 6:50:50 PM + * To change template for new class use + * Code Style | Class Templates options (Tools | IDE Options). + */ +package com.intellij.codeInspection; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; +import com.intellij.codeInspection.magicConstant.MagicConstantInspection; +import com.intellij.testFramework.InspectionTestCase; + +public class MagicConstantInspectionTest extends InspectionTestCase { + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath() + "/inspection"; + } + + private void doTest() throws Exception { + doTest("magic/" + getTestName(true), new LocalInspectionToolWrapper(new MagicConstantInspection()), "jdk 1.7"); + } + + public void testSimple() throws Exception { doTest(); } + + + + //{ + // SecurityManager securityManager = System.getSecurityManager(); + // securityManager.checkMemberAccess(getClass(), 948); + // + // Font font = null; + // new Cursor() ; + // JOptionPane.showConfirmDialog(null, null, null, 0, ); + // JList l = null; + // l.getSelectionModel().setSelectionMode(); + // new JSplitPane(9); + // MouseWheelEvent event = new MouseWheelEvent(null,0,0,0,0,0,0,false,0,0,0 ); + // Pattern p = Pattern.compile("", Pattern.CANON_EQ); + // JTree t = null; t.getSelectionModel().setSelectionMode(); + // + // TitledBorder border = new TitledBorder(null,"",0,0); + // new JLabel("text", ) + // Calendar calendar = Calendar.getInstance(); + // new Font("Arial", ) + //} + + //public static Font createFont() { + // return new Font("Arial", ); + //} + + + + + + +} diff --git a/java/mockJDK-1.7/jre/lib/annotations.jar b/java/mockJDK-1.7/jre/lib/annotations.jar index 5de67805f7b7..0a10d026aa4f 100644 Binary files a/java/mockJDK-1.7/jre/lib/annotations.jar and b/java/mockJDK-1.7/jre/lib/annotations.jar differ diff --git a/java/openapi/src/com/intellij/codeInsight/AnnotationUtil.java b/java/openapi/src/com/intellij/codeInsight/AnnotationUtil.java index 395a559b8af1..c5c008eaee88 100644 --- a/java/openapi/src/com/intellij/codeInsight/AnnotationUtil.java +++ b/java/openapi/src/com/intellij/codeInsight/AnnotationUtil.java @@ -87,7 +87,7 @@ public class AnnotationUtil { } @Nullable - public static PsiAnnotation findAnnotation(PsiModifierListOwner listOwner, @NotNull Set annotationNames) { + public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, @NotNull Set annotationNames) { return findAnnotation(listOwner, (Collection)annotationNames); } @@ -97,7 +97,7 @@ public class AnnotationUtil { } @Nullable - public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, Collection annotationNames, + public static PsiAnnotation findAnnotation(@Nullable PsiModifierListOwner listOwner, @NotNull Collection annotationNames, final boolean skipExternal) { if (listOwner == null) return null; final PsiModifierList list = listOwner.getModifierList(); @@ -138,7 +138,7 @@ public class AnnotationUtil { } @Nullable - public static PsiAnnotation findAnnotationInHierarchy(PsiModifierListOwner listOwner, Set annotationNames) { + public static PsiAnnotation findAnnotationInHierarchy(PsiModifierListOwner listOwner, @NotNull Set annotationNames) { PsiAnnotation directAnnotation = findAnnotation(listOwner, annotationNames); if (directAnnotation != null) return directAnnotation; if (listOwner instanceof PsiMethod) { @@ -146,15 +146,17 @@ public class AnnotationUtil { PsiClass aClass = method.getContainingClass(); if (aClass == null) return null; HierarchicalMethodSignature methodSignature = method.getHierarchicalMethodSignature(); - return findAnnotationInHierarchy(methodSignature, annotationNames, method, null); - } else if (listOwner instanceof PsiClass) { - return findAnnotationInHierarchy(((PsiClass)listOwner), annotationNames, null); + return findAnnotationInHierarchy(methodSignature, annotationNames, method, null, + JavaPsiFacade.getInstance(method.getProject()).getResolveHelper()); + } + if (listOwner instanceof PsiClass) { + return findAnnotationInHierarchy((PsiClass)listOwner, annotationNames, null); } return null; } @Nullable - private static PsiAnnotation findAnnotationInHierarchy(final @NotNull PsiClass psiClass, final Set annotationNames, @Nullable Set processed) { + private static PsiAnnotation findAnnotationInHierarchy(@NotNull final PsiClass psiClass, final Set annotationNames, @Nullable Set processed) { final PsiClass[] superClasses = psiClass.getSupers(); for (final PsiClass superClass : superClasses) { if (processed == null) processed = new THashSet(); @@ -168,12 +170,12 @@ public class AnnotationUtil { } @Nullable - private static PsiAnnotation findAnnotationInHierarchy(HierarchicalMethodSignature signature, - Set annotationNames, - PsiElement place, - @Nullable Set processed) { + private static PsiAnnotation findAnnotationInHierarchy(@NotNull HierarchicalMethodSignature signature, + @NotNull Set annotationNames, + @NotNull PsiElement place, + @Nullable Set processed, + @NotNull PsiResolveHelper resolveHelper) { final List superSignatures = signature.getSuperSignatures(); - final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(place.getProject()).getResolveHelper(); for (final HierarchicalMethodSignature superSignature : superSignatures) { final PsiMethod superMethod = superSignature.getMethod(); if (processed == null) processed = new THashSet(); @@ -181,7 +183,7 @@ public class AnnotationUtil { if (!resolveHelper.isAccessible(superMethod, place, null)) continue; PsiAnnotation direct = findAnnotation(superMethod, annotationNames); if (direct != null) return direct; - PsiAnnotation superResult = findAnnotationInHierarchy(superSignature, annotationNames, place, processed); + PsiAnnotation superResult = findAnnotationInHierarchy(superSignature, annotationNames, place, processed, resolveHelper); if (superResult != null) return superResult; } @@ -261,10 +263,10 @@ public class AnnotationUtil { * @return true if annotated of at least one annotation from the annotations list */ public static boolean checkAnnotatedUsingPatterns(PsiModifierListOwner owner, Collection annotations) { - List fqns = null; final PsiModifierList modList; if (owner == null || (modList = owner.getModifierList()) == null) return false; + List fqns = null; for (String fqn : annotations) { boolean isPattern = fqn.endsWith("*"); if (!isPattern && isAnnotated(owner, fqn, false)) { @@ -307,4 +309,71 @@ public class AnnotationUtil { } return null; } + + @NotNull + public static PsiAnnotation[] getAllAnnotations(@NotNull PsiModifierListOwner owner, boolean inHierarchy, Set visited) { + final PsiModifierList list = owner.getModifierList(); + PsiAnnotation[] annotations = PsiAnnotation.EMPTY_ARRAY; + if (list != null) { + annotations = list.getAnnotations(); + } + + final PsiAnnotation[] externalAnnotations = ExternalAnnotationsManager.getInstance(owner.getProject()).findExternalAnnotations(owner); + if (externalAnnotations != null) { + annotations = ArrayUtil.mergeArrays(annotations, externalAnnotations, PsiAnnotation.ARRAY_FACTORY); + } + + if (inHierarchy) { + if (owner instanceof PsiClass) { + for (PsiClass superClass : ((PsiClass)owner).getSupers()) { + if (visited == null) visited = new THashSet(); + if (visited.add(superClass)) annotations = ArrayUtil.mergeArrays(annotations, getAllAnnotations(superClass, inHierarchy, visited)); + } + } + else if (owner instanceof PsiMethod) { + PsiMethod method = (PsiMethod)owner; + PsiClass aClass = method.getContainingClass(); + if (aClass != null) { + HierarchicalMethodSignature methodSignature = method.getHierarchicalMethodSignature(); + + final List superSignatures = methodSignature.getSuperSignatures(); + PsiResolveHelper resolveHelper = PsiResolveHelper.SERVICE.getInstance(aClass.getProject()); + for (final HierarchicalMethodSignature superSignature : superSignatures) { + final PsiMethod superMethod = superSignature.getMethod(); + if (visited == null) visited = new THashSet(); + if (!visited.add(superMethod)) continue; + if (!resolveHelper.isAccessible(superMethod, owner, null)) continue; + annotations = ArrayUtil.mergeArrays(annotations, getAllAnnotations(superMethod, inHierarchy, visited)); + } + } + } + else if (owner instanceof PsiParameter) { + PsiParameter parameter = (PsiParameter)owner; + PsiElement scope = parameter.getDeclarationScope(); + if (scope instanceof PsiMethod) { + PsiMethod method = (PsiMethod)scope; + PsiClass aClass = method.getContainingClass(); + PsiElement parent = parameter.getParent(); + if (aClass != null && parent instanceof PsiParameterList) { + int index = ((PsiParameterList)parent).getParameterIndex(parameter); + HierarchicalMethodSignature methodSignature = method.getHierarchicalMethodSignature(); + + final List superSignatures = methodSignature.getSuperSignatures(); + PsiResolveHelper resolveHelper = PsiResolveHelper.SERVICE.getInstance(aClass.getProject()); + for (final HierarchicalMethodSignature superSignature : superSignatures) { + final PsiMethod superMethod = superSignature.getMethod(); + if (visited == null) visited = new THashSet(); + if (!visited.add(superMethod)) continue; + if (!resolveHelper.isAccessible(superMethod, owner, null)) continue; + PsiParameter[] superParameters = superMethod.getParameterList().getParameters(); + if (index < superParameters.length) { + annotations = ArrayUtil.mergeArrays(annotations, getAllAnnotations(superParameters[index], inHierarchy, visited)); + } + } + } + } + } + } + return annotations; + } } diff --git a/java/openapi/src/com/intellij/psi/util/PsiFormatUtil.java b/java/openapi/src/com/intellij/psi/util/PsiFormatUtil.java index d8fb6ab5dd90..07f974b9fa29 100644 --- a/java/openapi/src/com/intellij/psi/util/PsiFormatUtil.java +++ b/java/openapi/src/com/intellij/psi/util/PsiFormatUtil.java @@ -17,18 +17,24 @@ package com.intellij.psi.util; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; +import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class PsiFormatUtil extends PsiFormatUtilBase { + @MagicConstant(flags = {SHOW_MODIFIERS, SHOW_TYPE, TYPE_AFTER, SHOW_CONTAINING_CLASS, SHOW_FQ_NAME, SHOW_NAME, SHOW_MODIFIERS, SHOW_INITIALIZER, SHOW_RAW_TYPE, SHOW_RAW_NON_TOP_TYPE, SHOW_FQ_CLASS_NAMES}) + public @interface FormatVariableOptions {} - public static String formatVariable(PsiVariable variable, int options, PsiSubstitutor substitutor){ + public static String formatVariable(PsiVariable variable, @FormatVariableOptions int options, PsiSubstitutor substitutor){ StringBuilder buffer = new StringBuilder(); formatVariable(variable, options, substitutor,buffer); return buffer.toString(); } - private static void formatVariable(PsiVariable variable, int options, PsiSubstitutor substitutor,StringBuilder buffer){ + private static void formatVariable(PsiVariable variable, + @FormatVariableOptions int options, + PsiSubstitutor substitutor, + @NotNull StringBuilder buffer){ if ((options & SHOW_MODIFIERS) != 0 && (options & MODIFIERS_AFTER) == 0){ formatModifiers(variable, options,buffer); } @@ -97,16 +103,20 @@ public class PsiFormatUtil extends PsiFormatUtilBase { } } - public static String formatMethod(PsiMethod method, PsiSubstitutor substitutor, int options, int parameterOptions){ + public static String formatMethod(PsiMethod method, PsiSubstitutor substitutor, @FormatMethodOptions int options, @FormatVariableOptions int parameterOptions){ return formatMethod(method, substitutor, options, parameterOptions, MAX_PARAMS_TO_SHOW); } - public static String formatMethod(PsiMethod method, PsiSubstitutor substitutor, int options, int parameterOptions, int maxParametersToShow){ + public static String formatMethod(PsiMethod method, PsiSubstitutor substitutor, @FormatMethodOptions int options, @FormatVariableOptions int parameterOptions, int maxParametersToShow){ StringBuilder buffer = new StringBuilder(); formatMethod(method, substitutor, options, parameterOptions, maxParametersToShow,buffer); return buffer.toString(); } - private static void formatMethod(PsiMethod method, PsiSubstitutor substitutor, int options, int parameterOptions, int maxParametersToShow, StringBuilder buffer){ + + @MagicConstant(flags = {SHOW_MODIFIERS, MODIFIERS_AFTER, SHOW_TYPE, TYPE_AFTER, SHOW_CONTAINING_CLASS, SHOW_FQ_NAME, SHOW_NAME, SHOW_PARAMETERS, SHOW_THROWS, SHOW_RAW_TYPE, SHOW_RAW_NON_TOP_TYPE, SHOW_FQ_CLASS_NAMES}) + public @interface FormatMethodOptions {} + + private static void formatMethod(PsiMethod method, PsiSubstitutor substitutor, @FormatMethodOptions int options, @FormatVariableOptions int parameterOptions, int maxParametersToShow, StringBuilder buffer){ if ((options & SHOW_MODIFIERS) != 0 && (options & MODIFIERS_AFTER) == 0){ formatModifiers(method, options,buffer); } @@ -177,7 +187,7 @@ public class PsiFormatUtil extends PsiFormatUtilBase { } if ((options & SHOW_THROWS) != 0){ String throwsText = formatReferenceList(method.getThrowsList(), options); - if (throwsText.length() > 0){ + if (!throwsText.isEmpty()){ appendSpaceIfNeeded(buffer); //noinspection HardCodedStringLiteral buffer.append("throws "); @@ -186,7 +196,12 @@ public class PsiFormatUtil extends PsiFormatUtilBase { } } - @NotNull public static String formatClass(@NotNull PsiClass aClass, int options){ + + @MagicConstant(flags = {SHOW_MODIFIERS, SHOW_NAME, SHOW_ANONYMOUS_CLASS_VERBOSE, SHOW_FQ_NAME, MODIFIERS_AFTER, SHOW_EXTENDS_IMPLEMENTS, SHOW_REDUNDANT_MODIFIERS, JAVADOC_MODIFIERS_ONLY}) + public @interface FormatClassOptions {} + + @NotNull + public static String formatClass(@NotNull PsiClass aClass, @FormatClassOptions int options){ StringBuilder buffer = new StringBuilder(); if ((options & SHOW_MODIFIERS) != 0 && (options & MODIFIERS_AFTER) == 0){ formatModifiers(aClass, options,buffer); @@ -222,14 +237,14 @@ public class PsiFormatUtil extends PsiFormatUtilBase { } if ((options & SHOW_EXTENDS_IMPLEMENTS) != 0){ String extendsText = formatReferenceList(aClass.getExtendsList(), options); - if (extendsText.length() > 0){ + if (!extendsText.isEmpty()){ appendSpaceIfNeeded(buffer); //noinspection HardCodedStringLiteral buffer.append("extends "); buffer.append(extendsText); } String implementsText = formatReferenceList(aClass.getImplementsList(), options); - if (implementsText.length() > 0){ + if (!implementsText.isEmpty()){ appendSpaceIfNeeded(buffer); //noinspection HardCodedStringLiteral buffer.append("implements "); @@ -340,7 +355,7 @@ public class PsiFormatUtil extends PsiFormatUtilBase { return buffer.toString(); } - public static String formatType(PsiType type, int options, PsiSubstitutor substitutor){ + public static String formatType(PsiType type, int options, @NotNull PsiSubstitutor substitutor){ type = substitutor.substitute(type); if ((options & SHOW_RAW_TYPE) != 0) { type = TypeConversionUtil.erasure(type); @@ -367,6 +382,11 @@ public class PsiFormatUtil extends PsiFormatUtilBase { @Nullable public static String getExternalName(PsiModifierListOwner owner, final boolean showParamName) { + return getExternalName(owner, showParamName, MAX_PARAMS_TO_SHOW); + } + + @Nullable + public static String getExternalName(PsiModifierListOwner owner, final boolean showParamName, int maxParamsToShow) { final StringBuilder builder = new StringBuilder(); if (owner instanceof PsiClass) { ClassUtil.formatClassName((PsiClass)owner, builder); @@ -379,7 +399,7 @@ public class PsiFormatUtil extends PsiFormatUtilBase { builder.append(" "); formatMethod((PsiMethod)owner, PsiSubstitutor.EMPTY, SHOW_NAME | SHOW_FQ_NAME | SHOW_TYPE | SHOW_PARAMETERS | SHOW_FQ_CLASS_NAMES, - showParamName ? SHOW_NAME | SHOW_TYPE | SHOW_FQ_CLASS_NAMES : SHOW_TYPE | SHOW_FQ_CLASS_NAMES, MAX_PARAMS_TO_SHOW, builder); + showParamName ? SHOW_NAME | SHOW_TYPE | SHOW_FQ_CLASS_NAMES : SHOW_TYPE | SHOW_FQ_CLASS_NAMES, maxParamsToShow, builder); } else if (owner instanceof PsiField) { builder.append(" ").append(((PsiField)owner).getName()); @@ -392,7 +412,7 @@ public class PsiFormatUtil extends PsiFormatUtilBase { builder.append(" "); formatMethod(psiMethod, PsiSubstitutor.EMPTY, SHOW_NAME | SHOW_FQ_NAME | SHOW_TYPE | SHOW_PARAMETERS | SHOW_FQ_CLASS_NAMES, - showParamName ? SHOW_NAME | SHOW_TYPE | SHOW_FQ_CLASS_NAMES : SHOW_TYPE | SHOW_FQ_CLASS_NAMES, MAX_PARAMS_TO_SHOW, builder); + showParamName ? SHOW_NAME | SHOW_TYPE | SHOW_FQ_CLASS_NAMES : SHOW_TYPE | SHOW_FQ_CLASS_NAMES, maxParamsToShow, builder); builder.append(" "); if (showParamName) { @@ -415,7 +435,7 @@ public class PsiFormatUtil extends PsiFormatUtilBase { public static String getPackageDisplayName(@NotNull final PsiClass psiClass) { @NonNls String packageName = psiClass.getQualifiedName(); packageName = packageName == null || packageName.lastIndexOf('.') <= 0 ? "" : packageName.substring(0, packageName.lastIndexOf('.')); - if (packageName.length() == 0) { + if (packageName.isEmpty()) { packageName = "default package"; } return packageName; diff --git a/platform/annotations/src/org/intellij/lang/annotations/MagicConstant.java b/platform/annotations/src/org/intellij/lang/annotations/MagicConstant.java new file mode 100644 index 000000000000..aa5522c56b8b --- /dev/null +++ b/platform/annotations/src/org/intellij/lang/annotations/MagicConstant.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2012 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.intellij.lang.annotations; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, + ElementType.ANNOTATION_TYPE, // to subclass + ElementType.METHOD + }) +public @interface MagicConstant { + Class valuesFromClass() default void.class; + Class flagsFromClass() default void.class; // int constants which can be combined via | (bitwise or) operator. 0 and -1 are considered acceptable values. + String[] stringValues() default {}; + long[] intValues() default {}; + long[] flags() default {}; // int constants which can be combined via | (bitwise or) operator. 0 and -1 are considered acceptable values. +} diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 72cb9dbee1c3..363d08530d90 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -443,6 +443,7 @@ + diff --git a/resources-en/src/inspectionDescriptions/MagicConstant.html b/resources-en/src/inspectionDescriptions/MagicConstant.html new file mode 100644 index 000000000000..c45a06c7fa6a --- /dev/null +++ b/resources-en/src/inspectionDescriptions/MagicConstant.html @@ -0,0 +1,13 @@ + + +Report occurrences where usages of "magic" constants only are allowed + but other expressions are used instead.
+ E.g. new Font("Arial", 42)
+ instead of new Font("Arial", Font.BOLD)
+ +

+ + Please see org.intellij.lang.annotations.MagicConstant annotation description for details. +
+ + diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 36fd2f086b99..270dff6a41b6 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -515,6 +515,9 @@ + com.intellij.codeInsight.intention.impl.SplitIfAction