good code is red (in presence of raw overriding)
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / daemon / impl / analysis / HighlightClassUtil.java
blob1cbbbf85cba42ecd6cd54efdb6701bde9d4e5862
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Checks and Highlights problems with classes
19 * User: cdr
20 * Date: Aug 19, 2002
22 package com.intellij.codeInsight.daemon.impl.analysis;
24 import com.intellij.codeInsight.ClassUtil;
25 import com.intellij.codeInsight.ExceptionUtil;
26 import com.intellij.codeInsight.daemon.JavaErrorMessages;
27 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
28 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
29 import com.intellij.codeInsight.daemon.impl.RefCountHolder;
30 import com.intellij.codeInsight.daemon.impl.quickfix.*;
31 import com.intellij.codeInsight.intention.IntentionAction;
32 import com.intellij.codeInsight.intention.QuickFixFactory;
33 import com.intellij.openapi.application.ApplicationManager;
34 import com.intellij.openapi.fileTypes.StdFileTypes;
35 import com.intellij.openapi.module.Module;
36 import com.intellij.openapi.module.ModuleUtil;
37 import com.intellij.openapi.util.Comparing;
38 import com.intellij.openapi.util.TextRange;
39 import com.intellij.openapi.util.io.FileUtil;
40 import com.intellij.openapi.vfs.VirtualFile;
41 import com.intellij.psi.*;
42 import com.intellij.psi.impl.source.jsp.jspJava.JspClass;
43 import com.intellij.psi.search.GlobalSearchScope;
44 import com.intellij.psi.util.*;
45 import org.jetbrains.annotations.NotNull;
47 import java.util.ArrayList;
48 import java.util.Collection;
49 import java.util.HashSet;
50 import java.util.List;
52 public class HighlightClassUtil {
53 public static final String INTERFACE_EXPECTED = JavaErrorMessages.message("interface.expected");
54 public static final String CLASS_EXPECTED = JavaErrorMessages.message("class.expected");
55 private static final String STATIC_DECLARATION_IN_INNER_CLASS = JavaErrorMessages.message("static.declaration.in.inner.class");
56 private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance();
58 /**
59 * new ref(...) or new ref(..) { ... } where ref is abstract class
61 static HighlightInfo checkAbstractInstantiation(PsiJavaCodeReferenceElement ref) {
62 PsiElement parent = ref.getParent();
63 HighlightInfo highlightInfo = null;
64 if (parent instanceof PsiNewExpression && !PsiUtilBase.hasErrorElementChild(parent)) {
65 if (((PsiNewExpression)parent).getType() instanceof PsiArrayType) return null;
66 PsiElement refElement = ref.resolve();
67 if (refElement instanceof PsiClass) {
68 highlightInfo = checkInstantiationOfAbstractClass((PsiClass)refElement, ref);
71 else if (parent instanceof PsiAnonymousClass
72 && parent.getParent() instanceof PsiNewExpression
73 && !PsiUtilBase.hasErrorElementChild(parent.getParent())) {
74 PsiAnonymousClass aClass = (PsiAnonymousClass)parent;
75 highlightInfo = checkClassWithAbstractMethods(aClass, ref.getTextRange());
77 return highlightInfo;
80 static HighlightInfo checkClassWithAbstractMethods(PsiClass aClass, TextRange textRange) {
81 PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(aClass);
83 if (abstractMethod == null || abstractMethod.getContainingClass() == null) {
84 return null;
86 String baseClassName = HighlightUtil.formatClass(aClass, false);
87 String methodName = HighlightUtil.formatMethod(abstractMethod);
88 String message = JavaErrorMessages.message("class.must.be.abstract",
89 baseClassName,
90 methodName,
91 HighlightUtil.formatClass(abstractMethod.getContainingClass(), false));
93 HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, message);
94 if (ClassUtil.getAnyMethodToImplement(aClass) != null) {
95 QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createImplementMethodsFix(aClass));
97 if (!(aClass instanceof PsiAnonymousClass)
98 && HighlightUtil.getIncompatibleModifier(PsiModifier.ABSTRACT, aClass.getModifierList()) == null) {
99 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, true, false);
100 QuickFixAction.registerQuickFixAction(errorResult, fix);
102 return errorResult;
105 static HighlightInfo checkClassMustBeAbstract(final PsiClass aClass, final TextRange textRange) {
106 if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) || aClass.getRBrace() == null
107 || aClass.isEnum() && hasEnumConstants(aClass)
109 return null;
111 return checkClassWithAbstractMethods(aClass, textRange);
115 public static HighlightInfo checkInstantiationOfAbstractClass(PsiClass aClass, PsiElement highlighElement) {
116 HighlightInfo errorResult = null;
117 if (aClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
118 String baseClassName = aClass.getName();
119 String message = JavaErrorMessages.message("abstract.cannot.be.instantiated", baseClassName);
120 errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, highlighElement, message);
121 if (!aClass.isInterface() && ClassUtil.getAnyAbstractMethod(aClass) == null) {
122 // suggest to make not abstract only if possible
123 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.ABSTRACT, false, false);
124 QuickFixAction.registerQuickFixAction(errorResult, fix);
127 return errorResult;
130 private static boolean hasEnumConstants(PsiClass aClass) {
131 PsiField[] fields = aClass.getFields();
132 for (PsiField field : fields) {
133 if (field instanceof PsiEnumConstant) return true;
135 return false;
139 static HighlightInfo checkDuplicateTopLevelClass(PsiClass aClass) {
140 if (!(aClass.getParent() instanceof PsiFile)) return null;
141 String qualifiedName = aClass.getQualifiedName();
142 if (qualifiedName == null) return null;
143 int numOfClassesToFind = 2;
144 if (qualifiedName.contains("$")) {
145 qualifiedName = qualifiedName.replaceAll("\\$", ".");
146 numOfClassesToFind = 1;
148 PsiManager manager = aClass.getManager();
149 Module module = ModuleUtil.findModuleForPsiElement(aClass);
150 if (module == null) return null;
152 PsiClass[] classes = JavaPsiFacade.getInstance(aClass.getProject()).findClasses(qualifiedName, GlobalSearchScope.moduleScope(module));
153 if (classes.length < numOfClassesToFind) return null;
154 String dupFileName = null;
155 for (PsiClass dupClass : classes) {
156 // do not use equals
157 if (dupClass != aClass) {
158 VirtualFile file = dupClass.getContainingFile().getVirtualFile();
159 if (file != null && manager.isInProject(dupClass)) {
160 dupFileName = FileUtil.toSystemDependentName(file.getPath());
161 break;
165 if (dupFileName == null) return null;
166 String message = JavaErrorMessages.message("duplicate.class.in.other.file", dupFileName);
167 TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
169 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, message);
173 static HighlightInfo checkDuplicateNestedClass(PsiClass aClass) {
174 if (aClass == null) return null;
175 PsiElement parent = aClass;
176 if (aClass.getParent() instanceof PsiDeclarationStatement) {
177 parent = aClass.getParent();
179 String name = aClass.getName();
180 if (name == null) return null;
181 boolean duplicateFound = false;
182 boolean checkSiblings = true;
183 while (parent != null) {
184 if (parent instanceof PsiFile) break;
185 PsiElement element = checkSiblings ? parent.getPrevSibling() : null;
186 if (element == null) {
187 element = parent.getParent();
188 // JLS 14.3:
189 // The name of a local class C may not be redeclared
190 // as a local class of the directly enclosing method, constructor, or initializer block within the scope of C
191 // , or a compile-time error occurs.
192 // However, a local class declaration may be shadowed (?6.3.1)
193 // anywhere inside a class declaration nested within the local class declaration's scope.
194 if (element instanceof PsiMethod || element instanceof PsiClass) {
195 checkSiblings = false;
198 parent = element;
200 if (element instanceof PsiDeclarationStatement) element = PsiTreeUtil.getChildOfType(element, PsiClass.class);
201 if (element instanceof PsiClass && name.equals(((PsiClass)element).getName())) {
202 duplicateFound = true;
203 break;
207 if (duplicateFound) {
208 String message = JavaErrorMessages.message("duplicate.class", name);
209 TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
210 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, message);
212 return null;
217 static HighlightInfo checkPublicClassInRightFile(PsiKeyword keyword, PsiModifierList psiModifierList) {
218 // todo most testcase classes located in wrong files
219 if (ApplicationManager.getApplication().isUnitTestMode()) return null;
220 if (new PsiMatcherImpl(keyword)
221 .dot(PsiMatchers.hasText(PsiModifier.PUBLIC))
222 .parent(PsiMatchers.hasClass(PsiModifierList.class))
223 .parent(PsiMatchers.hasClass(PsiClass.class))
224 .parent(PsiMatchers.hasClass(PsiJavaFile.class))
225 .getElement() == null) {
226 return null;
228 PsiClass aClass = (PsiClass)keyword.getParent().getParent();
229 PsiJavaFile file = (PsiJavaFile)aClass.getContainingFile();
230 VirtualFile virtualFile = file.getVirtualFile();
231 HighlightInfo errorResult = null;
232 if (virtualFile != null && !aClass.getName().equals(virtualFile.getNameWithoutExtension()) && aClass.getNameIdentifier() != null) {
233 String message = JavaErrorMessages.message("public.class.should.be.named.after.file", aClass.getName());
235 errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, aClass.getNameIdentifier(), message);
236 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(psiModifierList, PsiModifier.PUBLIC, false, false);
237 QuickFixAction.registerQuickFixAction(errorResult, fix);
238 PsiClass[] classes = file.getClasses();
239 if (classes.length > 1) {
240 QuickFixAction.registerQuickFixAction(errorResult, new MoveClassToSeparateFileFix(aClass));
242 for (PsiClass otherClass : classes) {
243 if (!otherClass.getManager().areElementsEquivalent(otherClass, aClass) && otherClass.hasModifierProperty(PsiModifier.PUBLIC)
244 && otherClass.getName().equals(virtualFile.getNameWithoutExtension())) {
245 return errorResult;
248 QuickFixAction.registerQuickFixAction(errorResult, new RenameFileFix(aClass.getName() + "." + StdFileTypes.JAVA.getDefaultExtension()));
249 QuickFixAction.registerQuickFixAction(errorResult, new RenameElementFix(aClass));
251 return errorResult;
255 private static HighlightInfo checkStaticFieldDeclarationInInnerClass(PsiKeyword keyword) {
256 if (getEnclosingStaticClass(keyword, PsiField.class) == null) {
257 return null;
259 PsiField field = (PsiField)keyword.getParent().getParent();
260 if (PsiUtilBase.hasErrorElementChild(field)) return null;
261 // except compile time constants
262 if (PsiUtil.isCompileTimeConstant(field)) {
263 return null;
265 HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
266 keyword,
267 STATIC_DECLARATION_IN_INNER_CLASS);
268 IntentionAction fix1 = QUICK_FIX_FACTORY.createModifierListFix(field, PsiModifier.STATIC, false, false);
269 QuickFixAction.registerQuickFixAction(errorResult, fix1);
270 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(field.getContainingClass(), PsiModifier.STATIC, true, false);
271 QuickFixAction.registerQuickFixAction(errorResult, fix);
272 return errorResult;
276 private static HighlightInfo checkStaticMethodDeclarationInInnerClass(PsiKeyword keyword) {
277 if (getEnclosingStaticClass(keyword, PsiMethod.class) == null) {
278 return null;
280 PsiMethod method = (PsiMethod)keyword.getParent().getParent();
281 if (PsiUtilBase.hasErrorElementChild(method)) return null;
282 HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
283 keyword,
284 STATIC_DECLARATION_IN_INNER_CLASS);
285 IntentionAction fix1 = QUICK_FIX_FACTORY.createModifierListFix(method, PsiModifier.STATIC, false, false);
286 QuickFixAction.registerQuickFixAction(errorResult, fix1);
287 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix((PsiClass)keyword.getParent().getParent().getParent(), PsiModifier.STATIC, true, false);
288 QuickFixAction.registerQuickFixAction(errorResult, fix);
289 return errorResult;
293 private static HighlightInfo checkStaticInitializerDeclarationInInnerClass(PsiKeyword keyword) {
294 if (getEnclosingStaticClass(keyword, PsiClassInitializer.class) == null) {
295 return null;
297 PsiClassInitializer initializer = (PsiClassInitializer)keyword.getParent().getParent();
298 if (PsiUtilBase.hasErrorElementChild(initializer)) return null;
299 HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
300 keyword,
301 STATIC_DECLARATION_IN_INNER_CLASS);
302 IntentionAction fix1 = QUICK_FIX_FACTORY.createModifierListFix(initializer, PsiModifier.STATIC, false, false);
303 QuickFixAction.registerQuickFixAction(errorResult, fix1);
304 PsiClass owner = (PsiClass)keyword.getParent().getParent().getParent();
305 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(owner, PsiModifier.STATIC, true, false);
306 QuickFixAction.registerQuickFixAction(errorResult, fix);
307 return errorResult;
310 private static PsiElement getEnclosingStaticClass(PsiKeyword keyword, Class<?> parentClass) {
311 return new PsiMatcherImpl(keyword)
312 .dot(PsiMatchers.hasText(PsiModifier.STATIC))
313 .parent(PsiMatchers.hasClass(PsiModifierList.class))
314 .parent(PsiMatchers.hasClass(parentClass))
315 .parent(PsiMatchers.hasClass(PsiClass.class))
316 .dot(PsiMatchers.hasModifier(PsiModifier.STATIC, false))
317 .parent(PsiMatchers.hasClass(new Class[]{PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class}))
318 .getElement();
322 private static HighlightInfo checkStaticClassDeclarationInInnerClass(PsiKeyword keyword) {
323 // keyword points to 'class' or 'interface' or 'enum'
324 if (new PsiMatcherImpl(keyword)
325 .parent(PsiMatchers.hasClass(PsiClass.class))
326 .dot(PsiMatchers.hasModifier(PsiModifier.STATIC, true))
327 .parent(PsiMatchers.hasClass(PsiClass.class))
328 .dot(PsiMatchers.hasModifier(PsiModifier.STATIC, false))
329 .parent(PsiMatchers.hasClass(new Class[]{PsiClass.class, PsiDeclarationStatement.class, PsiNewExpression.class, PsiEnumConstant.class}))
330 .getElement() == null) {
331 return null;
333 PsiClass aClass = (PsiClass)keyword.getParent();
334 if (PsiUtilBase.hasErrorElementChild(aClass)) return null;
335 // highlight 'static' keyword if any, or class or interface if not
336 PsiElement context = null;
337 PsiModifierList modifierList = aClass.getModifierList();
338 PsiElement[] children = modifierList.getChildren();
339 for (PsiElement element : children) {
340 if (Comparing.equal(element.getText(), PsiModifier.STATIC)) {
341 context = element;
342 break;
345 TextRange textRange = context == null ? null : context.getTextRange();
346 if (textRange == null) {
347 textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
349 HighlightInfo errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, STATIC_DECLARATION_IN_INNER_CLASS);
350 if (context != keyword) {
351 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, false, false);
352 QuickFixAction.registerQuickFixAction(errorResult, fix);
354 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass.getContainingClass(), PsiModifier.STATIC, true, false);
355 QuickFixAction.registerQuickFixAction(errorResult, fix);
356 return errorResult;
360 static HighlightInfo checkStaticDeclarationInInnerClass(PsiKeyword keyword) {
361 HighlightInfo errorResult = checkStaticFieldDeclarationInInnerClass(keyword);
362 if (errorResult != null) return errorResult;
363 errorResult = checkStaticMethodDeclarationInInnerClass(keyword);
364 if (errorResult != null) return errorResult;
365 errorResult = checkStaticClassDeclarationInInnerClass(keyword);
366 if (errorResult != null) return errorResult;
367 errorResult = checkStaticInitializerDeclarationInInnerClass(keyword);
368 if (errorResult != null) return errorResult;
369 return null;
373 static HighlightInfo checkImplementsAllowed(PsiReferenceList list) {
374 if (list.getParent() instanceof PsiClass) {
375 PsiClass aClass = (PsiClass)list.getParent();
376 if (aClass.isInterface()) {
377 boolean isImplements = list.equals(aClass.getImplementsList());
378 if (isImplements) {
379 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, list, JavaErrorMessages.message("implements.after.interface"));
383 return null;
387 static HighlightInfo checkExtendsClassAndImplementsInterface(PsiReferenceList referenceList,
388 JavaResolveResult resolveResult,
389 PsiJavaCodeReferenceElement context) {
390 PsiClass aClass = (PsiClass)referenceList.getParent();
391 boolean isImplements = referenceList.equals(aClass.getImplementsList());
392 boolean isInterface = aClass.isInterface();
393 if (isInterface && isImplements) return null;
394 boolean mustBeInterface = isImplements || isInterface;
395 HighlightInfo errorResult = null;
396 PsiClass extendFrom = (PsiClass)resolveResult.getElement();
397 if (extendFrom.isInterface() != mustBeInterface) {
398 errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
399 context,
400 mustBeInterface ? INTERFACE_EXPECTED : CLASS_EXPECTED);
401 PsiClassType type =
402 JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(extendFrom, resolveResult.getSubstitutor());
403 QuickFixAction.registerQuickFixAction(errorResult, new ChangeExtendsToImplementsFix(aClass, type));
405 return errorResult;
409 static HighlightInfo checkCannotInheritFromFinal(PsiClass superClass, PsiElement elementToHighlight) {
410 HighlightInfo errorResult = null;
411 if (superClass.hasModifierProperty(PsiModifier.FINAL) || superClass.isEnum()) {
412 String message = JavaErrorMessages.message("inheritance.from.final.class", superClass.getQualifiedName());
413 errorResult = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, elementToHighlight, message);
414 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(superClass, PsiModifier.FINAL, false, false);
415 QuickFixAction.registerQuickFixAction(errorResult, fix);
417 return errorResult;
421 static HighlightInfo checkAnonymousInheritFinal(PsiNewExpression expression) {
422 PsiAnonymousClass aClass = PsiTreeUtil.getChildOfType(expression, PsiAnonymousClass.class);
423 if (aClass == null) return null;
424 PsiClassType baseClassReference = aClass.getBaseClassType();
425 PsiClass baseClass = baseClassReference.resolve();
426 if (baseClass == null) return null;
427 return checkCannotInheritFromFinal(baseClass, aClass.getBaseClassReference());
433 private static String checkDefaultConstructorThrowsException(PsiMethod constructor, @NotNull PsiClassType[] handledExceptions) {
434 PsiClassType[] referencedTypes = constructor.getThrowsList().getReferencedTypes();
435 List<PsiClassType> exceptions = new ArrayList<PsiClassType>();
436 for (PsiClassType referencedType : referencedTypes) {
437 if (!ExceptionUtil.isUncheckedException(referencedType) && !ExceptionUtil.isHandledBy(referencedType, handledExceptions)) {
438 exceptions.add(referencedType);
441 if (!exceptions.isEmpty()) {
442 return HighlightUtil.getUnhandledExceptionsDescriptor(exceptions);
444 return null;
448 public static HighlightInfo checkClassDoesNotCallSuperConstructorOrHandleExceptions(PsiClass aClass,
449 RefCountHolder refCountHolder,
450 final PsiResolveHelper resolveHelper) {
451 if (aClass.isEnum()) return null;
452 // check only no-ctr classes. Problem with specific constructor will be highlighted inside it
453 if (aClass.getConstructors().length != 0) return null;
454 // find no-args base class ctr
455 TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
456 return checkBaseClassDefaultConstructorProblem(aClass, refCountHolder, resolveHelper, textRange, PsiClassType.EMPTY_ARRAY);
459 public static HighlightInfo checkBaseClassDefaultConstructorProblem(PsiClass aClass,
460 RefCountHolder refCountHolder,
461 PsiResolveHelper resolveHelper,
462 TextRange textRange,
463 @NotNull PsiClassType[] handledExceptions) {
464 PsiClass baseClass = aClass.getSuperClass();
465 if (baseClass == null) return null;
466 PsiMethod[] constructors = baseClass.getConstructors();
467 if (constructors.length == 0) return null;
469 for (PsiMethod constructor : constructors) {
470 if (resolveHelper.isAccessible(constructor, aClass, null)) {
471 if (constructor.getParameterList().getParametersCount() == 0 ||
472 constructor.getParameterList().getParametersCount() == 1 && constructor.isVarArgs()
474 // it is an error if base ctr throws exceptions
475 String description = checkDefaultConstructorThrowsException(constructor, handledExceptions);
476 if (description != null) {
477 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description);
479 if (refCountHolder != null) {
480 refCountHolder.registerLocallyReferenced(constructor);
482 return null;
487 String description = JavaErrorMessages.message("no.default.constructor.available", HighlightUtil.formatClass(baseClass));
489 HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description);
490 QuickFixAction.registerQuickFixAction(info, new CreateConstructorMatchingSuperFix(aClass));
492 return info;
496 static HighlightInfo checkInterfaceCannotBeLocal(PsiClass aClass) {
497 if (PsiUtil.isLocalClass(aClass)) {
498 TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
499 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
500 textRange,
501 JavaErrorMessages.message("interface.cannot.be.local"));
503 return null;
507 public static HighlightInfo checkCyclicInheritance(PsiClass aClass) {
508 PsiClass circularClass = getCircularClass(aClass, new HashSet<PsiClass>());
509 if (circularClass != null) {
510 String description = JavaErrorMessages.message("cyclic.inheritance", HighlightUtil.formatClass(circularClass));
511 TextRange textRange = HighlightNamesUtil.getClassDeclarationTextRange(aClass);
512 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, textRange, description);
514 return null;
517 private static PsiClass getCircularClass(PsiClass aClass, Collection<PsiClass> usedClasses) {
518 if (usedClasses.contains(aClass)) {
519 return aClass;
521 try {
522 usedClasses.add(aClass);
523 PsiClass[] superTypes = aClass.getSupers();
524 for (PsiElement superType : superTypes) {
525 while (superType instanceof PsiClass) {
526 if (!"java.lang.Object".equals(((PsiClass)superType).getQualifiedName())) {
527 PsiClass circularClass = getCircularClass((PsiClass)superType, usedClasses);
528 if (circularClass != null) return circularClass;
530 // check class qualifier
531 superType = superType.getParent();
535 finally {
536 usedClasses.remove(aClass);
538 return null;
542 public static HighlightInfo checkExtendsDuplicate(PsiJavaCodeReferenceElement element, PsiElement resolved) {
543 if (!(element.getParent() instanceof PsiReferenceList)) return null;
544 PsiReferenceList list = (PsiReferenceList)element.getParent();
545 if (!(list.getParent() instanceof PsiClass)) return null;
546 if (!(resolved instanceof PsiClass)) return null;
547 PsiClass aClass = (PsiClass)resolved;
548 PsiClassType[] referencedTypes = list.getReferencedTypes();
549 int dupCount = 0;
550 for (PsiClassType referencedType : referencedTypes) {
551 PsiClass resolvedElement = referencedType.resolve();
552 if (resolvedElement != null && list.getManager().areElementsEquivalent(resolvedElement, aClass)) {
553 dupCount++;
556 if (dupCount > 1) {
557 String description = JavaErrorMessages.message("duplicate.class", HighlightUtil.formatClass(aClass));
558 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, element, description);
560 return null;
564 public static HighlightInfo checkClassAlreadyImported(PsiClass aClass, PsiElement elementToHighlight) {
565 PsiFile file = aClass.getContainingFile();
566 if (!(file instanceof PsiJavaFile)) return null;
567 PsiJavaFile javaFile = (PsiJavaFile)file;
568 // check only top-level classes conflicts
569 if (aClass.getParent() != javaFile) return null;
570 PsiImportList importList = javaFile.getImportList();
571 if (importList == null) return null;
572 PsiImportStatementBase[] importStatements = importList.getAllImportStatements();
573 for (PsiImportStatementBase importStatement : importStatements) {
574 if (importStatement.isOnDemand()) continue;
575 PsiElement resolved = importStatement.resolve();
576 if (resolved instanceof PsiClass && !resolved.equals(aClass) && Comparing.equal(aClass.getName(), ((PsiClass)resolved).getName(), true)) {
577 String description = JavaErrorMessages.message("class.already.imported", HighlightUtil.formatClass(aClass, false));
578 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
579 elementToHighlight,
580 description);
583 return null;
587 public static HighlightInfo checkClassExtendsOnlyOneClass(PsiReferenceList list) {
588 PsiClassType[] referencedTypes = list.getReferencedTypes();
589 PsiElement parent = list.getParent();
590 if (!(parent instanceof PsiClass)) return null;
592 PsiClass aClass = (PsiClass)parent;
593 if (!aClass.isInterface()
594 && referencedTypes.length > 1
595 && aClass.getExtendsList() == list) {
596 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
597 list,
598 JavaErrorMessages.message("class.cannot.extend.multiple.classes"));
601 return null;
605 public static HighlightInfo checkThingNotAllowedInInterface(PsiElement element, PsiClass aClass) {
606 if (aClass == null || !aClass.isInterface()) return null;
607 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
608 element,
609 JavaErrorMessages.message("not.allowed.in.interface"));
613 public static HighlightInfo checkQualifiedNewOfStaticClass(PsiNewExpression expression) {
614 PsiExpression qualifier = expression.getQualifier();
615 if (qualifier == null) return null;
616 PsiType type = expression.getType();
617 if (type instanceof PsiArrayType) {
618 HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
619 expression,
620 JavaErrorMessages.message("invalid.qualified.new"));
621 QuickFixAction.registerQuickFixAction(info, new RemoveNewQualifierFix(expression, null));
622 return info;
624 PsiClass aClass = PsiUtil.resolveClassInType(type);
625 if (aClass != null && aClass.hasModifierProperty(PsiModifier.STATIC)) {
626 HighlightInfo info = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR,
627 expression,
628 JavaErrorMessages.message("qualified.new.of.static.class"));
629 if (!aClass.isEnum()) {
630 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, false, false);
631 QuickFixAction.registerQuickFixAction(info, fix);
633 QuickFixAction.registerQuickFixAction(info, new RemoveNewQualifierFix(expression, aClass));
634 return info;
636 return null;
641 * class c extends foreign.inner {}
643 * @param extendRef points to the class in the extends list
644 * @param resolved extendRef resolved
646 public static HighlightInfo checkClassExtendsForeignInnerClass(PsiJavaCodeReferenceElement extendRef, PsiElement resolved) {
647 PsiElement parent = extendRef.getParent();
648 if (!(parent instanceof PsiReferenceList)) {
649 return null;
651 PsiElement grand = parent.getParent();
652 if (!(grand instanceof PsiClass)) {
653 return null;
655 PsiClass aClass = (PsiClass)grand;
656 if (aClass.getExtendsList() != parent || aClass instanceof PsiTypeParameter) {
657 return null;
659 if (!(resolved instanceof PsiClass)) {
660 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, extendRef, JavaErrorMessages.message("class.name.expected"));
662 PsiClass base = (PsiClass)resolved;
663 // must be inner class
664 if (!PsiUtil.isInnerClass(base)) return null;
665 PsiClass baseClass = base.getContainingClass();
667 if (!hasEnclosingInstanceInScope(baseClass, extendRef, true) && !qualifiedNewCalledInConstructors(aClass, baseClass)) {
668 String description = JavaErrorMessages.message("no.enclosing.instance.in.scope", HighlightUtil.formatClass(baseClass));
669 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, extendRef, description);
672 return null;
675 private static boolean qualifiedNewCalledInConstructors(final PsiClass aClass, final PsiClass baseClass) {
676 PsiMethod[] constructors = aClass.getConstructors();
677 if (constructors.length == 0) return false;
678 for (PsiMethod constructor : constructors) {
679 PsiCodeBlock body = constructor.getBody();
680 if (body == null) return false;
681 PsiStatement[] statements = body.getStatements();
682 if (statements.length == 0) return false;
683 PsiStatement firstStatement = statements[0];
684 if (!(firstStatement instanceof PsiExpressionStatement)) return false;
685 PsiExpression expression = ((PsiExpressionStatement)firstStatement).getExpression();
686 if (!HighlightUtil.isSuperOrThisMethodCall(expression)) return false;
687 PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
688 if (PsiKeyword.THIS.equals(methodCallExpression.getMethodExpression().getReferenceName())) continue;
689 PsiReferenceExpression referenceExpression = methodCallExpression.getMethodExpression();
690 PsiExpression qualifierExpression = referenceExpression.getQualifierExpression();
691 if (!(qualifierExpression instanceof PsiReferenceExpression)) return false;
692 PsiType type = qualifierExpression.getType();
693 if (!(type instanceof PsiClassType)) return false;
694 PsiClass resolved = ((PsiClassType)type).resolve();
695 if (resolved != baseClass) return false;
697 return true;
701 static HighlightInfo checkExternalizableHasPublicNoArgsConstructor(PsiClass aClass, PsiElement context) {
702 if (!isExternalizable(aClass) || aClass.hasModifierProperty(PsiModifier.ABSTRACT)) {
703 return null;
705 PsiMethod[] constructors = aClass.getConstructors();
706 boolean hasPublicNoArgsConstructor = constructors.length == 0;
707 for (PsiMethod constructor : constructors) {
708 if (constructor.getParameterList().getParametersCount() == 0 && constructor.hasModifierProperty(PsiModifier.PUBLIC)) {
709 hasPublicNoArgsConstructor = true;
710 break;
713 if (!hasPublicNoArgsConstructor) {
714 HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.WARNING,
715 context,
716 JavaErrorMessages.message("externalizable.class.should.have.public.constructor"));
717 QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddDefaultConstructorFix(aClass));
718 return highlightInfo;
720 return null;
723 private static boolean isExternalizable(PsiClass aClass) {
724 PsiManager manager = aClass.getManager();
725 PsiClass externalizableClass =
726 JavaPsiFacade.getInstance(manager.getProject()).findClass("java.io.Externalizable", aClass.getResolveScope());
727 return externalizableClass != null && aClass.isInheritor(externalizableClass, true);
730 public static boolean hasEnclosingInstanceInScope(PsiClass aClass, PsiElement scope, final boolean isSuperClassAccepted) {
731 PsiManager manager = aClass.getManager();
732 PsiElement place = scope;
733 while (place != null && place != aClass && !(place instanceof PsiFile)) {
734 if (place instanceof PsiClass) {
735 if (isSuperClassAccepted) {
736 if (InheritanceUtil.isInheritorOrSelf((PsiClass)place, aClass, true)) return true;
738 else {
739 if (manager.areElementsEquivalent(place, aClass)) return true;
742 if (place instanceof PsiModifierListOwner && ((PsiModifierListOwner)place).hasModifierProperty(PsiModifier.STATIC)) {
743 return false;
745 place = place.getParent();
747 return place == aClass;
750 public static HighlightInfo checkCreateInnerClassFromStaticContext(PsiNewExpression expression) {
751 PsiType type = expression.getType();
752 if (type == null || type instanceof PsiArrayType || type instanceof PsiPrimitiveType) return null;
753 PsiClass aClass = PsiUtil.resolveClassInType(type);
754 if (aClass == null) return null;
755 if (aClass instanceof PsiAnonymousClass) {
756 aClass = ((PsiAnonymousClass)aClass).getBaseClassType().resolve();
757 if (aClass == null) return null;
760 if (!PsiUtil.isInnerClass(aClass)) return null;
761 PsiClass outerClass = aClass.getContainingClass();
762 if (outerClass == null) return null;
764 PsiElement placeToSearchEnclosingFrom;
765 PsiExpression qualifier = expression.getQualifier();
766 if (qualifier != null) {
767 PsiType qtype = qualifier.getType();
768 placeToSearchEnclosingFrom = PsiUtil.resolveClassInType(qtype);
770 else {
771 placeToSearchEnclosingFrom = expression;
774 if (outerClass instanceof JspClass
775 || hasEnclosingInstanceInScope(outerClass, placeToSearchEnclosingFrom, true)) return null;
776 return reportIllegalEnclosingUsage(placeToSearchEnclosingFrom, aClass, outerClass, expression);
779 public static HighlightInfo checkSuperQualifierType(PsiMethodCallExpression superCall) {
780 if (!HighlightUtil.isSuperMethodCall(superCall)) return null;
781 PsiMethod ctr = PsiTreeUtil.getParentOfType(superCall, PsiMethod.class, true, PsiMember.class);
782 if (ctr == null) return null;
783 PsiClass targetClass = ctr.getContainingClass().getSuperClass();
784 if (targetClass == null) return null;
785 PsiExpression qualifier = superCall.getMethodExpression().getQualifierExpression();
786 if (qualifier != null && PsiUtil.isInnerClass(targetClass)) {
787 PsiClass outerClass = targetClass.getContainingClass();
788 PsiClassType outerType = JavaPsiFacade.getInstance(superCall.getProject()).getElementFactory().createType(outerClass);
789 return HighlightUtil.checkAssignability(outerType, null, qualifier, qualifier);
791 return null;
794 public static HighlightInfo reportIllegalEnclosingUsage(PsiElement place,
795 PsiClass aClass, PsiClass outerClass,
796 PsiElement elementToHighlight) {
797 if (outerClass != null && !PsiTreeUtil.isAncestor(outerClass, place, false)) {
798 String description = JavaErrorMessages.message("is.not.an.enclosing.class", HighlightUtil.formatClass(outerClass));
799 return HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, elementToHighlight, description);
801 PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, outerClass);
802 if (staticParent != null) {
803 String description = JavaErrorMessages.message("cannot.be.referenced.from.static.context",
804 outerClass == null
805 ? ""
806 : HighlightUtil.formatClass(outerClass) + "." + PsiKeyword.THIS);
807 HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, elementToHighlight, description);
808 // make context not static or referenced class static
809 IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(staticParent, PsiModifier.STATIC, false, false);
810 QuickFixAction.registerQuickFixAction(highlightInfo, fix);
811 if (aClass != null && HighlightUtil.getIncompatibleModifier(PsiModifier.STATIC, aClass.getModifierList()) == null) {
812 IntentionAction fix2 = QUICK_FIX_FACTORY.createModifierListFix(aClass, PsiModifier.STATIC, true, false);
813 QuickFixAction.registerQuickFixAction(highlightInfo, fix2);
815 return highlightInfo;
817 return null;