mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-04 00:20:55 +07:00
in production InspectionEP#displayName should be used; highlighting tests just do not use default names GitOrigin-RevId: f0c172e568219499e7b96982fe352c7d1d3acc69
594 lines
26 KiB
Java
594 lines
26 KiB
Java
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
|
package com.intellij.codeInspection;
|
|
|
|
import com.intellij.codeInsight.AnnotationUtil;
|
|
import com.intellij.codeInsight.ChangeContextUtil;
|
|
import com.intellij.codeInsight.daemon.GroupNames;
|
|
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
|
|
import com.intellij.codeInsight.intention.HighPriorityAction;
|
|
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.util.Comparing;
|
|
import com.intellij.openapi.util.TextRange;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import com.intellij.pom.java.LanguageLevel;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
|
import com.intellij.psi.controlFlow.AnalysisCanceledException;
|
|
import com.intellij.psi.controlFlow.ControlFlow;
|
|
import com.intellij.psi.controlFlow.ControlFlowUtil;
|
|
import com.intellij.psi.util.*;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import com.intellij.util.containers.hash.LinkedHashMap;
|
|
import com.intellij.util.text.UniqueNameGenerator;
|
|
import org.jetbrains.annotations.Nls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import javax.swing.*;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.*;
|
|
import java.util.function.UnaryOperator;
|
|
|
|
import static com.intellij.codeInsight.AnnotationUtil.CHECK_EXTERNAL;
|
|
|
|
public class AnonymousCanBeLambdaInspection extends AbstractBaseJavaLocalInspectionTool {
|
|
public static final Logger LOG = Logger.getInstance(AnonymousCanBeLambdaInspection.class);
|
|
|
|
public boolean reportNotAnnotatedInterfaces = true;
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getGroupDisplayName() {
|
|
return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME;
|
|
}
|
|
|
|
@Override
|
|
public boolean isEnabledByDefault() {
|
|
return true;
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public String getShortName() {
|
|
return "Convert2Lambda";
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public JComponent createOptionsPanel() {
|
|
return new SingleCheckboxOptionsPanel("Report when interface is not annotated with @FunctionalInterface", this, "reportNotAnnotatedInterfaces");
|
|
}
|
|
|
|
@NotNull
|
|
@Override
|
|
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
|
|
return new JavaElementVisitor() {
|
|
@Override
|
|
public void visitAnonymousClass(final PsiAnonymousClass aClass) {
|
|
super.visitAnonymousClass(aClass);
|
|
final PsiElement parent = aClass.getParent();
|
|
if (canBeConvertedToLambda(aClass, false, isOnTheFly || reportNotAnnotatedInterfaces, Collections.emptySet())) {
|
|
final PsiElement lBrace = aClass.getLBrace();
|
|
LOG.assertTrue(lBrace != null);
|
|
final TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent());
|
|
ProblemHighlightType type = ProblemHighlightType.LIKE_UNUSED_SYMBOL;
|
|
if (isOnTheFly && !reportNotAnnotatedInterfaces) {
|
|
final PsiClass baseClass = aClass.getBaseClassType().resolve();
|
|
LOG.assertTrue(baseClass != null);
|
|
if (!AnnotationUtil.isAnnotated(baseClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, CHECK_EXTERNAL)) {
|
|
type = ProblemHighlightType.INFORMATION;
|
|
}
|
|
}
|
|
holder.registerProblem(parent, "Anonymous #ref #loc can be replaced with lambda",
|
|
type, rangeInElement, new ReplaceWithLambdaFix());
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
public static boolean hasRuntimeAnnotations(PsiModifierListOwner listOwner, @NotNull Set<String> runtimeAnnotationsToIgnore) {
|
|
PsiModifierList modifierList = listOwner.getModifierList();
|
|
if (modifierList == null) return false;
|
|
PsiAnnotation[] annotations = modifierList.getAnnotations();
|
|
for (PsiAnnotation annotation : annotations) {
|
|
PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement();
|
|
PsiElement target = ref != null ? ref.resolve() : null;
|
|
if (target instanceof PsiClass) {
|
|
if (runtimeAnnotationsToIgnore.contains(((PsiClass)target).getQualifiedName())) {
|
|
continue;
|
|
}
|
|
final PsiAnnotation retentionAnno = AnnotationUtil.findAnnotation((PsiClass)target, Retention.class.getName());
|
|
if (retentionAnno != null) {
|
|
PsiAnnotationMemberValue value = retentionAnno.findAttributeValue("value");
|
|
if (value instanceof PsiReferenceExpression) {
|
|
final PsiElement resolved = ((PsiReferenceExpression)value).resolve();
|
|
if (resolved instanceof PsiField && RetentionPolicy.RUNTIME.name().equals(((PsiField)resolved).getName())) {
|
|
final PsiClass containingClass = ((PsiField)resolved).getContainingClass();
|
|
if (containingClass != null && RetentionPolicy.class.getName().equals(containingClass.getQualifiedName())) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static boolean hasForbiddenRefsInsideBody(PsiMethod method, PsiAnonymousClass aClass) {
|
|
final ForbiddenRefsChecker checker = new ForbiddenRefsChecker(method, aClass);
|
|
final PsiCodeBlock body = method.getBody();
|
|
LOG.assertTrue(body != null);
|
|
body.accept(checker);
|
|
return checker.hasForbiddenRefs();
|
|
}
|
|
|
|
private static PsiType getInferredType(PsiAnonymousClass aClass, PsiMethod method) {
|
|
final PsiExpression expression = (PsiExpression)aClass.getParent();
|
|
final PsiType psiType = PsiTypesUtil.getExpectedTypeByParent(expression);
|
|
if (psiType != null) {
|
|
return psiType;
|
|
}
|
|
|
|
PsiExpression topExpr = expression;
|
|
while (topExpr.getParent() instanceof PsiParenthesizedExpression) {
|
|
topExpr = (PsiExpression)topExpr.getParent();
|
|
}
|
|
|
|
final PsiCall call = LambdaUtil.treeWalkUp(topExpr);
|
|
if (call != null && call.resolveMethod() != null) {
|
|
Object marker = new Object();
|
|
PsiTreeUtil.mark(aClass, marker);
|
|
PsiCall copyCall = LambdaUtil.copyTopLevelCall(call);
|
|
if (copyCall == null) return null;
|
|
final PsiElement classArg = PsiTreeUtil.releaseMark(copyCall, marker);
|
|
if (classArg instanceof PsiAnonymousClass) {
|
|
PsiExpression lambda = JavaPsiFacade.getElementFactory(aClass.getProject())
|
|
.createExpressionFromText(ReplaceWithLambdaFix.composeLambdaText(method), expression);
|
|
lambda = (PsiExpression)classArg.getParent().replace(lambda);
|
|
((PsiLambdaExpression)lambda).getBody().replace(method.getBody());
|
|
final PsiType interfaceType;
|
|
if (copyCall.resolveMethod() == null) {
|
|
return PsiType.NULL;
|
|
}
|
|
else {
|
|
interfaceType = ((PsiLambdaExpression)lambda).getFunctionalInterfaceType();
|
|
}
|
|
|
|
return interfaceType;
|
|
}
|
|
}
|
|
|
|
return PsiType.NULL;
|
|
}
|
|
|
|
public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass,
|
|
boolean acceptParameterizedFunctionTypes,
|
|
@NotNull Set<String> ignoredRuntimeAnnotations) {
|
|
return canBeConvertedToLambda(aClass, acceptParameterizedFunctionTypes, true, ignoredRuntimeAnnotations);
|
|
}
|
|
|
|
public static boolean isLambdaForm(PsiAnonymousClass aClass, Set<String> ignoredRuntimeAnnotations) {
|
|
PsiMethod[] methods = aClass.getMethods();
|
|
if(methods.length != 1) return false;
|
|
PsiMethod method = methods[0];
|
|
return aClass.getFields().length == 0 &&
|
|
aClass.getInnerClasses().length == 0 &&
|
|
aClass.getInitializers().length == 0 &&
|
|
method.getBody() != null &&
|
|
method.getDocComment() == null &&
|
|
!hasRuntimeAnnotations(method, ignoredRuntimeAnnotations) &&
|
|
!method.hasModifierProperty(PsiModifier.SYNCHRONIZED) &&
|
|
!method.hasModifierProperty(PsiModifier.STRICTFP) &&
|
|
!hasForbiddenRefsInsideBody(method, aClass);
|
|
}
|
|
|
|
public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass,
|
|
boolean acceptParameterizedFunctionTypes,
|
|
boolean reportNotAnnotatedInterfaces,
|
|
@NotNull Set<String> ignoredRuntimeAnnotations) {
|
|
PsiElement parent = aClass.getParent();
|
|
final PsiElement lambdaContext = parent != null ? PsiUtil.skipParenthesizedExprUp(parent.getParent()) : null;
|
|
if (lambdaContext == null || !LambdaUtil.isValidLambdaContext(lambdaContext) && !(lambdaContext instanceof PsiReferenceExpression)) return false;
|
|
return isLambdaForm(aClass, acceptParameterizedFunctionTypes, reportNotAnnotatedInterfaces, ignoredRuntimeAnnotations);
|
|
}
|
|
|
|
public static boolean isLambdaForm(PsiAnonymousClass aClass,
|
|
boolean acceptParameterizedFunctionTypes,
|
|
@NotNull Set<String> ignoredRuntimeAnnotations) {
|
|
return isLambdaForm(aClass, acceptParameterizedFunctionTypes, true, ignoredRuntimeAnnotations);
|
|
}
|
|
|
|
public static boolean isLambdaForm(PsiAnonymousClass aClass,
|
|
boolean acceptParameterizedFunctionTypes,
|
|
boolean reportNotAnnotatedInterfaces,
|
|
@NotNull Set<String> ignoredRuntimeAnnotations) {
|
|
if (PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8)) {
|
|
final PsiClassType baseClassType = aClass.getBaseClassType();
|
|
final PsiClassType.ClassResolveResult resolveResult = baseClassType.resolveGenerics();
|
|
final PsiClass baseClass = resolveResult.getElement();
|
|
if (baseClass == null ||
|
|
!reportNotAnnotatedInterfaces && !AnnotationUtil.isAnnotated(baseClass, CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE, CHECK_EXTERNAL)) {
|
|
return false;
|
|
}
|
|
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult);
|
|
if (interfaceMethod != null && (acceptParameterizedFunctionTypes || !interfaceMethod.hasTypeParameters())) {
|
|
if (isLambdaForm(aClass, ignoredRuntimeAnnotations)) {
|
|
final PsiMethod method = aClass.getMethods()[0];
|
|
return getInferredType(aClass, method) != null;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static PsiExpression replaceAnonymousWithLambda(@NotNull PsiElement anonymousClass, PsiType expectedType) {
|
|
PsiNewExpression newArrayExpression = (PsiNewExpression)JavaPsiFacade.getElementFactory(anonymousClass.getProject())
|
|
.createExpressionFromText("new " + expectedType.getCanonicalText() + "[]{" + anonymousClass.getText() + "}", anonymousClass);
|
|
PsiArrayInitializerExpression initializer = newArrayExpression.getArrayInitializer();
|
|
LOG.assertTrue(initializer != null);
|
|
return replacePsiElementWithLambda(initializer.getInitializers()[0], true, false);
|
|
}
|
|
|
|
public static PsiExpression replacePsiElementWithLambda(@NotNull PsiElement element,
|
|
final boolean ignoreEqualsMethod,
|
|
boolean forceIgnoreTypeCast) {
|
|
if (!(element instanceof PsiNewExpression)) {
|
|
return null;
|
|
}
|
|
|
|
final PsiNewExpression newExpression = (PsiNewExpression)element;
|
|
final PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass();
|
|
|
|
if (anonymousClass == null) return null;
|
|
|
|
final PsiMethod method;
|
|
if (ignoreEqualsMethod) {
|
|
final List<PsiMethod> methods = ContainerUtil.filter(anonymousClass.getMethods(), method1 -> !"equals".equals(method1.getName()));
|
|
method = methods.get(0);
|
|
} else {
|
|
method = anonymousClass.getMethods()[0];
|
|
}
|
|
if (method == null || method.getBody() == null) return null;
|
|
|
|
return generateLambdaByMethod(anonymousClass, method, lambda -> (PsiLambdaExpression)newExpression.replace(lambda),
|
|
forceIgnoreTypeCast);
|
|
}
|
|
|
|
/**
|
|
* Try convert given method of given anonymous class into lambda and replace given element.
|
|
*
|
|
* @param anonymousClass physical anonymous class containing method
|
|
* @param method physical method to convert with non-empty body
|
|
* @param replacer an operator which actually inserts a lambda into the file (possibly removing anonymous class)
|
|
* and returns an inserted physical lambda
|
|
* @param forceIgnoreTypeCast if false, type cast might be added if necessary
|
|
* @return newly-generated lambda expression (possibly with typecast)
|
|
*/
|
|
@NotNull
|
|
static PsiExpression generateLambdaByMethod(PsiAnonymousClass anonymousClass,
|
|
PsiMethod method,
|
|
UnaryOperator<PsiLambdaExpression> replacer,
|
|
boolean forceIgnoreTypeCast) {
|
|
ChangeContextUtil.encodeContextInfo(anonymousClass, true);
|
|
final String canonicalText = anonymousClass.getBaseClassType().getCanonicalText();
|
|
|
|
final PsiCodeBlock body = method.getBody();
|
|
LOG.assertTrue(body != null);
|
|
|
|
final Collection<PsiComment> comments = collectCommentsOutsideMethodBody(anonymousClass.getParent(), body);
|
|
final Project project = anonymousClass.getProject();
|
|
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
|
|
|
|
final String withoutTypesDeclared = ReplaceWithLambdaFix.composeLambdaText(method);
|
|
|
|
PsiLambdaExpression lambdaExpression =
|
|
(PsiLambdaExpression)elementFactory.createExpressionFromText(withoutTypesDeclared, anonymousClass);
|
|
|
|
PsiElement lambdaBody = lambdaExpression.getBody();
|
|
LOG.assertTrue(lambdaBody != null);
|
|
lambdaBody.replace(body);
|
|
lambdaExpression = replacer.apply(lambdaExpression);
|
|
|
|
final Set<PsiVariable> variables = new HashSet<>();
|
|
final Set<String> usedLocalNames = new HashSet<>();
|
|
|
|
collectLocalVariablesDefinedInsideLambda(lambdaExpression, variables, usedLocalNames);
|
|
|
|
ReplaceWithLambdaFix
|
|
.giveUniqueNames(project, elementFactory, lambdaExpression,
|
|
usedLocalNames, variables.toArray(new PsiVariable[0]));
|
|
|
|
final PsiExpression singleExpr = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(lambdaExpression.getBody());
|
|
if (singleExpr != null) {
|
|
lambdaExpression.getBody().replace(singleExpr);
|
|
}
|
|
ChangeContextUtil.decodeContextInfo(lambdaExpression, null, null);
|
|
restoreComments(comments, lambdaExpression);
|
|
|
|
final JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
|
|
if (forceIgnoreTypeCast) {
|
|
return (PsiExpression)javaCodeStyleManager.shortenClassReferences(lambdaExpression);
|
|
}
|
|
|
|
PsiTypeCastExpression typeCast = (PsiTypeCastExpression)elementFactory
|
|
.createExpressionFromText("(" + canonicalText + ")" + withoutTypesDeclared, lambdaExpression);
|
|
final PsiExpression typeCastOperand = typeCast.getOperand();
|
|
LOG.assertTrue(typeCastOperand instanceof PsiLambdaExpression);
|
|
final PsiElement fromText = ((PsiLambdaExpression)typeCastOperand).getBody();
|
|
LOG.assertTrue(fromText != null);
|
|
lambdaBody = lambdaExpression.getBody();
|
|
LOG.assertTrue(lambdaBody != null);
|
|
fromText.replace(lambdaBody);
|
|
((PsiLambdaExpression)typeCastOperand).getParameterList().replace(lambdaExpression.getParameterList());
|
|
typeCast = (PsiTypeCastExpression)lambdaExpression.replace(typeCast);
|
|
if (RedundantCastUtil.isCastRedundant(typeCast)) {
|
|
final PsiExpression operand = typeCast.getOperand();
|
|
LOG.assertTrue(operand != null);
|
|
return (PsiExpression)typeCast.replace(operand);
|
|
}
|
|
return (PsiExpression)javaCodeStyleManager.shortenClassReferences(typeCast);
|
|
}
|
|
|
|
@NotNull
|
|
static Collection<PsiComment> collectCommentsOutsideMethodBody(PsiElement anonymousClass, PsiCodeBlock body) {
|
|
final Collection<PsiComment> psiComments = PsiTreeUtil.findChildrenOfType(anonymousClass, PsiComment.class);
|
|
psiComments.removeIf(comment -> PsiTreeUtil.isAncestor(body, comment, false));
|
|
return ContainerUtil.map(psiComments, (comment) -> (PsiComment)comment.copy());
|
|
}
|
|
|
|
private static void collectLocalVariablesDefinedInsideLambda(PsiLambdaExpression lambdaExpression,
|
|
final Set<PsiVariable> variables,
|
|
Set<? super String> namesOfVariablesInTheBlock) {
|
|
PsiElement block = PsiUtil.getTopLevelEnclosingCodeBlock(lambdaExpression, null);
|
|
if (block == null) {
|
|
block = lambdaExpression;
|
|
}
|
|
|
|
block.accept(new JavaRecursiveElementWalkingVisitor() {
|
|
@Override
|
|
public void visitVariable(PsiVariable variable) {
|
|
super.visitVariable(variable);
|
|
if (!(variable instanceof PsiField)) {
|
|
variables.add(variable);
|
|
}
|
|
}
|
|
});
|
|
|
|
final PsiResolveHelper helper = PsiResolveHelper.SERVICE.getInstance(lambdaExpression.getProject());
|
|
for (Iterator<PsiVariable> iterator = variables.iterator(); iterator.hasNext(); ) {
|
|
PsiVariable local = iterator.next();
|
|
final String localName = local.getName();
|
|
if (localName == null ||
|
|
shadowingResolve(localName, lambdaExpression, helper) ||
|
|
!PsiTreeUtil.isAncestor(lambdaExpression, local, false)) {
|
|
iterator.remove();
|
|
namesOfVariablesInTheBlock.add(localName);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean shadowingResolve(String localName, PsiLambdaExpression lambdaExpression, PsiResolveHelper helper) {
|
|
final PsiVariable variable = helper.resolveReferencedVariable(localName, lambdaExpression);
|
|
return variable == null || variable instanceof PsiField;
|
|
}
|
|
|
|
private static class ReplaceWithLambdaFix implements LocalQuickFix, HighPriorityAction {
|
|
@NotNull
|
|
@Override
|
|
public String getFamilyName() {
|
|
return "Replace with lambda";
|
|
}
|
|
|
|
@Override
|
|
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
|
final PsiElement element = descriptor.getPsiElement();
|
|
if (element != null) {
|
|
replacePsiElementWithLambda(element, false, false);
|
|
}
|
|
}
|
|
|
|
private static void giveUniqueNames(Project project,
|
|
final PsiElementFactory elementFactory,
|
|
PsiElement body,
|
|
Set<String> usedLocalNames, PsiVariable[] parameters) {
|
|
final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
|
|
final Map<PsiVariable, String> names = new HashMap<>();
|
|
for (PsiVariable parameter : parameters) {
|
|
String parameterName = parameter.getName();
|
|
String uniqueVariableName = UniqueNameGenerator.generateUniqueName(codeStyleManager.suggestUniqueVariableName(parameterName, parameter.getParent(), false), usedLocalNames);
|
|
if (!Comparing.equal(parameterName, uniqueVariableName)) {
|
|
names.put(parameter, uniqueVariableName);
|
|
}
|
|
}
|
|
|
|
if (names.isEmpty()) return;
|
|
|
|
final LinkedHashMap<PsiElement, PsiElement> replacements = new LinkedHashMap<>();
|
|
body.accept(new JavaRecursiveElementWalkingVisitor() {
|
|
@Override
|
|
public void visitVariable(PsiVariable variable) {
|
|
super.visitVariable(variable);
|
|
final String newName = names.get(variable);
|
|
if (newName != null) {
|
|
replacements.put(variable.getNameIdentifier(), elementFactory.createIdentifier(newName));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
|
super.visitReferenceExpression(expression);
|
|
final PsiElement resolve = expression.resolve();
|
|
if (resolve instanceof PsiVariable) {
|
|
final String newName = names.get(resolve);
|
|
if (newName != null) {
|
|
replacements.put(expression, elementFactory.createExpressionFromText(newName, expression));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
for (PsiElement psiElement : replacements.keySet()) {
|
|
psiElement.replace(replacements.get(psiElement));
|
|
}
|
|
}
|
|
|
|
private static String composeLambdaText(PsiMethod method) {
|
|
final StringBuilder buf = new StringBuilder();
|
|
final PsiParameter[] parameters = method.getParameterList().getParameters();
|
|
if (parameters.length != 1) {
|
|
buf.append("(");
|
|
}
|
|
buf.append(StringUtil.join(parameters, ReplaceWithLambdaFix::composeParameter, ","));
|
|
if (parameters.length != 1) {
|
|
buf.append(")");
|
|
}
|
|
buf.append("-> {}");
|
|
return buf.toString();
|
|
}
|
|
|
|
private static String composeParameter(PsiParameter parameter) {
|
|
return parameter.getName();
|
|
}
|
|
}
|
|
|
|
public static boolean functionalInterfaceMethodReferenced(PsiMethod psiMethod,
|
|
PsiAnonymousClass anonymClass,
|
|
PsiCallExpression callExpression) {
|
|
if (psiMethod != null && !psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
|
|
final PsiClass containingClass = psiMethod.getContainingClass();
|
|
if (containingClass != null &&
|
|
CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) {
|
|
return !(callExpression instanceof PsiMethodCallExpression && ((PsiMethodCallExpression)callExpression).getMethodExpression().isQualified());
|
|
}
|
|
|
|
if (callExpression instanceof PsiMethodCallExpression &&
|
|
((PsiMethodCallExpression)callExpression).getMethodExpression().isQualified()) {
|
|
return false;
|
|
}
|
|
|
|
if (InheritanceUtil.isInheritorOrSelf(anonymClass, containingClass, true) &&
|
|
!InheritanceUtil.hasEnclosingInstanceInScope(containingClass, anonymClass.getParent(), true, true)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static void restoreComments(Collection<? extends PsiComment> comments, PsiElement lambda) {
|
|
PsiElement anchor = PsiTreeUtil.getParentOfType(lambda, PsiStatement.class, PsiField.class);
|
|
if (anchor == null) {
|
|
anchor = lambda;
|
|
}
|
|
for (PsiComment comment : comments) {
|
|
anchor.getParent().addBefore(comment, anchor);
|
|
}
|
|
}
|
|
|
|
private static class ForbiddenRefsChecker extends JavaRecursiveElementWalkingVisitor {
|
|
private boolean myBodyContainsForbiddenRefs;
|
|
|
|
private final PsiMethod myMethod;
|
|
private final PsiAnonymousClass myAnonymClass;
|
|
|
|
ForbiddenRefsChecker(PsiMethod method, PsiAnonymousClass aClass) {
|
|
myMethod = method;
|
|
myAnonymClass = aClass;
|
|
}
|
|
|
|
@Override
|
|
public void visitMethodCallExpression(PsiMethodCallExpression methodCallExpression) {
|
|
if (myBodyContainsForbiddenRefs) return;
|
|
|
|
super.visitMethodCallExpression(methodCallExpression);
|
|
final PsiMethod psiMethod = methodCallExpression.resolveMethod();
|
|
if (psiMethod == myMethod ||
|
|
functionalInterfaceMethodReferenced(psiMethod, myAnonymClass, methodCallExpression)) {
|
|
myBodyContainsForbiddenRefs = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitThisExpression(PsiThisExpression expression) {
|
|
if (myBodyContainsForbiddenRefs) return;
|
|
|
|
if (expression.getQualifier() == null) {
|
|
myBodyContainsForbiddenRefs = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitSuperExpression(PsiSuperExpression expression) {
|
|
if (myBodyContainsForbiddenRefs) return;
|
|
|
|
if (expression.getQualifier() == null) {
|
|
myBodyContainsForbiddenRefs = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void visitVariable(PsiVariable variable) {
|
|
if (myBodyContainsForbiddenRefs) return;
|
|
|
|
super.visitVariable(variable);
|
|
}
|
|
|
|
@Override
|
|
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
|
if (myBodyContainsForbiddenRefs) return;
|
|
|
|
super.visitReferenceExpression(expression);
|
|
if (!(expression.getParent() instanceof PsiMethodCallExpression)) {
|
|
final PsiMember member = PsiTreeUtil.getParentOfType(myAnonymClass, PsiMember.class);
|
|
if (member instanceof PsiField || member instanceof PsiClassInitializer) {
|
|
final PsiElement resolved = expression.resolve();
|
|
final PsiClass memberContainingClass = member.getContainingClass();
|
|
if (resolved instanceof PsiField &&
|
|
memberContainingClass != null &&
|
|
PsiTreeUtil.isAncestor(((PsiField)resolved).getContainingClass(), memberContainingClass, false) &&
|
|
expression.getQualifierExpression() == null) {
|
|
final PsiExpression initializer = ((PsiField)resolved).getInitializer();
|
|
if (initializer == null ||
|
|
resolved == member ||
|
|
initializer.getTextOffset() > myAnonymClass.getTextOffset() && ((PsiField)resolved).hasModifierProperty(PsiModifier.STATIC) == member.hasModifierProperty(PsiModifier.STATIC)) {
|
|
myBodyContainsForbiddenRefs = true;
|
|
}
|
|
}
|
|
} else {
|
|
final PsiMethod method = PsiTreeUtil.getParentOfType(myAnonymClass, PsiMethod.class);
|
|
if (method != null && method.isConstructor()) {
|
|
final PsiElement resolved = expression.resolve();
|
|
if (resolved instanceof PsiField &&
|
|
((PsiField)resolved).hasModifierProperty(PsiModifier.FINAL) &&
|
|
((PsiField)resolved).getInitializer() == null &&
|
|
((PsiField)resolved).getContainingClass() == method.getContainingClass()) {
|
|
try {
|
|
final PsiCodeBlock constructorBody = method.getBody();
|
|
if (constructorBody != null) {
|
|
final ControlFlow flow = HighlightControlFlowUtil.getControlFlowNoConstantEvaluate(constructorBody);
|
|
final int startOffset = flow.getStartOffset(myAnonymClass);
|
|
final Collection<PsiVariable> writtenVariables = ControlFlowUtil.getWrittenVariables(flow, 0, startOffset, false);
|
|
if (!writtenVariables.contains(resolved)) {
|
|
myBodyContainsForbiddenRefs = true;
|
|
}
|
|
}
|
|
}
|
|
catch (AnalysisCanceledException e) {
|
|
myBodyContainsForbiddenRefs = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public boolean hasForbiddenRefs() {
|
|
return myBodyContainsForbiddenRefs;
|
|
}
|
|
}
|
|
}
|