| /* |
| * 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. |
| */ |
| |
| /* |
| * Checks and Highlights problems with classes |
| * User: cdr |
| * Date: Aug 19, 2002 |
| */ |
| package com.intellij.codeInsight.daemon.impl.analysis; |
| |
| import com.intellij.codeInsight.ClassUtil; |
| import com.intellij.codeInsight.ExceptionUtil; |
| import com.intellij.codeInsight.daemon.JavaErrorMessages; |
| import com.intellij.codeInsight.daemon.impl.HighlightInfo; |
| import com.intellij.codeInsight.daemon.impl.HighlightInfoType; |
| import com.intellij.codeInsight.daemon.impl.RefCountHolder; |
| import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; |
| import com.intellij.codeInsight.intention.QuickFixFactory; |
| import com.intellij.ide.highlighter.JavaFileType; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.util.*; |
| import com.intellij.refactoring.util.RefactoringChangeUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| |
| public class HighlightClassUtil { |
| private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); |
| |
| /** |
| * new ref(...) or new ref(..) { ... } where ref is abstract class |
| */ |
| @Nullable |
| static HighlightInfo checkAbstractInstantiation(@NotNull PsiJavaCodeReferenceElement ref, PsiElement resolved) { |
| PsiElement parent = ref.getParent(); |
| HighlightInfo highlightInfo = null; |
| if (parent instanceof PsiAnonymousClass |
| && parent.getParent() instanceof PsiNewExpression |
| && !PsiUtilCore.hasErrorElementChild(parent.getParent())) { |
| PsiAnonymousClass aClass = (PsiAnonymousClass)parent; |
| highlightInfo = checkClassWithAbstractMethods(aClass, ref.getTextRange()); |
| } |
| return highlightInfo; |
| } |
| |
| @Nullable |
| static HighlightInfo checkClassWithAbstractMethods(PsiClass aClass, TextRange range) { |
| return checkClassWithAbstractMethods(aClass, aClass, range); |
| } |
| |
| @Nullable |
| static HighlightInfo checkClassWithAbstractMethods(PsiClass aClass, PsiElement implementsFixElement, TextRange range) { |
| PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass); |
| |
| if (abstractMethod == null) { |
| return null; |
| } |
| |
| final PsiClass superClass = abstractMethod.getContainingClass(); |
| if (superClass == null) { |
| return null; |
| } |
| |
| String baseClassName = HighlightUtil.formatClass(aClass, false); |
| String methodName = JavaHighlightUtil.formatMethod(abstractMethod); |
| String message = JavaErrorMessages.message(aClass instanceof PsiEnumConstantInitializer || implementsFixElement instanceof PsiEnumConstant ? "enum.constant.should.implement.method" : "class.must.be.abstract", |
| baseClassName, |
| methodName, |
| HighlightUtil.formatClass(superClass, false)); |
| |
| HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); |
| final PsiMethod anyMethodToImplement = ClassUtil.getAnyMethodToImplement(aClass); |
| if (anyMethodToImplement != null) { |
| if (!anyMethodToImplement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || |
| JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, superClass)) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createImplementMethodsFix(implementsFixElement)); |
| } else { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(anyMethodToImplement, PsiModifier.PROTECTED, true, true)); |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(anyMethodToImplement, PsiModifier.PUBLIC, true, true)); |
| } |
| } |
| if (!(aClass instanceof PsiAnonymousClass) |
| && HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, aClass.getModifierList()) == null) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, true, false)); |
| } |
| return errorResult; |
| } |
| |
| @Nullable |
| static HighlightInfo checkClassMustBeAbstract(final PsiClass aClass, final TextRange textRange) { |
| if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) || aClass.getRBrace() == null || aClass.isEnum() && hasEnumConstants(aClass)) { |
| return null; |
| } |
| return checkClassWithAbstractMethods(aClass, textRange); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkInstantiationOfAbstractClass(PsiClass aClass, @NotNull PsiElement highlightElement) { |
| HighlightInfo errorResult = null; |
| if (aClass != null && aClass.hasModifierProperty(PsiModifier.ABSTRACT) |
| && (!(highlightElement instanceof PsiNewExpression) || !(((PsiNewExpression)highlightElement).getType() instanceof PsiArrayType))) { |
| String baseClassName = aClass.getName(); |
| String message = JavaErrorMessages.message("abstract.cannot.be.instantiated", baseClassName); |
| errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(highlightElement).descriptionAndTooltip(message).create(); |
| final PsiMethod anyAbstractMethod = ClassUtil.getAnyAbstractMethod(aClass); |
| if (!aClass.isInterface() && anyAbstractMethod == null) { |
| // suggest to make not abstract only if possible |
| QuickFixAction.registerQuickFixAction(errorResult, |
| QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, false, false)); |
| } |
| if (anyAbstractMethod != null && highlightElement instanceof PsiNewExpression && ((PsiNewExpression)highlightElement).getClassReference() != null) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createImplementAbstractClassMethodsFix(highlightElement)); |
| } |
| } |
| return errorResult; |
| } |
| |
| private static boolean hasEnumConstants(PsiClass aClass) { |
| PsiField[] fields = aClass.getFields(); |
| for (PsiField field : fields) { |
| if (field instanceof PsiEnumConstant) return true; |
| } |
| return false; |
| } |
| |
| @Nullable |
| static HighlightInfo checkDuplicateTopLevelClass(PsiClass aClass) { |
| if (!(aClass.getParent() instanceof PsiFile)) return null; |
| String qualifiedName = aClass.getQualifiedName(); |
| if (qualifiedName == null) return null; |
| int numOfClassesToFind = 2; |
| if (qualifiedName.contains("$")) { |
| qualifiedName = qualifiedName.replaceAll("\\$", "."); |
| numOfClassesToFind = 1; |
| } |
| PsiManager manager = aClass.getManager(); |
| Module module = ModuleUtilCore.findModuleForPsiElement(aClass); |
| if (module == null) return null; |
| |
| PsiClass[] classes = JavaPsiFacade.getInstance(aClass.getProject()).findClasses(qualifiedName, GlobalSearchScope.moduleScope(module)); |
| if (classes.length < numOfClassesToFind) return null; |
| String dupFileName = null; |
| for (PsiClass dupClass : classes) { |
| // do not use equals |
| if (dupClass != aClass) { |
| VirtualFile file = dupClass.getContainingFile().getVirtualFile(); |
| if (file != null && manager.isInProject(dupClass)) { |
| dupFileName = FileUtil.toSystemDependentName(file.getPath()); |
| break; |
| } |
| } |
| } |
| if (dupFileName == null) return null; |
| String message = JavaErrorMessages.message("duplicate.class.in.other.file", dupFileName); |
| TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(message).create(); |
| } |
| |
| @Nullable |
| static HighlightInfo checkDuplicateNestedClass(PsiClass aClass) { |
| if (aClass == null) return null; |
| PsiElement parent = aClass; |
| if (aClass.getParent() instanceof PsiDeclarationStatement) { |
| parent = aClass.getParent(); |
| } |
| String name = aClass.getName(); |
| if (name == null) return null; |
| boolean duplicateFound = false; |
| boolean checkSiblings = true; |
| while (parent != null) { |
| if (parent instanceof PsiFile) break; |
| PsiElement element = checkSiblings ? parent.getPrevSibling() : null; |
| if (element == null) { |
| element = parent.getParent(); |
| // JLS 14.3: |
| // The name of a local class C may not be redeclared |
| // as a local class of the directly enclosing method, constructor, or initializer block within the scope of C |
| // , or a compile-time error occurs. |
| // However, a local class declaration may be shadowed (?6.3.1) |
| // anywhere inside a class declaration nested within the local class declaration's scope. |
| if (element instanceof PsiMethod || element instanceof PsiClass || |
| element instanceof PsiCodeBlock && element.getParent() instanceof PsiClassInitializer) { |
| checkSiblings = false; |
| } |
| } |
| parent = element; |
| |
| if (element instanceof PsiDeclarationStatement) element = PsiTreeUtil.getChildOfType(element, PsiClass.class); |
| if (element instanceof PsiClass && name.equals(((PsiClass)element).getName())) { |
| duplicateFound = true; |
| break; |
| } |
| } |
| |
| if (duplicateFound) { |
| String message = JavaErrorMessages.message("duplicate.class", name); |
| TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(message).create(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkPublicClassInRightFile(PsiClass aClass) { |
| PsiFile containingFile = aClass.getContainingFile(); |
| if (aClass.getParent() != containingFile || !aClass.hasModifierProperty(PsiModifier.PUBLIC) || !(containingFile instanceof PsiJavaFile)) return null; |
| PsiJavaFile file = (PsiJavaFile)containingFile; |
| VirtualFile virtualFile = file.getVirtualFile(); |
| if (virtualFile == null || aClass.getName().equals(virtualFile.getNameWithoutExtension())) { |
| return null; |
| } |
| String message = JavaErrorMessages.message("public.class.should.be.named.after.file", aClass.getName()); |
| TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR). |
| range(aClass, range.getStartOffset(), range.getEndOffset()). |
| descriptionAndTooltip(message).create(); |
| PsiModifierList psiModifierList = aClass.getModifierList(); |
| QuickFixAction.registerQuickFixAction(errorResult, |
| QUICK_FIX_FACTORY.createModifierListFix(psiModifierList, PsiModifier.PUBLIC, false, false)); |
| PsiClass[] classes = file.getClasses(); |
| if (classes.length > 1) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMoveClassToSeparateFileFix(aClass)); |
| } |
| for (PsiClass otherClass : classes) { |
| if (!otherClass.getManager().areElementsEquivalent(otherClass, aClass) && |
| otherClass.hasModifierProperty(PsiModifier.PUBLIC) && |
| otherClass.getName().equals(virtualFile.getNameWithoutExtension())) { |
| return errorResult; |
| } |
| } |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createRenameFileFix(aClass.getName() + JavaFileType.DOT_DEFAULT_EXTENSION)); |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createRenameElementFix(aClass)); |
| return errorResult; |
| } |
| |
| @Nullable |
| static HighlightInfo checkClassAndPackageConflict(@NotNull PsiClass aClass) { |
| String name = aClass.getQualifiedName(); |
| |
| if (CommonClassNames.DEFAULT_PACKAGE.equals(name)) { |
| String message = JavaErrorMessages.message("class.clashes.with.package", name); |
| TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); |
| } |
| |
| PsiElement file = aClass.getParent(); |
| if (file instanceof PsiJavaFile && !((PsiJavaFile)file).getPackageName().isEmpty()) { |
| PsiElement directory = file.getParent(); |
| if (directory instanceof PsiDirectory) { |
| String simpleName = aClass.getName(); |
| PsiDirectory subDirectory = ((PsiDirectory)directory).findSubdirectory(simpleName); |
| if (subDirectory != null && simpleName.equals(subDirectory.getName())) { |
| String message = JavaErrorMessages.message("class.clashes.with.package", name); |
| TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private static HighlightInfo checkStaticFieldDeclarationInInnerClass(@NotNull PsiKeyword keyword) { |
| if (getEnclosingStaticClass(keyword, PsiField.class) == null) { |
| return null; |
| } |
| |
| PsiField field = (PsiField)keyword.getParent().getParent(); |
| if (PsiUtilCore.hasErrorElementChild(field) || PsiUtil.isCompileTimeConstant(field)) { |
| return null; |
| } |
| |
| String message = JavaErrorMessages.message("static.declaration.in.inner.class"); |
| HighlightInfo result = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); |
| |
| QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(field, PsiModifier.STATIC, false, false)); |
| |
| PsiClass aClass = field.getContainingClass(); |
| if (aClass != null) { |
| QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, true, false)); |
| } |
| |
| return result; |
| } |
| |
| @Nullable |
| private static HighlightInfo checkStaticMethodDeclarationInInnerClass(PsiKeyword keyword) { |
| if (getEnclosingStaticClass(keyword, PsiMethod.class) == null) { |
| return null; |
| } |
| PsiMethod method = (PsiMethod)keyword.getParent().getParent(); |
| if (PsiUtilCore.hasErrorElementChild(method)) return null; |
| String message = JavaErrorMessages.message("static.declaration.in.inner.class"); |
| HighlightInfo result = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.STATIC, false, false)); |
| QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix((PsiClass)keyword.getParent().getParent().getParent(), PsiModifier.STATIC, true, false)); |
| return result; |
| } |
| |
| @Nullable |
| private static HighlightInfo checkStaticInitializerDeclarationInInnerClass(PsiKeyword keyword) { |
| if (getEnclosingStaticClass(keyword, PsiClassInitializer.class) == null) { |
| return null; |
| } |
| PsiClassInitializer initializer = (PsiClassInitializer)keyword.getParent().getParent(); |
| if (PsiUtilCore.hasErrorElementChild(initializer)) return null; |
| String message = JavaErrorMessages.message("static.declaration.in.inner.class"); |
| HighlightInfo result = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(initializer, PsiModifier.STATIC, false, false)); |
| PsiClass owner = (PsiClass)keyword.getParent().getParent().getParent(); |
| QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createModifierListFix(owner, PsiModifier.STATIC, true, false)); |
| return result; |
| } |
| |
| private static PsiElement getEnclosingStaticClass(@NotNull PsiKeyword keyword, @NotNull Class<?> parentClass) { |
| return new PsiMatcherImpl(keyword) |
| .dot(PsiMatchers.hasText(PsiModifier.STATIC)) |
| .parent(PsiMatchers.hasClass(PsiModifierList.class)) |
| .parent(PsiMatchers.hasClass(parentClass)) |
| .parent(PsiMatchers.hasClass(PsiClass.class)) |
| .dot(JavaMatchers.hasModifier(PsiModifier.STATIC, false)) |
| .parent(PsiMatchers.hasClass(PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class)) |
| .getElement(); |
| } |
| |
| @Nullable |
| private static HighlightInfo checkStaticClassDeclarationInInnerClass(PsiKeyword keyword) { |
| // keyword points to 'class' or 'interface' or 'enum' |
| if (new PsiMatcherImpl(keyword) |
| .parent(PsiMatchers.hasClass(PsiClass.class)) |
| .dot(JavaMatchers.hasModifier(PsiModifier.STATIC, true)) |
| .parent(PsiMatchers.hasClass(PsiClass.class)) |
| .dot(JavaMatchers.hasModifier(PsiModifier.STATIC, false)) |
| .parent(PsiMatchers.hasClass(PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class)) |
| .getElement() == null) { |
| return null; |
| } |
| |
| PsiClass aClass = (PsiClass)keyword.getParent(); |
| if (PsiUtilCore.hasErrorElementChild(aClass)) { |
| return null; |
| } |
| |
| // highlight 'static' keyword if any, or class or interface if not |
| PsiElement context = null; |
| PsiModifierList modifierList = aClass.getModifierList(); |
| if (modifierList != null) { |
| for (PsiElement element : modifierList.getChildren()) { |
| if (Comparing.equal(element.getText(), PsiModifier.STATIC)) { |
| context = element; |
| break; |
| } |
| } |
| } |
| TextRange range = context != null ? context.getTextRange() : HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| String message = JavaErrorMessages.message("static.declaration.in.inner.class"); |
| HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); |
| if (context != keyword) { |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, false, false)); |
| } |
| PsiClass containingClass = aClass.getContainingClass(); |
| if (containingClass != null) { |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(containingClass, PsiModifier.STATIC, true, false)); |
| } |
| return info; |
| } |
| |
| @Nullable |
| static HighlightInfo checkStaticDeclarationInInnerClass(PsiKeyword keyword) { |
| HighlightInfo errorResult = checkStaticFieldDeclarationInInnerClass(keyword); |
| if (errorResult != null) return errorResult; |
| errorResult = checkStaticMethodDeclarationInInnerClass(keyword); |
| if (errorResult != null) return errorResult; |
| errorResult = checkStaticClassDeclarationInInnerClass(keyword); |
| if (errorResult != null) return errorResult; |
| errorResult = checkStaticInitializerDeclarationInInnerClass(keyword); |
| if (errorResult != null) return errorResult; |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkExtendsAllowed(PsiReferenceList list) { |
| if (list.getParent() instanceof PsiClass) { |
| PsiClass aClass = (PsiClass)list.getParent(); |
| if (aClass.isEnum()) { |
| boolean isExtends = list.equals(aClass.getExtendsList()); |
| if (isExtends) { |
| String description = JavaErrorMessages.message("extends.after.enum"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkImplementsAllowed(PsiReferenceList list) { |
| if (list.getParent() instanceof PsiClass) { |
| PsiClass aClass = (PsiClass)list.getParent(); |
| if (aClass.isInterface()) { |
| boolean isImplements = list.equals(aClass.getImplementsList()); |
| if (isImplements) { |
| String description = JavaErrorMessages.message("implements.after.interface"); |
| HighlightInfo result = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); |
| final PsiClassType[] referencedTypes = list.getReferencedTypes(); |
| if (referencedTypes.length > 0) { |
| QuickFixAction.registerQuickFixAction(result, QUICK_FIX_FACTORY.createChangeExtendsToImplementsFix(aClass, referencedTypes[0])); |
| } |
| return result; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkExtendsClassAndImplementsInterface(PsiReferenceList referenceList, |
| JavaResolveResult resolveResult, |
| PsiJavaCodeReferenceElement ref) { |
| PsiClass aClass = (PsiClass)referenceList.getParent(); |
| boolean isImplements = referenceList.equals(aClass.getImplementsList()); |
| boolean isInterface = aClass.isInterface(); |
| if (isInterface && isImplements) return null; |
| boolean mustBeInterface = isImplements || isInterface; |
| HighlightInfo errorResult = null; |
| PsiClass extendFrom = (PsiClass)resolveResult.getElement(); |
| if (extendFrom.isInterface() != mustBeInterface) { |
| String message = JavaErrorMessages.message(mustBeInterface ? "interface.expected" : "no.interface.expected"); |
| errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(message).create(); |
| PsiClassType type = |
| JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(ref); |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createChangeExtendsToImplementsFix(aClass, type)); |
| } |
| return errorResult; |
| } |
| |
| @Nullable |
| static HighlightInfo checkCannotInheritFromFinal(PsiClass superClass, PsiElement elementToHighlight) { |
| HighlightInfo errorResult = null; |
| if (superClass.hasModifierProperty(PsiModifier.FINAL) || superClass.isEnum()) { |
| String message = JavaErrorMessages.message("inheritance.from.final.class", superClass.getQualifiedName()); |
| errorResult = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(errorResult, |
| QUICK_FIX_FACTORY.createModifierListFix(superClass, PsiModifier.FINAL, false, false)); |
| } |
| return errorResult; |
| } |
| |
| @Nullable |
| static HighlightInfo checkAnonymousInheritFinal(PsiNewExpression expression) { |
| PsiAnonymousClass aClass = PsiTreeUtil.getChildOfType(expression, PsiAnonymousClass.class); |
| if (aClass == null) return null; |
| PsiClassType baseClassReference = aClass.getBaseClassType(); |
| PsiClass baseClass = baseClassReference.resolve(); |
| if (baseClass == null) return null; |
| return checkCannotInheritFromFinal(baseClass, aClass.getBaseClassReference()); |
| } |
| |
| @Nullable |
| private static String checkDefaultConstructorThrowsException(PsiMethod constructor, @NotNull PsiClassType[] handledExceptions) { |
| PsiClassType[] referencedTypes = constructor.getThrowsList().getReferencedTypes(); |
| List<PsiClassType> exceptions = new ArrayList<PsiClassType>(); |
| for (PsiClassType referencedType : referencedTypes) { |
| if (!ExceptionUtil.isUncheckedException(referencedType) && !ExceptionUtil.isHandledBy(referencedType, handledExceptions)) { |
| exceptions.add(referencedType); |
| } |
| } |
| if (!exceptions.isEmpty()) { |
| return HighlightUtil.getUnhandledExceptionsDescriptor(exceptions); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkClassDoesNotCallSuperConstructorOrHandleExceptions(@NotNull PsiClass aClass, |
| RefCountHolder refCountHolder, |
| @NotNull PsiResolveHelper resolveHelper) { |
| if (aClass.isEnum()) return null; |
| // check only no-ctr classes. Problem with specific constructor will be highlighted inside it |
| if (aClass.getConstructors().length != 0) return null; |
| // find no-args base class ctr |
| TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| return checkBaseClassDefaultConstructorProblem(aClass, refCountHolder, resolveHelper, textRange, PsiClassType.EMPTY_ARRAY); |
| } |
| |
| public static HighlightInfo checkBaseClassDefaultConstructorProblem(@NotNull PsiClass aClass, |
| RefCountHolder refCountHolder, |
| @NotNull PsiResolveHelper resolveHelper, |
| @NotNull TextRange range, |
| @NotNull PsiClassType[] handledExceptions) { |
| if (aClass instanceof PsiAnonymousClass) return null; |
| PsiClass baseClass = aClass.getSuperClass(); |
| if (baseClass == null) return null; |
| PsiMethod[] constructors = baseClass.getConstructors(); |
| if (constructors.length == 0) return null; |
| |
| for (PsiMethod constructor : constructors) { |
| if (resolveHelper.isAccessible(constructor, aClass, null)) { |
| if (constructor.getParameterList().getParametersCount() == 0 || |
| constructor.getParameterList().getParametersCount() == 1 && constructor.isVarArgs() |
| ) { |
| // it is an error if base ctr throws exceptions |
| String description = checkDefaultConstructorThrowsException(constructor, handledExceptions); |
| if (description != null) { |
| HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createCreateConstructorMatchingSuperFix(aClass)); |
| return info; |
| } |
| if (refCountHolder != null) { |
| refCountHolder.registerLocallyReferenced(constructor); |
| } |
| return null; |
| } |
| } |
| } |
| |
| String description = JavaErrorMessages.message("no.default.constructor.available", HighlightUtil.formatClass(baseClass)); |
| |
| HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createCreateConstructorMatchingSuperFix(aClass)); |
| |
| return info; |
| } |
| |
| @Nullable |
| static HighlightInfo checkInterfaceCannotBeLocal(PsiClass aClass) { |
| if (PsiUtil.isLocalClass(aClass)) { |
| TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| String description = JavaErrorMessages.message("interface.cannot.be.local"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description).create(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkCyclicInheritance(PsiClass aClass) { |
| PsiClass circularClass = getCircularClass(aClass, new HashSet<PsiClass>()); |
| if (circularClass != null) { |
| String description = JavaErrorMessages.message("cyclic.inheritance", HighlightUtil.formatClass(circularClass)); |
| TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(description).create(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static PsiClass getCircularClass(PsiClass aClass, Collection<PsiClass> usedClasses) { |
| if (usedClasses.contains(aClass)) { |
| return aClass; |
| } |
| try { |
| usedClasses.add(aClass); |
| PsiClass[] superTypes = aClass.getSupers(); |
| for (PsiElement superType : superTypes) { |
| while (superType instanceof PsiClass) { |
| if (!CommonClassNames.JAVA_LANG_OBJECT.equals(((PsiClass)superType).getQualifiedName())) { |
| PsiClass circularClass = getCircularClass((PsiClass)superType, usedClasses); |
| if (circularClass != null) return circularClass; |
| } |
| // check class qualifier |
| superType = superType.getParent(); |
| } |
| } |
| } |
| finally { |
| usedClasses.remove(aClass); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkExtendsDuplicate(PsiJavaCodeReferenceElement element, PsiElement resolved, @NotNull PsiFile containingFile) { |
| if (!(element.getParent() instanceof PsiReferenceList)) return null; |
| PsiReferenceList list = (PsiReferenceList)element.getParent(); |
| if (!(list.getParent() instanceof PsiClass)) return null; |
| if (!(resolved instanceof PsiClass)) return null; |
| PsiClass aClass = (PsiClass)resolved; |
| PsiClassType[] referencedTypes = list.getReferencedTypes(); |
| int dupCount = 0; |
| PsiManager manager = containingFile.getManager(); |
| for (PsiClassType referencedType : referencedTypes) { |
| PsiClass resolvedElement = referencedType.resolve(); |
| if (resolvedElement != null && manager.areElementsEquivalent(resolvedElement, aClass)) { |
| dupCount++; |
| } |
| } |
| if (dupCount > 1) { |
| String description = JavaErrorMessages.message("duplicate.class", HighlightUtil.formatClass(aClass)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkClassAlreadyImported(PsiClass aClass, PsiElement elementToHighlight) { |
| PsiFile file = aClass.getContainingFile(); |
| if (!(file instanceof PsiJavaFile)) return null; |
| PsiJavaFile javaFile = (PsiJavaFile)file; |
| // check only top-level classes conflicts |
| if (aClass.getParent() != javaFile) return null; |
| PsiImportList importList = javaFile.getImportList(); |
| if (importList == null) return null; |
| PsiImportStatementBase[] importStatements = importList.getAllImportStatements(); |
| for (PsiImportStatementBase importStatement : importStatements) { |
| if (importStatement.isOnDemand()) continue; |
| PsiElement resolved = importStatement.resolve(); |
| if (resolved instanceof PsiClass && !resolved.equals(aClass) && Comparing.equal(aClass.getName(), ((PsiClass)resolved).getName(), true)) { |
| String description = JavaErrorMessages.message("class.already.imported", HighlightUtil.formatClass(aClass, false)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(description).create(); |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkClassExtendsOnlyOneClass(PsiReferenceList list) { |
| PsiClassType[] referencedTypes = list.getReferencedTypes(); |
| PsiElement parent = list.getParent(); |
| if (!(parent instanceof PsiClass)) return null; |
| |
| PsiClass aClass = (PsiClass)parent; |
| if (!aClass.isInterface() |
| && referencedTypes.length > 1 |
| && aClass.getExtendsList() == list) { |
| String description = JavaErrorMessages.message("class.cannot.extend.multiple.classes"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(description).create(); |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkThingNotAllowedInInterface(PsiElement element, PsiClass aClass) { |
| if (aClass == null || !aClass.isInterface()) return null; |
| String description = JavaErrorMessages.message("not.allowed.in.interface"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkQualifiedNew(PsiNewExpression expression, PsiType type, PsiClass aClass) { |
| PsiExpression qualifier = expression.getQualifier(); |
| if (qualifier == null) return null; |
| if (type instanceof PsiArrayType) { |
| String description = JavaErrorMessages.message("invalid.qualified.new"); |
| HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRemoveNewQualifierFix(expression, null)); |
| return info; |
| } |
| HighlightInfo info = null; |
| if (aClass != null) { |
| if (aClass.hasModifierProperty(PsiModifier.STATIC)) { |
| String description = JavaErrorMessages.message("qualified.new.of.static.class"); |
| info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| if (!aClass.isEnum()) { |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, false, false)); |
| } |
| |
| } else if (aClass instanceof PsiAnonymousClass) { |
| final PsiClass baseClass = PsiUtil.resolveClassInType(((PsiAnonymousClass)aClass).getBaseClassType()); |
| if (baseClass != null && baseClass.isInterface()) { |
| info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression) |
| .descriptionAndTooltip("Anonymous class implements interface; cannot have qualifier for new").create(); |
| } |
| } |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRemoveNewQualifierFix(expression, aClass)); |
| } |
| return info; |
| } |
| |
| |
| /** |
| * class c extends foreign.inner {} |
| * |
| * @param extendRef points to the class in the extends list |
| * @param resolved extendRef resolved |
| */ |
| @Nullable |
| public static HighlightInfo checkClassExtendsForeignInnerClass(final PsiJavaCodeReferenceElement extendRef, final PsiElement resolved) { |
| PsiElement parent = extendRef.getParent(); |
| if (!(parent instanceof PsiReferenceList)) { |
| return null; |
| } |
| PsiElement grand = parent.getParent(); |
| if (!(grand instanceof PsiClass)) { |
| return null; |
| } |
| final PsiClass aClass = (PsiClass)grand; |
| final PsiClass containerClass; |
| if (aClass instanceof PsiTypeParameter) { |
| final PsiTypeParameterListOwner owner = ((PsiTypeParameter)aClass).getOwner(); |
| if (!(owner instanceof PsiClass)) { |
| return null; |
| } |
| containerClass = (PsiClass)owner; |
| } else { |
| containerClass = aClass; |
| } |
| if (aClass.getExtendsList() != parent && aClass.getImplementsList() != parent) { |
| return null; |
| } |
| if (!(resolved instanceof PsiClass)) { |
| String description = JavaErrorMessages.message("class.name.expected"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef).descriptionAndTooltip(description).create(); |
| } |
| final HighlightInfo[] infos = new HighlightInfo[1]; |
| extendRef.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitElement(PsiElement element) { |
| if (infos[0] != null) return; |
| super.visitElement(element); |
| } |
| |
| @Override |
| public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { |
| super.visitReferenceElement(reference); |
| final PsiElement resolve = reference.resolve(); |
| if (resolve instanceof PsiClass) { |
| final PsiClass base = (PsiClass)resolve; |
| final PsiClass baseClass = base.getContainingClass(); |
| if (baseClass != null && base.hasModifierProperty(PsiModifier.PRIVATE) && baseClass == containerClass) { |
| String description = JavaErrorMessages.message("private.symbol", |
| HighlightUtil.formatClass(base), |
| HighlightUtil.formatClass(baseClass)); |
| infos[0] = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef).descriptionAndTooltip(description).create(); |
| return; |
| } |
| |
| // must be inner class |
| if (!PsiUtil.isInnerClass(base)) return; |
| |
| if (resolve == resolved && baseClass != null && (!PsiTreeUtil.isAncestor(baseClass, extendRef, true) || aClass.hasModifierProperty(PsiModifier.STATIC)) && |
| !InheritanceUtil.hasEnclosingInstanceInScope(baseClass, extendRef, !aClass.hasModifierProperty(PsiModifier.STATIC), true) && !qualifiedNewCalledInConstructors(aClass, baseClass)) { |
| String description = JavaErrorMessages.message("no.enclosing.instance.in.scope", HighlightUtil.formatClass(baseClass)); |
| infos[0] = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(extendRef).descriptionAndTooltip(description).create(); |
| } |
| } |
| } |
| }); |
| |
| return infos[0]; |
| } |
| |
| private static boolean qualifiedNewCalledInConstructors(final PsiClass aClass, final PsiClass baseClass) { |
| PsiMethod[] constructors = aClass.getConstructors(); |
| if (constructors.length == 0) return false; |
| for (PsiMethod constructor : constructors) { |
| PsiCodeBlock body = constructor.getBody(); |
| if (body == null) return false; |
| PsiStatement[] statements = body.getStatements(); |
| if (statements.length == 0) return false; |
| PsiStatement firstStatement = statements[0]; |
| if (!(firstStatement instanceof PsiExpressionStatement)) return false; |
| PsiExpression expression = ((PsiExpressionStatement)firstStatement).getExpression(); |
| if (!RefactoringChangeUtil.isSuperOrThisMethodCall(expression)) return false; |
| PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; |
| if (PsiKeyword.THIS.equals(methodCallExpression.getMethodExpression().getReferenceName())) continue; |
| PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression(); |
| PsiExpression qualifierExpression = PsiUtil.skipParenthesizedExprDown(referenceExpression.getQualifierExpression()); |
| if (!(qualifierExpression instanceof PsiReferenceExpression) && !(qualifierExpression instanceof PsiCallExpression)) return false; |
| PsiType type = qualifierExpression.getType(); |
| if (!(type instanceof PsiClassType)) return false; |
| PsiClass resolved = ((PsiClassType)type).resolve(); |
| if (resolved != baseClass) return false; |
| } |
| return true; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkCreateInnerClassFromStaticContext(PsiNewExpression expression, PsiType type, PsiClass aClass) { |
| if (type == null || type instanceof PsiArrayType || type instanceof PsiPrimitiveType) return null; |
| if (aClass == null) return null; |
| if (aClass instanceof PsiAnonymousClass) { |
| aClass = ((PsiAnonymousClass)aClass).getBaseClassType().resolve(); |
| if (aClass == null) return null; |
| } |
| |
| PsiExpression qualifier = expression.getQualifier(); |
| return checkCreateInnerClassFromStaticContext(expression, qualifier, aClass); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkCreateInnerClassFromStaticContext(PsiElement element, |
| @Nullable PsiExpression qualifier, |
| PsiClass aClass) { |
| PsiElement placeToSearchEnclosingFrom; |
| if (qualifier != null) { |
| PsiType qType = qualifier.getType(); |
| placeToSearchEnclosingFrom = PsiUtil.resolveClassInType(qType); |
| } |
| else { |
| placeToSearchEnclosingFrom = element; |
| } |
| return checkCreateInnerClassFromStaticContext(element, placeToSearchEnclosingFrom, aClass); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkCreateInnerClassFromStaticContext(PsiElement element, |
| PsiElement placeToSearchEnclosingFrom, |
| PsiClass aClass) { |
| if (aClass == null || !PsiUtil.isInnerClass(aClass)) return null; |
| PsiClass outerClass = aClass.getContainingClass(); |
| if (outerClass == null) return null; |
| |
| if (outerClass instanceof PsiSyntheticClass || InheritanceUtil.hasEnclosingInstanceInScope(outerClass, placeToSearchEnclosingFrom, true, |
| false)) return null; |
| return reportIllegalEnclosingUsage(placeToSearchEnclosingFrom, aClass, outerClass, element); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkSuperQualifierType(@NotNull Project project, @NotNull PsiMethodCallExpression superCall) { |
| if (!RefactoringChangeUtil.isSuperMethodCall(superCall)) return null; |
| PsiMethod ctr = PsiTreeUtil.getParentOfType(superCall, PsiMethod.class, true, PsiMember.class); |
| if (ctr == null) return null; |
| final PsiClass aClass = ctr.getContainingClass(); |
| if (aClass == null) return null; |
| PsiClass targetClass = aClass.getSuperClass(); |
| if (targetClass == null) return null; |
| PsiExpression qualifier = superCall.getMethodExpression().getQualifierExpression(); |
| if (qualifier != null) { |
| if (PsiUtil.isInnerClass(targetClass)) { |
| PsiClass outerClass = targetClass.getContainingClass(); |
| if (outerClass != null) { |
| PsiClassType outerType = JavaPsiFacade.getInstance(project).getElementFactory().createType(outerClass); |
| return HighlightUtil.checkAssignability(outerType, null, qualifier, qualifier); |
| } |
| } else { |
| String description = "'" + HighlightUtil.formatClass(targetClass) + "' is not an inner class"; |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create(); |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo reportIllegalEnclosingUsage(PsiElement place, |
| @Nullable PsiClass aClass, |
| PsiClass outerClass, |
| PsiElement elementToHighlight) { |
| if (outerClass != null && !PsiTreeUtil.isContextAncestor(outerClass, place, false)) { |
| String description = JavaErrorMessages.message("is.not.an.enclosing.class", HighlightUtil.formatClass(outerClass)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(description).create(); |
| } |
| PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, outerClass); |
| if (staticParent != null) { |
| String element = outerClass == null ? "" : HighlightUtil.formatClass(outerClass) + "." + |
| (place instanceof PsiSuperExpression ? PsiKeyword.SUPER : PsiKeyword.THIS); |
| String description = JavaErrorMessages.message("cannot.be.referenced.from.static.context", element); |
| HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(elementToHighlight).descriptionAndTooltip(description).create(); |
| // make context not static or referenced class static |
| QuickFixAction.registerQuickFixAction(highlightInfo, |
| QUICK_FIX_FACTORY.createModifierListFix(staticParent, PsiModifier.STATIC, false, false)); |
| if (aClass != null && HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, aClass.getModifierList()) == null) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, |
| QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, true, false)); |
| } |
| return highlightInfo; |
| } |
| return null; |
| } |
| } |